PHP types

PHP types

It would be too trivial to say that following strict types and avoiding implicit conversions reduces code magic, which leads to stability and reliability.
That's why in Spiral, Yii, Cycle and other projects where I contribute, we use strict types whenever possible.
It's kind of obvious, but... This is PHP. And that means you can't do without nuances 💩

declare(strict_types=1);

Straight from the documentation: by default, PHP will coerce values of the wrong type into the expected scalar type declaration if possible. ... It is possible to enable strict mode on a per-file basis. In strict mode, only a value corresponding exactly to the type declaration will be accepted, otherwise a TypeError will be thrown. The only exception to this rule is that an int value will pass a float type declaration.

Warning Function calls from within internal functions will not be affected by the strict_types declaration.

Note the warning. Many core functions do not follow type strictness. For example, array_map() and array_filter() will make an implicit type conversion.

php
print_r(array_map(
    fn(int $a, int $b) => $a + $b,
    [1, '10', 3],
    [4, 5, '1e2'],
));
// output:
Array (
    [0] => 5
    [1] => 15
    [2] => 103
)

But call_user_func() will complain about type mismatch.

Reflection doesn't follow strict types too. So instead of calling newInstanceArgs()/newInstance() in the Container Factory, we have

php
$instance = new $class(...$arguments);

It may be a little slower, but it's more reliable.

Now let's go to hacks.

Variable Typing

With a simple hack you can bind a type to a variable.

php
function makeInt(int &$i): void
{
    static $list = [];
    $list[] = $obj = new class() {
        public int $i;
    };
    $obj->i = &$i;
}

$int = 1;
makeInt($int);

$int = 42; // 42
$int = 'foo'; // Fatal error: Uncaught TypeError: Cannot assign string...

Don't use it because it leaks.

PHP 8.2

The null, false and true types can now be used stand-alone.
What is this for? To provide covariance. For example, when extending a method, the return value with bool can be narrowed down to true or false, and nullable (?Foo) can be narrowed to less specific null. There are a lot of such cases in libraries.

The DNF (Disjunctive Normal Form) has arrived.
Now you may meet such monster in code:

(Countable&Traversable)|array

Notes

ℹ️ Curious but nullable-sugar (?Foo) was added in PHP 7.1, before Union Types.
ℹ️ Always explicitly specify the type of nullable parameters (?Foo $foo = null) rather than relying only on the default null value (Foo $foo = null).
ℹ️ The never type was added in PHP 8.1. But you don't need it if you use RoadRunner.
ℹ️ The callable type exists only in function and method signatures. It can't be used in properties, so you can't put it into Promoted properties either. That's because in different contexts callable can be different.
So how can we get a callable and write it to a property? For example like this:

php
// A class declaration
private \Closure $callback;

public function __construct(callable $callback)
{
    $this->callback = $callback(...);
}