Chain of Responsibility design pattern is used to pass request along the list of classes until the relevant one handles it. It is like saying "I processed the request and did my bit so now the next class in the list can process it and do his bit". The chain order is important - e.g. in the case of authentication we would check the user credentials first, then check if the use is active or not then if the user has required permissions to use our service. The example below does it. For more information read Chain-of-responsibility pattern.




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)