/ Gists / 16. Visitor
On gists

16. Visitor

PHP Patterns

pic.md Raw #

Visitor1.php Raw #

<?php

interface Entity
{
    public function accept(Visitor $visitor): string;
}


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);
    }
}


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);
    }
}


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);
    }
}



interface Visitor
{
    public function visitCompany(Company $company): string;

    public function visitDepartment(Department $department): string;

    public function visitEmployee(Employee $employee): string;
}


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";
    }
}


// USAGE
$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 just for a single department:\n\n";
echo $techSupport->accept($report);

/*

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 just for a single department:

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)
    
  */

Visitor2.php Raw #

<?php

interface RoleVisitor
{
    public function visitUser(User $role);

    public function visitGroup(Group $role);
}

interface Role
{
    public function accept(RoleVisitor $visitor);
}


class RecordingVisitor implements RoleVisitor
{
    /**
     * @var Role[]
     */
    private array $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;
    }
}


class User implements Role
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return sprintf('User %s', $this->name);
    }

    public function accept(RoleVisitor $visitor)
    {
        $visitor->visitUser($this);
    }
}


class Group implements Role
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return sprintf('Group: %s', $this->name);
    }

    public function accept(RoleVisitor $visitor)
    {
        $visitor->visitGroup($this);
    }
}

Visitor3.php Raw #

<?php

interface Visitable
{
    public function accept(Visitor $visitor);
}

interface Visitor
{
    public function visitUniversity(University $university): array;

    public function visitStudent(Student $student): string;
}

class University implements Visitable
{
    private $name;
    private $students;

    public function __construct(string $name, array $students)
    {
        $this->name = $name;
        $this->students = $students;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getStudents(): array
    {
        return $this->students;
    }

    public function accept(Visitor $visitor): array
    {
        return $visitor->visitUniversity($this);
    }
}


class Student implements Visitable
{
    private $name;
    private $sickLeaves = [];

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function addSickLeave(\DateTime $start, \DateTime $end) : void
    {
        $this->sickLeaves[] = new SickLeave($start, $end);
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getSickLeaves(): array
    {
        return $this->sickLeaves;
    }

    public function accept(Visitor $visitor): string
    {
        return $visitor->visitStudent($this);
    }
}


class SickLeaveReport implements Visitor
{
    public function visitUniversity(University $university): array
    {
        $msgs = [];
        $msgs[] = "Generating sick leave report for: \"{$university->getName()}\"";

        foreach ($university->getStudents() as $student) {
            $msgs[] = $this->visitStudent($student);
        }

        return $msgs;
    }

    public function visitStudent(Student $student): string
    {
        $daysMissed = 0;

        foreach ($student->getSickLeaves() as $sickLeave) {
            $daysMissed += $sickLeave->getStart()->diff($sickLeave->getEnd())->days + 1;
        }

        return "Student: {$student->getName()} missed {$daysMissed} days";
    }
}


class SickLeave
{
    private $start;
    private $end;

    public function __construct(\DateTime $start, \DateTime $end)
    {
        $this->start = $start;
        $this->end = $end;
    }

    public function getStart(): \DateTime
    {
        return $this->start;
    }

    public function getEnd(): \DateTime
    {
        return $this->end;
    }
}


// USAGE
$john = new Student("John");
$john->addSickLeave(new \DateTime("2019-10-01"), new \DateTime("2019-10-21"));
$john->addSickLeave(new \DateTime("2019-11-02"), new \DateTime("2019-11-10"));

$jan = new Student("Jan");
$jan->addSickLeave(new \DateTime("2019-11-01"), new \DateTime("2019-11-15"));

$ann = new Student("Ann");

$university = new University("Visitor University", [$john, $jan, $ann]);
$results = $university->accept(new SickLeaveReport());

function show($data)
{
    foreach ($data as $datum) {
        echo $datum.PHP_EOL;
    }
}

show($results);