
Visitor in PHP
Visitor is a behavioral design pattern that allows adding new behaviors to existing class hierarchy without altering any existing code.
Read why Visitors can’t be simply replaced with method overloading in our article Visitor and Double Dispatch.
Complexity:
Popularity:
Usage examples: The Visitor pattern isn’t very common in PHP code because of its complexity and narrow applicability.
Conceptual Example
This example illustrates the structure of the Visitor design pattern and focuses on the following questions:
- What classes does it consist of?
- What roles do these classes play?
- In what way the elements of the pattern are related?
After learning about the pattern’s structure it’ll be easier for you to grasp the following example, based on a real-world PHP use case.
index.php: Conceptual example
<?php
namespace RefactoringGuru\Visitor\Conceptual;
/**
* The Component interface declares an `accept` method that should take the base
* visitor interface as an argument.
*/
interface Component
{
public function accept(Visitor $visitor): void;
}
/**
* Each Concrete Component must implement the `accept` method in such a way that
* it calls the visitor's method corresponding to the component's class.
*/
class ConcreteComponentA implements Component
{
/**
* Note that we're calling `visitConcreteComponentA`, which matches the
* current class name. This way we let the visitor know the class of the
* component it works with.
*/
public function accept(Visitor $visitor): void
{
$visitor->visitConcreteComponentA($this);
}
/**
* Concrete Components may have special methods that don't exist in their
* base class or interface. The Visitor is still able to use these methods
* since it's aware of the component's concrete class.
*/
public function exclusiveMethodOfConcreteComponentA(): string
{
return "A";
}
}
class ConcreteComponentB implements Component
{
/**
* Same here: visitConcreteComponentB => ConcreteComponentB
*/
public function accept(Visitor $visitor): void
{
$visitor->visitConcreteComponentB($this);
}
public function specialMethodOfConcreteComponentB(): string
{
return "B";
}
}
/**
* The Visitor Interface declares a set of visiting methods that correspond to
* component classes. The signature of a visiting method allows the visitor to
* identify the exact class of the component that it's dealing with.
*/
interface Visitor
{
public function visitConcreteComponentA(ConcreteComponentA $element): void;
public function visitConcreteComponentB(ConcreteComponentB $element): void;
}
/**
* Concrete Visitors implement several versions of the same algorithm, which can
* work with all concrete component classes.
*
* You can experience the biggest benefit of the Visitor pattern when using it
* with a complex object structure, such as a Composite tree. In this case, it
* might be helpful to store some intermediate state of the algorithm while
* executing visitor's methods over various objects of the structure.
*/
class ConcreteVisitor1 implements Visitor
{
public function visitConcreteComponentA(ConcreteComponentA $element): void
{
echo $element->exclusiveMethodOfConcreteComponentA() . " + ConcreteVisitor1\n";
}
public function visitConcreteComponentB(ConcreteComponentB $element): void
{
echo $element->specialMethodOfConcreteComponentB() . " + ConcreteVisitor1\n";
}
}
class ConcreteVisitor2 implements Visitor
{
public function visitConcreteComponentA(ConcreteComponentA $element): void
{
echo $element->exclusiveMethodOfConcreteComponentA() . " + ConcreteVisitor2\n";
}
public function visitConcreteComponentB(ConcreteComponentB $element): void
{
echo $element->specialMethodOfConcreteComponentB() . " + ConcreteVisitor2\n";
}
}
/**
* The client code can run visitor operations over any set of elements without
* figuring out their concrete classes. The accept operation directs a call to
* the appropriate operation in the visitor object.
*/
function clientCode(array $components, Visitor $visitor)
{
// ...
foreach ($components as $component) {
$component->accept($visitor);
}
// ...
}
$components = [
new ConcreteComponentA(),
new ConcreteComponentB(),
];
echo "The client code works with all visitors via the base Visitor interface:\n";
$visitor1 = new ConcreteVisitor1();
clientCode($components, $visitor1);
echo "\n";
echo "It allows the same client code to work with different types of visitors:\n";
$visitor2 = new ConcreteVisitor2();
clientCode($components, $visitor2);
Output.txt: Execution result
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2
Real World Example
In this example, the Visitor pattern helps to introduce a reporting feature into an existing class hierarchy: Company > Department > Employee
Once the Visitor infrastructure is added to the app, you can easily add other similar behaviors to the app, without changing the existing classes.
index.php: Real world example
<?php
namespace RefactoringGuru\Visitor\RealWorld;
/**
* The Component interface declares a method of accepting visitor objects.
*
* In this method, a Concrete Component must call a specific Visitor's method
* that has the same parameter type as that component.
*/
interface Entity
{
public function accept(Visitor $visitor): string;
}
/**
* The Company Concrete Component.
*/
class Company implements Entity
{
private $name;
/**
* @var Department[]
*/
private $departments;
public function __construct(string $name, array $departments)
{
$this->name = $name;
$this->departments = $departments;
}
public function getName(): string
{
return $this->name;
}
public function getDepartments(): array
{
return $this->departments;
}
// ...
public function accept(Visitor $visitor): string
{
// See, the Company component must call the visitCompany method. The
// same principle applies to all components.
return $visitor->visitCompany($this);
}
}
/**
* The Department Concrete Component.
*/
class Department implements Entity
{
private $name;
/**
* @var Employee[]
*/
private $employees;
public function __construct(string $name, array $employees)
{
$this->name = $name;
$this->employees = $employees;
}
public function getName(): string
{
return $this->name;
}
public function getEmployees(): array
{
return $this->employees;
}
public function getCost(): int
{
$cost = 0;
foreach ($this->employees as $employee) {
$cost += $employee->getSalary();
}
return $cost;
}
// ...
public function accept(Visitor $visitor): string
{
return $visitor->visitDepartment($this);
}
}
/**
* The Employee Concrete Component.
*/
class Employee implements Entity
{
private $name;
private $position;
private $salary;
public function __construct(string $name, string $position, int $salary)
{
$this->name = $name;
$this->position = $position;
$this->salary = $salary;
}
public function getName(): string
{
return $this->name;
}
public function getPosition(): string
{
return $this->position;
}
public function getSalary(): int
{
return $this->salary;
}
// ...
public function accept(Visitor $visitor): string
{
return $visitor->visitEmployee($this);
}
}
/**
* The Visitor interface declares a set of visiting methods for each of the
* Concrete Component classes.
*/
interface Visitor
{
public function visitCompany(Company $company): string;
public function visitDepartment(Department $department): string;
public function visitEmployee(Employee $employee): string;
}
/**
* The Concrete Visitor must provide implementations for every single class of
* the Concrete Components.
*/
class SalaryReport implements Visitor
{
public function visitCompany(Company $company): string
{
$output = "";
$total = 0;
foreach ($company->getDepartments() as $department) {
$total += $department->getCost();
$output .= "\n--" . $this->visitDepartment($department);
}
$output = $company->getName() .
" (" . money_format("%i", $total) . ")\n" . $output;
return $output;
}
public function visitDepartment(Department $department): string
{
$output = "";
foreach ($department->getEmployees() as $employee) {
$output .= " " . $this->visitEmployee($employee);
}
$output = $department->getName() .
" (" . money_format("%i", $department->getCost()) . ")\n\n" .
$output;
return $output;
}
public function visitEmployee(Employee $employee): string
{
return money_format("%#6n", $employee->getSalary()) .
" " . $employee->getName() .
" (" . $employee->getPosition() . ")\n";
}
}
/**
* The client code.
*/
$mobileDev = new Department("Mobile Development", [
new Employee("Albert Falmore", "designer", 100000),
new Employee("Ali Halabay", "programmer", 100000),
new Employee("Sarah Konor", "programmer", 90000),
new Employee("Monica Ronaldino", "QA engineer", 31000),
new Employee("James Smith", "QA engineer", 30000),
]);
$techSupport = new Department("Tech Support", [
new Employee("Larry Ulbrecht", "supervisor", 70000),
new Employee("Elton Pale", "operator", 30000),
new Employee("Rajeet Kumar", "operator", 30000),
new Employee("John Burnovsky", "operator", 34000),
new Employee("Sergey Korolev", "operator", 35000),
]);
$company = new Company("SuperStarDevelopment", [$mobileDev, $techSupport]);
setlocale(LC_MONETARY, 'en_US');
$report = new SalaryReport();
echo "Client: I can print a report for a whole company:\n\n";
echo $company->accept($report);
echo "\nClient: ...or for different entities " .
"such as an employee, a department, or the whole company:\n\n";
$someEmployee = new Employee("Some employee", "operator", 35000);
$differentEntities = [$someEmployee, $techSupport, $company];
foreach ($differentEntities as $entity) {
echo $entity->accept($report) . "\r\n";
}
// $export = new JSONExport();
// echo $company->accept($export);
Output.txt: Execution result
Client: I can print a report for a whole company:
SuperStarDevelopment (USD550,000.00)
--Mobile Development (USD351,000.00)
$100,000.00 Albert Falmore (designer)
$100,000.00 Ali Halabay (programmer)
$ 90,000.00 Sarah Konor (programmer)
$ 31,000.00 Monica Ronaldino (QA engineer)
$ 30,000.00 James Smith (QA engineer)
--Tech Support (USD199,000.00)
$ 70,000.00 Larry Ulbrecht (supervisor)
$ 30,000.00 Elton Pale (operator)
$ 30,000.00 Rajeet Kumar (operator)
$ 34,000.00 John Burnovsky (operator)
$ 35,000.00 Sergey Korolev (operator)
Client: ...or for different entities such as an employee, a department, or the whole company:
35000 Some employee (operator)
Tech Support (199000)
70000 Larry Ulbrecht (supervisor)
30000 Elton Pale (operator)
30000 Rajeet Kumar (operator)
34000 John Burnovsky (operator)
35000 Sergey Korolev (operator)
SuperStarDevelopment (550000)
--Mobile Development (351000)
100000 Albert Falmore (designer)
100000 Ali Halabay (programmer)
90000 Sarah Konor (programmer)
31000 Monica Ronaldino (QA engineer)
30000 James Smith (QA engineer)
--Tech Support (199000)
70000 Larry Ulbrecht (supervisor)
30000 Elton Pale (operator)
30000 Rajeet Kumar (operator)
34000 John Burnovsky (operator)
35000 Sergey Korolev (operator)