PHP7 Visitor

This commit is contained in:
Dominik Liebler
2016-09-22 11:29:48 +02:00
parent 19fff0aed9
commit 1c30978a70
9 changed files with 72 additions and 149 deletions

View File

@@ -2,29 +2,25 @@
namespace DesignPatterns\Behavioral\Visitor;
/**
* An example of a Visitor: Group.
*/
class Group extends Role
class Group implements Role
{
/**
* @var string
*/
protected $name;
private $name;
/**
* @param string $name
*/
public function __construct($name)
public function __construct(string $name)
{
$this->name = (string) $name;
$this->name = $name;
}
/**
* @return string
*/
public function getName()
public function getName(): string
{
return 'Group: '.$this->name;
return sprintf('Group: %s', $this->name);
}
public function accept(RoleVisitorInterface $visitor)
{
$visitor->visitGroup($this);
}
}

View File

@@ -31,9 +31,9 @@ RoleVisitorInterface.php
:language: php
:linenos:
RolePrintVisitor.php
RoleVisitor.php
.. literalinclude:: RolePrintVisitor.php
.. literalinclude:: RoleVisitor.php
:language: php
:linenos:

View File

@@ -2,32 +2,7 @@
namespace DesignPatterns\Behavioral\Visitor;
/**
* class Role.
*/
abstract class Role
interface Role
{
/**
* This method handles a double dispatch based on the short name of the Visitor.
*
* Feel free to override it if your object must call another visiting behavior
*
* @param \DesignPatterns\Behavioral\Visitor\RoleVisitorInterface $visitor
*
* @throws \InvalidArgumentException
*/
public function accept(RoleVisitorInterface $visitor)
{
// this trick to simulate double-dispatch based on type-hinting
$klass = get_called_class();
preg_match('#([^\\\\]+)$#', $klass, $extract);
$visitingMethod = 'visit'.$extract[1];
// this ensures strong typing with visitor interface, not some visitor objects
if (!method_exists(__NAMESPACE__.'\RoleVisitorInterface', $visitingMethod)) {
throw new \InvalidArgumentException("The visitor you provide cannot visit a $klass instance");
}
call_user_func(array($visitor, $visitingMethod), $this);
}
public function accept(RoleVisitorInterface $visitor);
}

View File

@@ -1,27 +0,0 @@
<?php
namespace DesignPatterns\Behavioral\Visitor;
/**
* Visitor Pattern.
*
* An implementation of a concrete Visitor
*/
class RolePrintVisitor implements RoleVisitorInterface
{
/**
* {@inheritdoc}
*/
public function visitGroup(Group $role)
{
echo 'Role: '.$role->getName();
}
/**
* {@inheritdoc}
*/
public function visitUser(User $role)
{
echo 'Role: '.$role->getName();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace DesignPatterns\Behavioral\Visitor;
class RoleVisitor implements RoleVisitorInterface
{
/**
* @var Role[]
*/
private $visited = [];
public function visitGroup(Group $role)
{
$this->visited[] = $role;
}
public function visitUser(User $role)
{
$this->visited[] = $role;
}
/**
* @return Role[]
*/
public function getVisited(): array
{
return $this->visited;
}
}

View File

@@ -3,29 +3,12 @@
namespace DesignPatterns\Behavioral\Visitor;
/**
* Visitor Pattern.
*
* The contract for the visitor.
*
* Note 1 : in C++ or Java, with method polymorphism based on type-hint, there are many
* methods visit() with different type for the 'role' parameter.
*
* Note 2 : the visitor must not choose itself which method to
* invoke, it is the Visitee that make this decision.
* Note: the visitor must not choose itself which method to
* invoke, it is the Visitee that make this decision
*/
interface RoleVisitorInterface
{
/**
* Visit a User object.
*
* @param \DesignPatterns\Behavioral\Visitor\User $role
*/
public function visitUser(User $role);
/**
* Visit a Group object.
*
* @param \DesignPatterns\Behavioral\Visitor\Group $role
*/
public function visitGroup(Group $role);
}

View File

@@ -4,42 +4,34 @@ namespace DesignPatterns\Tests\Visitor\Tests;
use DesignPatterns\Behavioral\Visitor;
/**
* VisitorTest tests the visitor pattern.
*/
class VisitorTest extends \PHPUnit_Framework_TestCase
{
protected $visitor;
/**
* @var Visitor\RoleVisitor
*/
private $visitor;
protected function setUp()
{
$this->visitor = new Visitor\RolePrintVisitor();
$this->visitor = new Visitor\RoleVisitor();
}
public function getRole()
public function provideRoles()
{
return array(
array(new Visitor\User('Dominik'), 'Role: User Dominik'),
array(new Visitor\Group('Administrators'), 'Role: Group: Administrators'),
);
return [
[new Visitor\User('Dominik')],
[new Visitor\Group('Administrators')],
];
}
/**
* @dataProvider getRole
* @dataProvider provideRoles
*
* @param Visitor\Role $role
*/
public function testVisitSomeRole(Visitor\Role $role, $expect)
public function testVisitSomeRole(Visitor\Role $role)
{
$this->expectOutputString($expect);
$role->accept($this->visitor);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Mock
*/
public function testUnknownObject()
{
$mock = $this->getMockForAbstractClass('DesignPatterns\Behavioral\Visitor\Role');
$mock->accept($this->visitor);
$this->assertSame($role, $this->visitor->getVisited()[0]);
}
}

View File

@@ -2,31 +2,25 @@
namespace DesignPatterns\Behavioral\Visitor;
/**
* Visitor Pattern.
*
* One example for a visitee. Each visitee has to extends Role
*/
class User extends Role
class User implements Role
{
/**
* @var string
*/
protected $name;
private $name;
/**
* @param string $name
*/
public function __construct($name)
public function __construct(string $name)
{
$this->name = (string) $name;
$this->name = $name;
}
/**
* @return string
*/
public function getName()
public function getName(): string
{
return 'User '.$this->name;
return sprintf('User %s', $this->name);
}
public function accept(RoleVisitorInterface $visitor)
{
$visitor->visitUser($this);
}
}