3.7. Observer

3.7.1. Purpose

To implement a publish/subscribe behaviour to an object, whenever a“Subject” object changes its state, the attached “Observers” will benotified. It is used to shorten the amount of coupled objects and usesloose coupling instead.

3.7.2. Examples

  • a message queue system is observed to show the progress of a job in aGUI

3.7.3. Note

PHP already defines two interfaces that can help to implement thispattern: SplObserver and SplSubject.

3.7.4. UML Diagram

Alt Observer UML Diagram

3.7.5. Code

You can also find this code on GitHub

User.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Observer;
  4.  
  5. /**
  6. * User implements the observed object (called Subject), it maintains a list of observers and sends notifications to
  7. * them in case changes are made on the User object
  8. */
  9. class User implements \SplSubject
  10. {
  11. /**
  12. * @var string
  13. */
  14. private $email;
  15.  
  16. /**
  17. * @var \SplObjectStorage
  18. */
  19. private $observers;
  20.  
  21. public function __construct()
  22. {
  23. $this->observers = new \SplObjectStorage();
  24. }
  25.  
  26. public function attach(\SplObserver $observer)
  27. {
  28. $this->observers->attach($observer);
  29. }
  30.  
  31. public function detach(\SplObserver $observer)
  32. {
  33. $this->observers->detach($observer);
  34. }
  35.  
  36. public function changeEmail(string $email)
  37. {
  38. $this->email = $email;
  39. $this->notify();
  40. }
  41.  
  42. public function notify()
  43. {
  44. /** @var \SplObserver $observer */
  45. foreach ($this->observers as $observer) {
  46. $observer->update($this);
  47. }
  48. }
  49. }

UserObserver.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Observer;
  4.  
  5. class UserObserver implements \SplObserver
  6. {
  7. /**
  8. * @var User[]
  9. */
  10. private $changedUsers = [];
  11.  
  12. /**
  13. * It is called by the Subject, usually by SplSubject::notify()
  14. *
  15. * @param \SplSubject $subject
  16. */
  17. public function update(\SplSubject $subject)
  18. {
  19. $this->changedUsers[] = clone $subject;
  20. }
  21.  
  22. /**
  23. * @return User[]
  24. */
  25. public function getChangedUsers(): array
  26. {
  27. return $this->changedUsers;
  28. }
  29. }

3.7.6. Test

Tests/ObserverTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Observer\Tests;
  4.  
  5. use DesignPatterns\Behavioral\Observer\User;
  6. use DesignPatterns\Behavioral\Observer\UserObserver;
  7. use PHPUnit\Framework\TestCase;
  8.  
  9. class ObserverTest extends TestCase
  10. {
  11. public function testChangeInUserLeadsToUserObserverBeingNotified()
  12. {
  13. $observer = new UserObserver();
  14.  
  15. $user = new User();
  16. $user->attach($observer);
  17.  
  18. $user->changeEmail('foo@bar.com');
  19. $this->assertCount(1, $observer->getChangedUsers());
  20. }
  21. }