3.13. Visitor
3.13.1. Rôle
Le pattern Visitor vous permet de sous-traiter des opérations sur des objets à d’autres objets. La principale raison de faire cela est de maintenir une séparation des préoccupations. Mais les classes doivent définir un contrat pour autoriser les visiteurs (la méthode Role::accept
dans l’exemple).
Le contrat est une classe abstraite mais vous pouvez aussi avoir une interface propre. Dans ce cas, chaque visiteur doit choisir lui-même la méthode à invoquer sur le visiteur.
3.13.2. Diagramme UML

3.13.3. Code
Vous pouvez également trouver ce code sur GitHub
RoleVisitor.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7/**
8 * Note: the visitor must not choose itself which method to
9 * invoke, it is the visited object that makes this decision
10 */
11interface RoleVisitor
12{
13 public function visitUser(User $role);
14
15 public function visitGroup(Group $role);
16}
RecordingVisitor.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7class RecordingVisitor implements RoleVisitor
8{
9 /**
10 * @var Role[]
11 */
12 private array $visited = [];
13
14 public function visitGroup(Group $role)
15 {
16 $this->visited[] = $role;
17 }
18
19 public function visitUser(User $role)
20 {
21 $this->visited[] = $role;
22 }
23
24 /**
25 * @return Role[]
26 */
27 public function getVisited(): array
28 {
29 return $this->visited;
30 }
31}
Role.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7interface Role
8{
9 public function accept(RoleVisitor $visitor);
10}
User.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7class User implements Role
8{
9 public function __construct(private string $name)
10 {
11 }
12
13 public function getName(): string
14 {
15 return sprintf('User %s', $this->name);
16 }
17
18 public function accept(RoleVisitor $visitor)
19 {
20 $visitor->visitUser($this);
21 }
22}
Group.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7class Group implements Role
8{
9 public function __construct(private string $name)
10 {
11 }
12
13 public function getName(): string
14 {
15 return sprintf('Group: %s', $this->name);
16 }
17
18 public function accept(RoleVisitor $visitor)
19 {
20 $visitor->visitGroup($this);
21 }
22}
3.13.4. Test
Tests/VisitorTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Tests\Visitor\Tests;
6
7use DesignPatterns\Behavioral\Visitor\RecordingVisitor;
8use DesignPatterns\Behavioral\Visitor\User;
9use DesignPatterns\Behavioral\Visitor\Group;
10use DesignPatterns\Behavioral\Visitor\Role;
11use DesignPatterns\Behavioral\Visitor;
12use PHPUnit\Framework\TestCase;
13
14class VisitorTest extends TestCase
15{
16 private RecordingVisitor $visitor;
17
18 protected function setUp(): void
19 {
20 $this->visitor = new RecordingVisitor();
21 }
22
23 public function provideRoles()
24 {
25 return [
26 [new User('Dominik')],
27 [new Group('Administrators')],
28 ];
29 }
30
31 /**
32 * @dataProvider provideRoles
33 */
34 public function testVisitSomeRole(Role $role)
35 {
36 $role->accept($this->visitor);
37 $this->assertSame($role, $this->visitor->getVisited()[0]);
38 }
39}