php-ddd/notification

PHP implementation of Fowler's Notification pattern

v1.0.2 2014-12-27 22:12 UTC

This package is not auto-updated.

Last update: 2024-04-27 14:12:12 UTC


README

PHP 5.3+ library to handle to replace throwing exceptions with notification in validations.

"If you're validating some data, you usually shouldn't be using exceptions to signal validation failures." -- Martin Fowler

Installation

This is installable via Composer as php-ddd/notification:

$ composer require php-ddd/notification

Usage

Suppose we have a code like this:

use Exception;

class PurchaseOrder
{
    /**
     * @var Items[]
     */
    private $items = array();
    
    /**
     * @var ShippingInformation
     */
    private $shippingInformation;
    
    /**
     * Check if we can validate the purchase order
     *
     * @throw Exception an Exception containing the reason why we can't validate the Order
     */
    public function canValidate()
    {
        if (empty($this->items)) {
            throw new Exception('There is nothing to purchase.');
        }
        
        foreach($this->items as $item) {
            if ($item->isProductExpires()) {
                throw new Exception(sprintf('The item %s is no longer available.', (string)$item));
            }
        }
        
        if (!$this->shippingInformation->isComplete()) {
            throw new Exception('You should first complete your shipping information');
        }
    }
}

We want to avoid using Exception because the goal of this method is to know if we can validate the Purchase order, not to validate it.
This kind of method should return a boolean value saying whether we can validate or not.
But returning a boolean without throwing exception means we loose information about why we can't purchase this order.
We can having both information by introducing the Notification pattern.

use PhpDDD\Notification;

class PurchaseOrder
{
    /**
     * @var Items[]
     */
    private $items = array();
    
    /**
     * @var ShippingInformation
     */
    private $shippingInformation;
    
    /**
     * Check if we can validate the purchase order
     *
     * @param Notification $notification
     *
     * @return bool whether we can validate this order or not
     */
    public function canValidate(Notification $notification)
    {
        if (empty($this->items)) {
            $notification->addError('There is nothing to purchase.');
        }
        
        foreach($this->items as $item) {
            if ($item->isProductExpires()) {
                $notification->addError(sprintf('The item %s is no longer available.', (string)$item));
            }
        }
        
        if (!$this->shippingInformation->isComplete()) {
            $notification->addError('You should first complete your shipping information');
        }
        
        return $notification->hasError();
    }
}

// Elsewhere
use Exception;

$order = new PurchaseOrder();
// ...
$notification = new Notification();
if (!$order->canValidate($notification)) {
    throw new Exception($notification->firstMessage());
}

Bear in mind that this changes the behavior of your code. You should read Martin Fowler's blog article to see how you can do this without breaking your code.

Sometimes, you don't want to know why we cannot validate. So, creating the notification object is irrelevant for your context.
In this case, simply update your code like so:

use PhpDDD\Notification;

class PurchaseOrder
{
    /**
     * @var Items[]
     */
    private $items = array();
    
    /**
     * @var ShippingInformation
     */
    private $shippingInformation;
    
    /**
     * Check if we can validate the purchase order
     *
     * @param Notification|null $notification
     *
     * @return bool whether we can validate this order or not
     */
    public function canValidate(Notification $notification = null)
    {
        if (null === $notification) {
            $notification = new Notification();
        }
        
        if (empty($this->items)) {
            $notification->addError('There is nothing to purchase.');
        }
        
        foreach($this->items as $item) {
            if ($item->isProductExpires()) {
                $notification->addError(sprintf('The item %s is no longer available.', (string)$item));
            }
        }
        
        if (!$this->shippingInformation->isComplete()) {
            $notification->addError('You should first complete your shipping information');
        }
        
        return $notification->hasError();
    }
}

// Elsewhere
$order = new PurchaseOrder();
// ...
if (!$order->canValidate()) {
    return;
}