In the case of Chain of responsibility pattern, the request is passed to a chain of objects where it either gets processed by one or some or all of them. In general, the order of the chain is not important but sometimes it can be based on the business requirements. In example below, the order is important and all the objects in chain process the request. It authenticates the user by checking the credentials first, then check if the use is active or not then if the user has required permissions to use our service. For more information read Chain-of-responsibility pattern. Note: See more examples towards the end of the post.




Chain classes


AbstractChain


declare(strict_types=1);

abstract class AbstractChain
{
/** @var self */
private $next;

public function linkNext(self $next): self
{
$this->next = $next;

return $next;
}

public function check(User $user): bool
{
return $this->next ? $this->next->check($user) : true;
}
}

CredentialChain


declare(strict_types=1);

class CredentialChain extends AbstractChain
{
private $validCredentials = [
['username' => 'hello', 'password' => '123123'],
['username' => 'inanzzz', 'password' => '321321'],
['username' => 'inanzzz', 'password' => '123123']
];

public function check(User $user): bool
{
foreach ($this->validCredentials as $validCredential) {
if (
$user->getUsername() === $validCredential['username'] &&
$user->getPassword() === $validCredential['password']
) {
return parent::check($user);
}
}

throw new Exception('Invalid credentials.');
}
}

StatusChain


declare(strict_types=1);

class StatusChain extends AbstractChain
{
public function check(User $user): bool
{
if ($user->isActive()) {
return parent::check($user);
}

throw new Exception('Invalid status.');
}
}

RoleChain


declare(strict_types=1);

class RoleChain extends AbstractChain
{
private $validRoles = [
'STUDENT',
'LECTURER',
'ADMIN',
];

public function check(User $user): bool
{
foreach ($this->validRoles as $validRole) {
if ($user->getRole() === $validRole) {
return parent::check($user);
}
}

throw new Exception('Invalid role.');
}
}

Other classes


User


declare(strict_types=1);

class User
{
private $username;
private $password;
private $role;
private $isActive;

public function __construct(string $username, string $password, string $role, bool $isActive)
{
$this->username = $username;
$this->password = $password;
$this->role = $role;
$this->isActive = $isActive;
}

public function getUsername(): string
{
return $this->username;
}

public function getPassword(): string
{
return $this->password;
}

public function getRole(): string
{
return $this->role;
}

public function isActive(): bool
{
return $this->isActive;
}
}

Authenticate


declare(strict_types=1);

class Authenticate
{
private $chain;

public function __construct(AbstractChain $chain)
{
$this->chain = $chain;
}

public function login(User $user): bool
{
return $this->chain->check($user);
}
}

Test


declare(strict_types=1);

require_once 'AbstractChain.php';
require_once 'CredentialChain.php';
require_once 'StatusChain.php';
require_once 'RoleChain.php';
require_once 'User.php';
require_once 'Authenticate.php';

$chain = new CredentialChain();
$chain
->linkNext(new StatusChain())
->linkNext(new RoleChain());

try {
(new Authenticate($chain))
->login(
new User('inanzzz', '123123', 'ADMIN', true)
);

echo 'Success'.PHP_EOL;
} catch (Exception $e) {
echo $e->getMessage().PHP_EOL;
}

Result


# Success
new User('inanzzz', '123123', 'ADMIN', true)

# Invalid credentials.
new User('inanzzzA', '123123', 'ADMIN', true)
new User('inanzzz', '1231230', 'ADMIN', true)

# Invalid status.
new User('inanzzz', '123123', 'ADMIN', false)

# Invalid role.
new User('inanzzz', '123123', 'UUU', true)

Other examples


Single handler


In this example, only one handler processes the data.


abstract class AbstractChain
{
/** @var self */
private $next;

public function addNext(self $next): self
{
$this->next = $next;

return $next;
}

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

class BikeHandler extends AbstractChain
{
public function handle(string $type): ?string
{
echo 'BikeHandler has run'.PHP_EOL;

if ($type === 'bike') {
return $type;
}

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

class CarHandler extends AbstractChain
{
public function handle(string $type): ?string
{
echo 'CarHandler has run'.PHP_EOL;

if ($type === 'car') {
return $type;
}

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

class BusHandler extends AbstractChain
{
public function handle(string $type): ?string
{
echo 'BusHandler has run'.PHP_EOL;

if ($type === 'bus') {
return $type;
}

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

// Client
$bikeHandler = new BikeHandler();
$carHandler = new CarHandler();
$busHandler = new BusHandler();

$bikeHandler->addNext($carHandler)->addNext($busHandler);

echo $bikeHandler->handle('bike').PHP_EOL;

// Result
BikeHandler has run
bike

Some handlers


In this example, only relevant handlers process the data.


abstract class AbstractChain
{
/** @var self */
private $next;

public function addNext(self $next): self
{
$this->next = $next;

return $next;
}

public function handle(Car $car): Car
{
return $this->next ? $this->next->handle($car) : $car;
}
}

class TypeHandler extends AbstractChain
{
public function handle(Car $car): Car
{
echo 'TypeHandler has run'.PHP_EOL;

if ($car->type === '4x4') {
$car->canRace = true;

return $car;
}

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

class OriginHandler extends AbstractChain
{
public function handle(Car $car): Car
{
echo 'OriginHandler has run'.PHP_EOL;

if ($car->origin === 'Germany') {
$car->canRace = true;

return $car;
}

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

class BelongHandler extends AbstractChain
{
public function handle(Car $car): Car
{
echo 'BelongHandler has run'.PHP_EOL;

if ($car->belongsTo === 'Boss') {
$car->canRace = true;

return $car;
}

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

class Car
{
public $type;
public $origin;
public $belongsTo;

public $canRace = false;
}

// Client
$car = new Car();
$car->type = 'invalid';
$car->origin = 'Germany';
$car->belongsTo = 'Boss';

$typeHandler = new TypeHandler();
$originHandler = new OriginHandler();
$belongHandler = new BelongHandler();

$typeHandler->addNext($originHandler)->addNext($belongHandler);

$typeHandler->handle($car);

print_r($car);

// Result
TypeHandler has run
OriginHandler has run
Car Object
(
[type] => invalid
[origin] => Germany
[belongsTo] => Boss
[canRace] => 1
)

All handlers


In this example, all the handlers process the data (this is same as the main example above).


abstract class AbstractChain
{
/** @var self */
private $next;

public function addNext(self $next): self
{
$this->next = $next;

return $next;
}

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

class AddHandler extends AbstractChain
{
public function handle(int $number): int
{
echo 'AddHandler has run'.PHP_EOL;

$number = $number + 1;

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

class RemoveHandler extends AbstractChain
{
public function handle(int $number): int
{
echo 'RemoveHandler has run'.PHP_EOL;

$number = $number - 2;

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

class MultiplyHandler extends AbstractChain
{
public function handle(int $number): int
{
echo 'MultiplyHandler has run'.PHP_EOL;

$number = $number * 3;

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

// Client
$addHandler = new AddHandler();
$removeHandler = new RemoveHandler();
$multiplyHandler = new MultiplyHandler();

$addHandler->addNext($removeHandler)->addNext($multiplyHandler);

echo $addHandler->handle(3).PHP_EOL;

// Result
AddHandler has run
RemoveHandler has run
MultiplyHandler has run
6