1.1. Chain of responsibility¶
1.1.1. Intent¶
According to the Gang of Four, the Chain of Responsibility pattern is a way to “avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 223).
1.1.2. When to use it?¶
You should use the Chain of Responsibility pattern if multiple receivers are able to handle a request and if you don’t need to explicitly specify which receiver has to handle a given request (not known in advance and determined automatically). Moreover, it allows you to keep senders and receivers decoupled.
Keep in mind that the chain doesn’t ensure that a request will be handled before the end of the chain.
1.1.3. Diagram¶
Created using PhpStorm and yFiles.
1.1.4. Implementation¶
Order.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | <?php
namespace Phpatterns\Behavioral\ChainOfResponsibility;
class Order
{
const ORDER_STATUS_PENDING = 1; //default
const ORDER_STATUS_PAID = 2;
const ORDER_STATUS_PREPARED = 3;
const ORDER_STATUS_SHIPPED = 4;
const ORDER_STATUS_DELIVERED = 5;
const ORDER_STATUS_CANCELED = 6;
/** @var int */
private $uid;
/** @var int */
private $status;
/** @var string */
private $trackingNumber;
/** @var string */
private $customerFirstName;
/** @var string */
private $customerLastName;
/**
* ...
* Other useful fields: address, items, quantities, VAT, etc.
* ...
*/
/**
* @param int $uid
* @param string $firstName
* @param string $lastName
*/
public function __construct($uid, $firstName, $lastName)
{
$this->uid = $uid;
$this->customerFirstName = $firstName;
$this->customerLastName = $lastName;
$this->status = self::ORDER_STATUS_PENDING;
$this->trackingNumber = null;
}
/**
* @return int
*/
public function getUid()
{
return $this->uid;
}
/**
* @return string
*/
public function getCustomerFirstName()
{
return $this->customerFirstName;
}
/**
* @return string
*/
public function getCustomerLastName()
{
return $this->customerLastName;
}
/**
* @return int
*/
public function getStatus()
{
return $this->status;
}
/**
* @param int $status
* @return $this
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
/**
* @return string
*/
public function getTrackingNumber()
{
return $this->trackingNumber;
}
/**
* @param string $trackingNumber
* @return $this
*/
public function setTrackingNumber($trackingNumber)
{
$this->trackingNumber = $trackingNumber;
return $this;
}
}
|
AbstractOrderNotification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <?php
namespace Phpatterns\Behavioral\ChainOfResponsibility;
abstract class AbstractOrderNotification
{
/** @var int */
protected $orderStatus;
/** @var AbstractOrderNotification */
protected $followingOrderNotification;
/**
* Add another OrderNotification object (another responsibility) to the end of the chain
* @param AbstractOrderNotification $orderNotification
*/
public function appendNotification(AbstractOrderNotification $orderNotification)
{
$this->followingOrderNotification = $orderNotification;
}
/**
* @param Order $order
* @return string
*/
public function handleOrder(Order $order)
{
if ($order->getStatus() === $this->orderStatus) {
return $this->sendNotification($order);
} elseif (! is_null($this->followingOrderNotification)) {
return $this->followingOrderNotification->handleOrder($order);
}
return '';
}
/**
* @param Order $order
* @return string
*/
abstract protected function sendNotification(Order $order);
}
|
PaidOrderNotification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?php
namespace Phpatterns\Behavioral\ChainOfResponsibility\OrderNotification;
use Phpatterns\Behavioral\ChainOfResponsibility;
class PaidOrderNotification extends ChainOfResponsibility\AbstractOrderNotification
{
public function __construct()
{
$this->orderStatus = ChainOfResponsibility\Order::ORDER_STATUS_PAID;
$this->followingOrderNotification = null;
}
/**
* @param ChainOfResponsibility\Order $order
* @return string
*/
public function sendNotification(ChainOfResponsibility\Order $order)
{
return sprintf(
'Dear %s %s, thank you for your order (id: %d). We will ship it shortly!',
$order->getCustomerFirstName(),
$order->getCustomerLastName(),
$order->getUid()
);
}
}
|
ShippedOrderNotification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?php
namespace Phpatterns\Behavioral\ChainOfResponsibility\OrderNotification;
use Phpatterns\Behavioral\ChainOfResponsibility;
class ShippedOrderNotification extends ChainOfResponsibility\AbstractOrderNotification
{
public function __construct()
{
$this->orderStatus = ChainOfResponsibility\Order::ORDER_STATUS_SHIPPED;
$this->followingOrderNotification = null;
}
/**
* @param ChainOfResponsibility\Order $order
* @return string
*/
public function sendNotification(ChainOfResponsibility\Order $order)
{
return sprintf(
'Dear %s %s, your order has been shipped! Your tracking number: %s',
$order->getCustomerFirstName(),
$order->getCustomerLastName(),
$order->getTrackingNumber()
);
}
}
|
1.1.5. Tests¶
ChainOfResponsibilityTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <?php
namespace Test\Phpatterns\Behavioral\ChainOfResponsibility;
use Phpatterns\Behavioral\ChainOfResponsibility;
use Phpatterns\Behavioral\ChainOfResponsibility\OrderNotification;
class ChainOfResponsibilityTest extends \PHPUnit_Framework_TestCase
{
/** @var ChainOfResponsibility\AbstractOrderNotification */
private $chainOfResponsibility;
protected function setUp()
{
$this->chainOfResponsibility = new OrderNotification\PaidOrderNotification();
$this->chainOfResponsibility->appendNotification(new OrderNotification\ShippedOrderNotification());
}
public function testPaidOrderNotification()
{
$order = new ChainOfResponsibility\Order(12345, 'John', 'Doe');
$order->setStatus(ChainOfResponsibility\Order::ORDER_STATUS_PAID);
$this->assertSame(
'Dear John Doe, thank you for your order (id: 12345). We will ship it shortly!',
$this->chainOfResponsibility->handleOrder($order)
);
}
public function testShippedOrderNotification()
{
$order = new ChainOfResponsibility\Order(56789, 'Albert', 'Einstein');
$order
->setStatus(ChainOfResponsibility\Order::ORDER_STATUS_SHIPPED)
->setTrackingNumber('X4RT657P5');
$this->assertSame(
'Dear Albert Einstein, your order has been shipped! Your tracking number: X4RT657P5',
$this->chainOfResponsibility->handleOrder($order)
);
}
/**
* There is no responsibility set for a canceled order.
* No notification will be sent.
*/
public function testCanceledOrderNotification()
{
$order = new ChainOfResponsibility\Order(34541, 'Denzel', 'Washington');
$order->setStatus(ChainOfResponsibility\Order::ORDER_STATUS_CANCELED);
$this->assertSame('', $this->chainOfResponsibility->handleOrder($order));
}
}
|