3.12. Visitor

3.12.1. Purpose

The Visitor Pattern lets you outsource operations on objects to otherobjects. The main reason to do this is to keep a separation of concerns.But classes have to define a contract to allow visitors (theRole::accept method in the example).

The contract is an abstract class but you can have also a cleaninterface. In that case, each Visitor has to choose itself which methodto invoke on the visitor.

3.12.2. UML Diagram

Alt Visitor UML Diagram

3.12.3. Code

You can also find this code on GitHub

RoleVisitorInterface.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Visitor;
  4.  
  5. /**
  6. * Note: the visitor must not choose itself which method to
  7. * invoke, it is the Visitee that make this decision
  8. */
  9. interface RoleVisitorInterface
  10. {
  11. public function visitUser(User $role);
  12.  
  13. public function visitGroup(Group $role);
  14. }

RoleVisitor.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Visitor;
  4.  
  5. class RoleVisitor implements RoleVisitorInterface
  6. {
  7. /**
  8. * @var Role[]
  9. */
  10. private $visited = [];
  11.  
  12. public function visitGroup(Group $role)
  13. {
  14. $this->visited[] = $role;
  15. }
  16.  
  17. public function visitUser(User $role)
  18. {
  19. $this->visited[] = $role;
  20. }
  21.  
  22. /**
  23. * @return Role[]
  24. */
  25. public function getVisited(): array
  26. {
  27. return $this->visited;
  28. }
  29. }

Role.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Visitor;
  4.  
  5. interface Role
  6. {
  7. public function accept(RoleVisitorInterface $visitor);
  8. }

User.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Visitor;
  4.  
  5. class User implements Role
  6. {
  7. /**
  8. * @var string
  9. */
  10. private $name;
  11.  
  12. public function __construct(string $name)
  13. {
  14. $this->name = $name;
  15. }
  16.  
  17. public function getName(): string
  18. {
  19. return sprintf('User %s', $this->name);
  20. }
  21.  
  22. public function accept(RoleVisitorInterface $visitor)
  23. {
  24. $visitor->visitUser($this);
  25. }
  26. }

Group.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Visitor;
  4.  
  5. class Group implements Role
  6. {
  7. /**
  8. * @var string
  9. */
  10. private $name;
  11.  
  12. public function __construct(string $name)
  13. {
  14. $this->name = $name;
  15. }
  16.  
  17. public function getName(): string
  18. {
  19. return sprintf('Group: %s', $this->name);
  20. }
  21.  
  22. public function accept(RoleVisitorInterface $visitor)
  23. {
  24. $visitor->visitGroup($this);
  25. }
  26. }

3.12.4. Test

Tests/VisitorTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Tests\Visitor\Tests;
  4.  
  5. use DesignPatterns\Behavioral\Visitor;
  6. use PHPUnit\Framework\TestCase;
  7.  
  8. class VisitorTest extends TestCase
  9. {
  10. /**
  11. * @var Visitor\RoleVisitor
  12. */
  13. private $visitor;
  14.  
  15. protected function setUp()
  16. {
  17. $this->visitor = new Visitor\RoleVisitor();
  18. }
  19.  
  20. public function provideRoles()
  21. {
  22. return [
  23. [new Visitor\User('Dominik')],
  24. [new Visitor\Group('Administrators')],
  25. ];
  26. }
  27.  
  28. /**
  29. * @dataProvider provideRoles
  30. *
  31. * @param Visitor\Role $role
  32. */
  33. public function testVisitSomeRole(Visitor\Role $role)
  34. {
  35. $role->accept($this->visitor);
  36. $this->assertSame($role, $this->visitor->getVisited()[0]);
  37. }
  38. }