3.8. Observer
3.8.1. Purpose
To implement a publish/subscribe behaviour to an object, whenever a “Subject” object changes its state, the attached “Observers” will be notified. It is used to shorten the amount of coupled objects and uses loose coupling instead.
3.8.2. Examples
a message queue system is observed to show the progress of a job in a GUI
3.8.3. Note
PHP already defines two interfaces that can help to implement this pattern: SplObserver and SplSubject.
3.8.4. UML Diagram
3.8.5. Code
You can also find this code on GitHub
User.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Observer;
6
7use SplSubject;
8use SplObjectStorage;
9use SplObserver;
10
11/**
12 * User implements the observed object (called Subject), it maintains a list of observers and sends notifications to
13 * them in case changes are made on the User object
14 */
15class User implements SplSubject
16{
17 private SplObjectStorage $observers;
18 private $email;
19
20 public function __construct()
21 {
22 $this->observers = new SplObjectStorage();
23 }
24
25 public function attach(SplObserver $observer): void
26 {
27 $this->observers->attach($observer);
28 }
29
30 public function detach(SplObserver $observer): void
31 {
32 $this->observers->detach($observer);
33 }
34
35 public function changeEmail(string $email): void
36 {
37 $this->email = $email;
38 $this->notify();
39 }
40
41 public function notify(): void
42 {
43 /** @var SplObserver $observer */
44 foreach ($this->observers as $observer) {
45 $observer->update($this);
46 }
47 }
48}
UserObserver.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Observer;
6
7use SplObserver;
8use SplSubject;
9
10class UserObserver implements SplObserver
11{
12 /**
13 * @var SplSubject[]
14 */
15 private array $changedUsers = [];
16
17 /**
18 * It is called by the Subject, usually by SplSubject::notify()
19 */
20 public function update(SplSubject $subject): void
21 {
22 $this->changedUsers[] = clone $subject;
23 }
24
25 /**
26 * @return SplSubject[]
27 */
28 public function getChangedUsers(): array
29 {
30 return $this->changedUsers;
31 }
32}
3.8.6. Test
Tests/ObserverTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Observer\Tests;
6
7use DesignPatterns\Behavioral\Observer\User;
8use DesignPatterns\Behavioral\Observer\UserObserver;
9use PHPUnit\Framework\TestCase;
10
11class ObserverTest extends TestCase
12{
13 public function testChangeInUserLeadsToUserObserverBeingNotified()
14 {
15 $observer = new UserObserver();
16
17 $user = new User();
18 $user->attach($observer);
19
20 $user->changeEmail('foo@bar.com');
21 $this->assertCount(1, $observer->getChangedUsers());
22 }
23}