Visitor pattern moves the calculation/operation to a separate class. This way, we can define a new operation without changing the related classes.
This article demonstrates Visitor pattern implementations in PHP. Check the following examples.
Implementation
Check the following steps for Visitor pattern implementation in PHP-
- Create an interface for the element/item classes. Declare a method that accepts a visitor object.
- Create element/item classes and implement the item interface. There can be other properties and methods as per the requirement of that class.
- Create visitor interface, and declare methods for adding/processing elements.
- Create visitor class and implement the interface. Define methods declared in the interface. Use properties to handle the processing of the content of the elements.
Here is a simple Visitor pattern implementation-
<?php
// Visitor pattern implementation in PHP
interface ElementInterface {
function visit(VisitorInterface $visitor): void;
}
class Element1 implements ElementInterface {
public function __construct(private string $elementContent) {
}
public function getContent(): string{
return $this->elementContent;
}
public function visit(VisitorInterface $visitor): void {
$visitor->addContent($this);
}
}
class Element2 implements ElementInterface {
public function __construct(private string $elementContent, private bool $addMergin = false) {
}
public function getContent(): string{
return $this->elementContent;
}
public function getMargin(): bool{
return $this->addMergin;
}
public function visit(VisitorInterface $visitor): void {
$visitor->addContentWithMargin($this);
}
}
interface VisitorInterface {
function addContent(Element1 $element): void;
function addContentWithMargin(Element2 $element): void;
}
class Visitor implements VisitorInterface {
public string $output = '';
public function addContent(Element1 $element1): void {
$this->output .= $element1->getContent();
}
public function addContentWithMargin(Element2 $element2): void {
if ($element2->getMargin()) {
$this->output .= '[margin]' . $element2->getContent() . '[/margin]';
} else {
$this->output .= $element2->getContent();
}
}
}
// list of elements
$elements = [
new Element1("Element1: first element"),
new Element2("Element2: second element without margin"),
new Element2("Element2: third element with margin", true),
new Element1("Element1: last element"),
];
$visitior = new Visitor();
foreach ($elements as $element) {
$element->visit($visitior);
}
echo $visitior->output;
Output will be as below-
Element1: first element
Element2: second element without margin
[margin]Element2: third element with margin[/margin]
Element1: last element
Examples
Check the following example of Visitor pattern-
Example #1: Hosting Cost Calculator
In this example we are calculating costing of a hosting provider. There are services like – compute, database, serverless, storage, etc. provided by the hosting.
We want to use the total cost of hosting based on the service used, by implementing Visitor pattern.
Service Interface
- Create file “Service.php”.
- Define interface “Service”.
- Declare method “accept” which accepts a visitor object.
<?php
// Service.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
interface Service {
public function accept(HostingCalculatorVisitor $hostingCalculatorVisitor): float;
}
Compute Service Class
- Create file “ComputeService.php”.
- Define class “ComputeService”.
- Implement the “Service” interface.
- Define properties “PRICE” and “$quantity”.
- In the constructor accept and set “$quantity”.
- Define getter methods for price and quantity.
- Define “accept” method as part of the interface implementation. Call the processing method from the visitor object, and pass the current object($this).
<?php
// ComputeService.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
class ComputeService implements Service {
private const PRICE = 10.50;
private int $quantity;
public function __construct(int $quantity) {
$this->quantity = $quantity;
}
public function getPrice(): float {
return self::PRICE;
}
public function getQuantity(): int {
return $this->quantity;
}
public function accept(HostingCalculatorVisitor $hostingCalculatorVisitor): float {
return $hostingCalculatorVisitor->visitComputeService($this);
}
}
Database Service Class
- Create file “DatabaseService.php”.
- Define class “DatabaseService” and implement the “Service” interface.
- Define properties and methods as per the class requirement.
<?php
// DatabaseService.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
class DatabaseService implements Service {
private const PRICE = 100.00;
private const BACKUP_PRICE = 30.00;
private int $quantity;
private bool $backupEnabled;
public function __construct(int $quantity, bool $backupEnabled = false) {
$this->quantity = $quantity;
$this->backupEnabled = $backupEnabled;
}
public function getPrice(): float {
return self::PRICE;
}
public function getQuantity(): int {
return $this->quantity;
}
public function getBackupPrice(): float {
return self::BACKUP_PRICE;
}
public function isBackupEnabled(): bool {
return $this->backupEnabled;
}
public function accept(HostingCalculatorVisitor $hostingCalculatorVisitor): float {
return $hostingCalculatorVisitor->visitDatabaseService($this);
}
}
File Storage Service Class
- Create file “FileStorageService.php”.
- Define class “FileStorageService” and implement the “Service” interface.
- Define properties and methods as per the class requirement.
<?php
// FileStorageService.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
class FileStorageService implements Service {
private const PRICE_PER_GB = 1.70;
private int $quantity;
public function __construct(int $quantity) {
$this->quantity = $quantity;
}
public function getPricePerGB(): float {
return self::PRICE_PER_GB;
}
public function getQuantity(): int {
return $this->quantity;
}
public function accept(HostingCalculatorVisitor $hostingCalculatorVisitor): float {
return $hostingCalculatorVisitor->visitFileStorageService($this);
}
}
Serverless Service Class
- Create file “ServerlessService.php”.
- Define class “ServerlessService” and implement the “Service” interface.
- Define properties and methods as per the class requirement.
<?php
// ServerlessService.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
class ServerlessService implements Service {
private const HOURLY_PRICE = 0.32;
private int $totalHours;
public function __construct(int $totalHours) {
$this->totalHours = $totalHours;
}
public function getHourlyPrice(): float {
return self::HOURLY_PRICE;
}
public function getTotalHours(): int {
return $this->totalHours;
}
public function accept(HostingCalculatorVisitor $hostingCalculatorVisitor): float {
return $hostingCalculatorVisitor->visitServerlessService($this);
}
}
Container Service Class
- Create file “ContainerService.php”.
- Define class “ContainerService” and implement the “Service” interface.
- Define properties and methods as per the class requirement.
<?php
// ContainerService.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
class ContainerService implements Service {
private const PRICE = 5.60;
private int $quantity;
public function __construct(int $quantity) {
$this->quantity = $quantity;
}
public function getPrice(): float {
return self::PRICE;
}
public function getQuantity(): int {
return $this->quantity;
}
public function accept(HostingCalculatorVisitor $hostingCalculatorVisitor): float {
return $hostingCalculatorVisitor->visitContainerService($this);
}
}
Visitor Interface
- Create file “HostingCalculatorVisitor.php”.
- Define interface “HostingCalculatorVisitor”.
- Declare methods for calculation of different services, like – “visitComputService”, “visitDatabaseService”, “visitFileStorageService” etc. These methods will be used to perform the main calculation for each service.
<?php
// HostingCalculatorVisitor.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
interface HostingCalculatorVisitor {
public function visitComputeService(ComputeService $computeService): float;
public function visitDatabaseService(DatabaseService $databaseService): float;
public function visitFileStorageService(FileStorageService $fileStorageService): float;
public function visitServerlessService(ServerlessService $serverlessService): float;
public function visitContainerService(ContainerService $containerService): float;
}
Visitor Interface Implementation
- Create file “HostingCalculatorVisitorImpl.php”.
- Define class “HostingCalculatorVisitorImpl”.
- Implement the visitor interface “HostingCalculatorVisitor” for the class.
- In the method implementations calculate the prices for relevant services.
<?php
// HostingCalculatorVisitorImpml.php
namespace BigBoxCodeDesignPatternVisitorHostingCost;
class HostingCalculatorVisitorImpl implements HostingCalculatorVisitor {
public function visitComputeService(ComputeService $computeService): float {
return $computeService->getPrice() * $computeService->getQuantity();
}
public function visitDatabaseService(DatabaseService $databaseService): float {
$serviceCost = $databaseService->getPrice() * $databaseService->getQuantity();
$backupCost = 0;
if ($databaseService->isBackupEnabled()) {
$backupCost = $databaseService->getBackupPrice() * $databaseService->getQuantity();
}
return $serviceCost + $backupCost;
}
public function visitFileStorageService(FileStorageService $fileStorageService): float {
return $fileStorageService->getPricePerGB() * $fileStorageService->getQuantity();
}
public function visitServerlessService(ServerlessService $serverlessService): float {
return $serverlessService->getHourlyPrice() * $serverlessService->getTotalHours();
}
public function visitContainerService(ContainerService $containerService): float {
return $containerService->getPrice() * $containerService->getQuantity();
}
}
Demo
In the client, define which services we want to use, and add a service object to your list of services. Then call the “accept” method to obtain the calculated price.
<?php
// demo.php
require __DIR__ . '/../../vendor/autoload.php';
use BigBoxCodeDesignPatternVisitorHostingCostComputeService;
use BigBoxCodeDesignPatternVisitorHostingCostContainerService;
use BigBoxCodeDesignPatternVisitorHostingCostDatabaseService;
use BigBoxCodeDesignPatternVisitorHostingCostFileStorageService;
use BigBoxCodeDesignPatternVisitorHostingCostHostingCalculatorVisitorImpl;
use BigBoxCodeDesignPatternVisitorHostingCostServerlessService;
// Utility function calculating the hosting cost
// This is not required for the Visitor pattern implementation
// This uses the visitor pattern implementation
function calculateHostingCost(array $services): float {
$hostingCalculatorVisitorImpl = new HostingCalculatorVisitorImpl();
$totalCost = 0;
foreach ($services as $service) {
$totalCost += $service->accept($hostingCalculatorVisitorImpl);
}
return $totalCost;
}
// Demo
$usedServices = [
new ComputeService(3),
new DatabaseService(3, true),
new FileStorageService(120),
new ServerlessService(720),
new ContainerService(2),
];
$totalCost = calculateHostingCost($usedServices);
echo "Total cost of hosting is: " . $totalCost . "n";
Output
Output of the total price of hosting will be as below-
Total cost of hosting is: 867.1
Example #1: UI Elements
In this example, we are considering a system for printing UI Elements.
UI Element Interface
- Create file “UiElement.php”.
- Define interface “UiElement”.
- Declare method “appendElement” which accepts a visitor object.
<?php
// UiElement.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
interface UIElement {
public function appendElement(Visitor $visitor): void;
}
Text Element Class
- Create file “TextElement.php”.
- Define class “TextElement “.
- Implement “UIElement” interface for the class. In the “appendElement” method implementation call the “appendContent” from the visitor object, and pass the current object($this).
<?php
// TextElement.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
class TextElement implements UIElement {
private string $text;
public function __construct(string $text) {
$this->text = $text;
}
public function getText(): string {
return $this->text;
}
public function appendElement(Visitor $visitor): void {
$visitor->appendContent($this);
}
}
Wrap Element Class
- Create file “WrapElement.php”.
- Define class “WrapElement” and implement interface “UIElement” for the class.
<?php
// WrapElement.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
class WrapElement implements UIElement {
private string $text;
private string $wrapper;
public function __construct(string $text, string $wrapper) {
$this->text = $text;
$this->wrapper = $wrapper;
}
public function getText(): string {
return $this->text;
}
public function getWrapper(): string {
return $this->wrapper;
}
public function appendElement(Visitor $visitor): void {
$visitor->appendContentWithWrapper($this);
}
}
Head Element Class
- Create file “HeadElement.php”.
- Define class “HeadElement” and implement interface “UIElement” for the class.
<?php
// HeadElement.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
class HeadElement implements UIElement {
private string $text;
private string $wrapper = 'h1';
public function __construct(string $text) {
$this->text = $text;
}
public function getText(): string {
return $this->text;
}
public function getWrapper(): string {
return $this->wrapper;
}
public function appendElement(Visitor $visitor): void {
$visitor->appendContentWithWrapper($this);
}
}
List Element Class
- Create file “ListElement.php”.
- Define class “ListElement” and implement interface “UIElement” for the class.
<?php
// ListElement.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
class ListElement implements UIElement {
private array $lines;
public function __construct(array $lines) {
$this->lines = $lines;
}
public function getListItems(): array {
return $this->lines;
}
public function appendElement(Visitor $visitor): void {
$visitor->appendList($this);
}
}
Visitor Interface
- Create file “Visitor.php”.
- Define interface “Visitor”.
- Declare methods for the content processing, like – “appendContent”, “appendContentWithWrapper”, “appendList”.
<?php
// Visitor.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
interface Visitor {
public function appendContent(TextElement $textElement): void;
public function appendContentWithWrapper(WrapElement | HeadElement $wrapElement): void;
public function appendList(ListElement $listElement): void;
}
Visitor Interface Implementation
- Create file “ElementVisitor.php”.
- Define class “ElementVisitor”.
- Implement interface “Visitor”. In the method implementation write relevant code for content processing/structuring.
<?php
// ElementVisitor.php
namespace BigBoxCodeDesignPatternVisitorUiElement;
class ElementVisitor implements Visitor {
public string $output = '';
public function appendContent(TextElement $textElement): void {
$this->output .= $textElement->getText();
}
public function appendContentWithWrapper(WrapElement | HeadElement $wrapElement): void {
$this->output .= "[" . $wrapElement->getWrapper() . "] " . $wrapElement->getText() . " [/" . $wrapElement->getWrapper() . "]";
}
public function appendList(ListElement $listElement): void {
$this->output .= '[ul]';
foreach ($listElement->getListItems() as $listItem) {
$this->output .= "[li] $listItem [/li]";
}
$this->output .= '[/ul]';
}
}
Demo
The client can decide to use a bunch of elements, and we need to generate an object of those elements. Then we can call the “appendElement” method from those objects to to generate those UI elements
<?php
// demo.php
require __DIR__ . '/../../vendor/autoload.php';
use BigBoxCodeDesignPatternVisitorUiElementElementVisitor;
use BigBoxCodeDesignPatternVisitorUiElementHeadElement;
use BigBoxCodeDesignPatternVisitorUiElementListElement;
use BigBoxCodeDesignPatternVisitorUiElementTextElement;
use BigBoxCodeDesignPatternVisitorUiElementWrapElement;
$uiElements = [
new HeadElement('My Heading'),
new TextElement('First line of text'),
new ListElement(['abc', 'def', 'ghi', 'jkl']),
new WrapElement('Content wrapped with div', 'div'),
new TextElement('Last line of text'),
];
$visitor = new ElementVisitor();
foreach ($uiElements as $element) {
$element->appendElement($visitor);
}
echo $visitor->output;
Output
Following output will be generated after processing those UI elements-
[h1] My Heading [/h1]
First line of text
[ul]
[li] abc [/li]
[li] def [/li]
[li] ghi [/li]
[li] jkl [/li]
[/ul]
[div] Content wrapped with div [/div]
Last line of text
Source Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check Visitor pattern implementation in other programming languages.