1. <?php
    2. /**
    3. * The UserRepository represents a Subject. Various objects are interested in
    4. * tracking its internal state, whether it's adding a new user or removing one.
    5. */
    6. class UserRepository implements \SplSubject
    7. {
    8. /**
    9. * @var array The list of users.
    10. */
    11. private $users = [];
    12. // Here goes the actual Observer management infrastructure. Note that it's
    13. // not everything that our class is responsible for. Its primary business
    14. // logic is listed below these methods.
    15. /**
    16. * @var array
    17. */
    18. private $observers = [];
    19. public function __construct()
    20. {
    21. // A special event group for observers that want to listen to all
    22. // events.
    23. $this->observers["*"] = [];
    24. }
    25. private function initEventGroup(string $event = "*"): void
    26. {
    27. if (!isset($this->observers[$event])) {
    28. $this->observers[$event] = [];
    29. }
    30. }
    31. private function getEventObservers(string $event = "*"): array
    32. {
    33. $this->initEventGroup($event);
    34. $group = $this->observers[$event];
    35. $all = $this->observers["*"];
    36. return array_merge($group, $all);
    37. }
    38. public function attach(\SplObserver $observer, string $event = "*"): void
    39. {
    40. $this->initEventGroup($event);
    41. $this->observers[$event][] = $observer;
    42. }
    43. public function detach(\SplObserver $observer, string $event = "*"): void
    44. {
    45. foreach ($this->getEventObservers($event) as $key => $s) {
    46. if ($s === $observer) {
    47. unset($this->observers[$event][$key]);
    48. }
    49. }
    50. }
    51. public function notify(string $event = "*", $data = null): void
    52. {
    53. echo "UserRepository: Broadcasting the '$event' event.\n";
    54. foreach ($this->getEventObservers($event) as $observer) {
    55. $observer->update($this, $event, $data);
    56. }
    57. }
    58. // Here are the methods representing the business logic of the class.
    59. public function initialize($filename): void
    60. {
    61. echo "UserRepository: Loading user records from a file.\n";
    62. // ...
    63. $this->notify("users:init", $filename);
    64. }
    65. public function createUser(array $data): User
    66. {
    67. echo "UserRepository: Creating a user.\n";
    68. $user = new User();
    69. $user->update($data);
    70. $id = bin2hex(openssl_random_pseudo_bytes(16));
    71. $user->update(["id" => $id]);
    72. $this->users[$id] = $user;
    73. $this->notify("users:created", $user);
    74. return $user;
    75. }
    76. public function updateUser(User $user, array $data): ?User
    77. {
    78. echo "UserRepository: Updating a user.\n";
    79. $id = $user->attributes["id"];
    80. if (!isset($this->users[$id])) {
    81. return null;
    82. }
    83. $user = $this->users[$id];
    84. $user->update($data);
    85. $this->notify("users:updated", $user);
    86. return $user;
    87. }
    88. public function deleteUser(User $user): void
    89. {
    90. echo "UserRepository: Deleting a user.\n";
    91. $id = $user->attributes["id"];
    92. if (!isset($this->users[$id])) {
    93. return;
    94. }
    95. unset($this->users[$id]);
    96. $this->notify("users:deleted", $user);
    97. }
    98. }
    99. /**
    100. * Let's keep the User class trivial since it's not the focus of our example.
    101. */
    102. class User
    103. {
    104. public $attributes = [];
    105. public function update($data): void
    106. {
    107. $this->attributes = array_merge($this->attributes, $data);
    108. }
    109. }
    110. /**
    111. * This Concrete Component logs any events it's subscribed to.
    112. */
    113. class Logger implements \SplObserver
    114. {
    115. private $filename;
    116. public function __construct($filename)
    117. {
    118. $this->filename = $filename;
    119. if (file_exists($this->filename)) {
    120. unlink($this->filename);
    121. }
    122. }
    123. public function update(\SplSubject $repository, string $event = null, $data = null): void
    124. {
    125. $entry = date("Y-m-d H:i:s") . ": '$event' with data '" . json_encode($data,320) . "'\n";
    126. file_put_contents($this->filename, $entry, FILE_APPEND);
    127. echo "Logger: I've written '$event' entry to the log.\n";
    128. }
    129. }
    130. /**
    131. * This Concrete Component sends initial instructions to new users. The client
    132. * is responsible for attaching this component to a proper user creation event.
    133. */
    134. class OnboardingNotification implements \SplObserver
    135. {
    136. private $adminEmail;
    137. public function __construct($adminEmail)
    138. {
    139. $this->adminEmail = $adminEmail;
    140. }
    141. public function update(\SplSubject $repository, string $event = null, $data = null): void
    142. {
    143. // mail($this->adminEmail,
    144. // "Onboarding required",
    145. // "We have a new user. Here's his info: " .json_encode($data));
    146. echo "OnboardingNotification: The notification has been emailed!\n";
    147. }
    148. }
    149. /**
    150. * The client code.
    151. */
    152. $repository = new UserRepository();
    153. $repository->attach(new Logger(__DIR__ . "/log.txt"), "*");
    154. $repository->attach(new OnboardingNotification("1@example.com"), "users:created");
    155. $repository->initialize(__DIR__ . "/users.csv");
    156. // ...
    157. $user = $repository->createUser([
    158. "name" => "John Smith",
    159. "email" => "john99@example.com",
    160. ]);
    161. // ...
    162. $repository->deleteUser($user);