PHP Chain of Responsibility Design Pattern Behavioral Pattern

PHP Chain of Responsibility Pattern

A short screencast: the Chain of Responsibility pattern — pass a request along a chain of handlers until one of them handles it. Let us approve a request through a team lead and a manager.

Chain Of Responsibility Pattern — open the lesson page

Overview

The Chain of Responsibility passes a request along a chain of handlers until one of them handles it. A request amount first reaches the team lead: up to 1000 they approve it themselves, otherwise they pass it to the manager. The manager approves up to 5000, and a larger amount has no one further to pass to — null is returned. The client calls only the first handler and does not know who will answer.

UML diagram

classDiagram class Handler { <<interface>> +setNext(next: Handler): Handler +handle(amount: int): string } class AbstractHandler { -next: Handler +setNext(next: Handler): Handler +handle(amount: int): string } class TeamLead { +handle(amount: int): string } class Manager { +handle(amount: int): string } Handler <|-- AbstractHandler : implements AbstractHandler <|-- TeamLead : extends AbstractHandler <|-- Manager : extends AbstractHandler o-- Handler : next

Code

Handler.php

interface Handler
{
    public function setNext(Handler $next): Handler;

    public function handle(int $amount): ?string;
}

AbstractHandler.php

abstract class AbstractHandler implements Handler
{
    private ?Handler $next = null;

    public function setNext(Handler $next): Handler
    {
        $this->next = $next;

        return $next;
    }

    public function handle(int $amount): ?string
    {
        return $this->next?->handle($amount);
    }
}

TeamLead.php

class TeamLead extends AbstractHandler
{
    public function handle(int $amount): ?string
    {
        if ($amount <= 1000) {
            return 'Team lead approved '.$amount;
        }

        return parent::handle($amount);
    }
}

Manager.php

class Manager extends AbstractHandler
{
    public function handle(int $amount): ?string
    {
        if ($amount <= 5000) {
            return 'Manager approved '.$amount;
        }

        return parent::handle($amount);
    }
}

demo.php

require_once 'Handler.php';
require_once 'AbstractHandler.php';
require_once 'TeamLead.php';
require_once 'Manager.php';

$teamLead = new TeamLead;
$manager = new Manager;

$teamLead->setNext($manager);

echo $teamLead->handle(500).PHP_EOL;
echo $teamLead->handle(3000).PHP_EOL;
var_dump($teamLead->handle(99999));

Example output

Running php demo.php shows the request travelling along the chain: 500 is approved by the team lead, 3000 already by the manager, and for 99999 no handler matches, so NULL is returned:

Team lead approved 500
Manager approved 3000
NULL

Advantages

  • The sender does not know which handler will end up handling the request.
  • Handlers can be added, removed and reordered without touching the client.
  • Each handler is responsible for a single condition (single responsibility principle).
  • The chain is assembled dynamically and may differ per scenario.

When to use

  • When several candidates can handle a request and it is not known in advance which one will.
  • When handlers should be chained and pass the request on.
  • When the set and order of handlers change at runtime.
  • When responsibility must be split between separate handlers.

Usage in Laravel

In Laravel the Chain of Responsibility is HTTP middleware: Illuminate\Pipeline\Pipeline runs a request through the app/Http/Middleware/* layer, and each link either continues the chain or breaks it. The same pipeline powers the router and job processing, and the middleware stack assembled by the core kernel (Illuminate\Foundation\Http\Kernel) is exactly the chain every request travels along.