Assertions in PHP

Table of contents:

# What are assertions?

Assertions are a way to validate your data but not like with validators. Validators are used to validate user input e.g. from requests. Assertions are used to validate your data in the code e.g. function arguments, constructor parameters of business objects.

Assertions are great if we don't want to overuse conditional statements. If you're using assertions, you don't have to write a lot of if statements to check if the data is valid. You can just use assertions Assertion::lessOrEqualThan($value = 5, $limit = 3) and if the data is invalid, an exception will be thrown Provided "5" is not less or equal than "3"..

Sometimes you need to check if a value is of a specific type and stop the execution of your application. You probably do it with is_int() or is_null() functions, but you can also use assertions like Assert::integer($value) or Assert::null($value, 'Value is NULL').

Another time you want to check more complex things like array has only uppercase strings Assert::allUpper(['ONLY', 'UPPERCASE', 'CHARACTERS']) or if an array has only objects of the same class Assert::allIsInstanceOf($articles, User::class). It's hard to do it within a simple if statement, but you can do it with assertions.

# PHP packages to use assertions

In the PHP world we have a few packages to use assertions. All of them have basic checks for types, comparisons, strings, files, objects and arrays.

Their usage is almost the same, but they have some differences.

Basically you give them a value and an optional message that will be added to the exception if the assertion fails.

Let's go through the most popular packages and see some examples.

# webmozart/assert

The most popular package (made by Bernhard Schussek). It has over 7k stars on GitHub.

This package has type, comparison, string, file, object, array and function assertions. All of them can be used on arrays values with all prefix. For example Assert::allInteger($values). You can also use nullOr*() prefix to check if the value is null or of a specific type e.g. Assert::nullOrInteger($value).

Syntax:

use Webmozart\Assert\Assert;

// Check that the given array contains unique values.
Assert::uniqueValues($values = [1, 2, 3], $message = 'Message to show in exception');

# beberlei/assert

Made by Benjamin Eberlei (core member of Doctrine).

Beyond basic assertions, you have the option to chain assertions. If the first assertion fails, the second one won't be executed:

use Assert\Assertion;

Assert::that($value)->integer()->greaterThan(0);

A big advantage of this package is that it has Lazy Assertions. With lazy assertions, all assertions will be executed, and you will get all the failed assertions at once in one exception:

use Assert\Assertion;

$value = '';

Assert::lazy()
    ->that($value)->integer()
    ->that($value)->greaterThan(0)
    ->verifyNow();

The following 2 assertions failed:

The following 2 assertions failed: 
1) : Value "" is not an integer. 
2) : Provided "" is not greater than "0".

# Respect/Assertion

It has more than 1500 assertions. Under the hood it's based on the Respect/Validation package that is a validation engine and has plenty of interesting validation rules. Using them is possible in assertions:

use Respect\Assertion\Assert;

Assert::macAddress('00:11:22:33:44:55')

Like the previous package it allows to chain assertions:

use Respect\Assertion\Assert;

Assert::that($value)->intVal()->greaterThan(0);

You aren't limited to assertions provided by these packages. All of them are extendable, because it's possible to create custom assertions.

# Where to use assertions?

It's a good idea to use assertions in:

# Value Objects

use Assert\Assertion;

class Age
{
    private int $age;

    public function __construct(int $age)
    {
        Assertion::greaterThan($age, 0);
        Assertion::lessThan($age, 120);
        
        $this->age = $age;
    }
}

# Setters e.g. for objects created with the Builder pattern:

use Respect\Assertion\Assert;

class SearchEndpoint
{
    private string $search;

    public function setSearch(string $search): self
    {
        Assert::greaterThan(strlen($search), 3);
    
        $this->search = $search;
    
        return $this;
    }
}

# Arrays as arguments of functions:

It's helpful especially if you have an array of objects (or any type), and you want to check if all of them are of the same type. In PHP we don't have generics, so we can't do something like array<SomeClass>. But we can use assertions to check if all the objects in the array are of the same type:

use Webmozart\Assert\Assert;

Assert::allIsInstanceOf($articles, User::class);

If not, an exception Webmozart\Assert\InvalidArgumentException is thrown:

Expected an instance of App\Models\User. Got: App\Models\Article