PHP argument validation using assert

When creating classes, we either pass arguments to the constructor function or have setter-getter functions to access class properties. These arguments may sometime have a specific format or requirements. For Example :

/**
 * $age integer
 *
 * @return  string
 */
function($age){
    return "You are $age years old";
}

Above function expects $age to be an integer. If any other type is passed, function will no work as expected.

The example was very granular. When your core classes have functions which affect important domain logics of your project, argument validation becomes an important part.

PHP comes with default function(or more specifically a language construct for PHP version 7) called assert, which basically checks if provided assertion if false. If yes then you can handle the exceptions in callback. Even if this is useful, I will strongly suggest using composer plugin webmozart/assert for this. It has very useful intuitive methods for the same.

  • Example :

Let's consider a function placeOrder() which takes array of $items having instances of Item class, a $shippingPrice and $customer having instance of Customer class.

<?php 

use Webmozart\Assert\Assert;

Class Order{

    /**
     * Array of Item instances
     * @var array
     */
    private $items;

    /**
     * Shipping Price
     * @var float
     */
    private $shippingPrice;

    /**
     * Customer Instance
     * @var Customer
     */
    private $customer;

    /**
     * Instantiate Order class
     * 
     * @param $items
     * @param $shippingPrice
     * @param $customer
     */
    function __construct($items, $shippingPrice, $customer)
    {
        Assert::isArray($items, '$items must be an array. Got: %s');
        Assert::allIsInstanceOf($items, '\Item', '$items must contain array of Item class instances. Got: %s');
        Assert::numeric($shippingPrice, '$shippingPrice must be numeric. Got: %s');
        Assert::isInstanceOf($customer, '\Customer', '$customer must be an instance of Customer class. Got: %s');

        $this->shippingPrice    = $shippingPrice;
        $this->customer         = $customer;
        $this->items            = $items;
    }

    /**
     * Places order
     * 
     * @return Order
     */
    public function placeOrder(){

        // PlaceOrder
    }
}

Now, when we instantiate the order class :

$customer = new Customer('John Doe');

$order = new Order([], 12.22, $customer);

We are passing an invalid value for $items argument. It will throw \InvalidArgumentException with error message : $items must contain array of Item class instances. Got: Array.

If you are using setter-getter functions instead of constructor parameters, you can have these assertions inside the setter functions. Above methodology is sometimes referred as defensive programming where we are making sure function will not break at the intial stages itself. The counter concept of defensive programming will be validating these arguments as and when they are actually used inside placeOrder function.

Advantages :

  1. Prevents errors caused due to unexpected type of arguments passed
  2. Easier to debug
  3. Easier to create unit tests and catch asset exceptions
  4. Easier to write readable custom error messages

Disadvantages :

If not properly thought, duplicate validations can cause redundancies. For example in above case, if instance of Item class contains a valid price is a concern of Item class. If that validation is also present in Order class as well, it created redundancies.

You can find more assertion options available here : https://github.com/webmozart/assert

 
 
By : Mihir Bhende Categories : laravel, php Tags : laravel, php, validation, arguments, assert