PHP 7 Expectations / Assertions

PHP 7 has many new features. One of them are Expectations maybe better known as assertions. It's a common practice to use an assertion library like beberlei/assert to ensure correct values and types on low level, for instance Value Objects or Aggregates. On the first sight, the Expectations in PHP 7 can replace the assertion libraries and you have a dependency fewer and of course no static calls. assert() is a language construct in PHP 7, so it's quite faster than (static) function calls. Sounds good, but not on the second sight.

I'm surprised that PHP 7 Expectations should only used for development. I know that assertions are not replacing input filtering and validation but is it a replacement for assertion libraries? You know, if you rely on an external dependency, you are dependent on the time of the Maintainer or contributors. Things are changed from time to time and what is if your PR is not recognized. Sure, you can write all the stuff yourself, but that's not an option and in the end it's even worse than this.

Problem Details for HTTP APIs

Take a look at a typical problem if you work with APIs and a server to server communication. You want to map the JSON data to an object. This example uses the API Problem from Apigility. The requirements are that the response object should be immutable and don't use reflection. And a fixed set of data is provided and some optional information can be available. The only solution is constructor injection with an array. That's a nice solution, because the caller have only put the JSON encoded data to the constructor. Note, we trust Apigility to provide the right data if the application/problem+json header is present. The response object can look like this:

declare(strict_types = 1);

class ApiProblem  
{
    private $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function type() : string
    {
        return $this->data['type'];
    }

    public function title() : string
    {
        return $this->data['title'];
    }

    public function status() : int
    {
        return $this->data['status'];
    }

    public function detail() : string
    {
        return $this->data['detail'];
    }

    public function additionalDetails(string $name)
    {
        return $this->data[$name] ?? null;
    }
}

Nothing special here, but strict types are used and if one the function doesn't return the correct type, for instance null instead string, an error is raised. Sure you can use checks in the functions like return $this->data['detail'] ?? ''; but is this good code? You must be able to rely on some data definitions. Here is where assertion libraries and PHP 7 Expectations comes into the game.

With beberlei/assert the constructor function would look like this:

public function __construct(array $data)  
{
    Assertion::allKeyExists($data, [ 'type',  'title', 'status', 'detail'], 'Not set');

    Assertion::string($data['type'], '"type" wrong');
    Assertion::string($data['title'], '"title" wrong');
    Assertion::integer($data['status'], '"status" wrong');
    Assertion::string($data['detail'], '"detail" wrong');

    $this->data = $data;
}

To use PHP 7 Expectations you must configure your PHP ini settings with ini_set('assert.exception', 1); ini_set('zend.assertions', -1); . The constructor function would look like this:

public function __construct(array $data)  
{
    assert(isset($data['type']) && is_string($data['type']), '"type" not set/wrong');

    assert(isset($data['title']) && is_string($data['title']), '"title" not set/wrong');

    assert(isset($data['status']) && is_int($data['status']), '"status" not set/wrong');

    assert(isset($data['detail']) && is_string($data['detail']), '"detail" not set/wrong');

    $this->data = $data;
}

With PHP 7 Expectations you can also use your own exceptions for instance assert(false, new MyDomainException('my message'));. This can be useful to catch exceptions by component, but in most cases, if something goes wrong on this level, you can only graceful give up. But it's really good for debugging or analyzing the abnormal behaviour.

On the Pro side there is no new project dependency and you can use own exceptions.

On the Contra side it's only useful for type checks. Otherwise it's too much boilerplate code and error prone for instance strlen() checks and it depends on PHP ini settings.

Conclusion

You cannot rely that the Expectations throw exceptions because it depends on PHP ini settings. That's a no go for public code, but internal projects can use it. If your internal component has to do only some simple checks, then maybe it's good to avoid any assertion library and use assert() instead. It could also be useful to write an own assertion library which fits your needs and which don't rely on not needed PHP extensions. If you use the PHP intl extension, why should you install the PHP mbstring extension too?

Don't hesitate to put a comment and share your experience with PHP 7 Expectations.

Discuss (0 Comments)