PHP 8.0: News in Data Types (2/4)
PHP version 8.0 has just been released. It's full of new features, like no
version before. Their introduction deserves four separate articles. In the
second part, we will take a look at data types.
Let's go back in history. The introduction of scalar type hints was a
significant breakthrough in PHP 7. It almost didn't happen. Andreu Faulds, the author of the ingenious
solution, which was entirely backwards compatible and optional thanks to
declare(strict_types=1), was harshly rejected by the community.
Fortunately, Anthony Ferrara defended
her and the proposal at the time, launched a campaign and the RFC passed very
closely. Whew. Most of the news in PHP 8 are courtesy of the legendary Nikita Popov and they all passed the voting
like a knife through butter. The world is changing for the better.
PHP 8 brings types to perfection. The vast majority of phpDoc annotations
@var will be
replaced by native notation. But most importantly, types will be checked by the
PHP engine. Only descriptions of structures such as
more complex annotations for PHPStan will remain in the comments.
Union types are an enumeration of two or more types that a variable can
private string|object $caption;
public function setCaption(string|object $caption)
$this->caption = $caption;
Certain union types have been introduced to PHP before. Nullable types, for
example. Such as
?string, which is equivalent to the union type
string|null. Question mark notation can be considered an
abbreviation. Of course, it also works in PHP 8, but you cannot combine it with
vertical bars. So instead of
?string|object you have to write
iterable was always
array|Traversable. You may be surprised that
float is also a union type, as it accepts both
int|float, but casts the value to
You cannot use pseudotypes
mixed in unions
because it would make no sense.
Nette is ready for union types. In Schema,
accepts them, and presenters also accept them. You can use them in render and
action methods, for example:
public function renderDetail(int|array $id)
On the other hand, autowiring in Nette DI rejects union types. So far, there
is no use case where it would make sense for the constructor to accept either
one or another object. Of course, if such use-case appears, it will be possible
to adjust the behaviour of the container accordingly.
Nette\Utils\Reflection throw an
exception in the case of the union type (that is in version 3.1; in the earlier
version 3.0, these methods do return to maintain null compatibility).
mixed accepts any value.
In the case of parameters and properties, it results in the same behaviour as
if we do not specify any type. So, what is its purpose? To distinguish when a
type is merely missing and when it is intentionally
In the case of function and method return values, not specifying the type
differs from using
mixed. It means the opposite of
void, as it requires the function to return something. A missing
return then produces a fatal error.
In practice, you should rarely use it, because thanks to union types, you can
specify the value precisely. It is therefore only suitable in unique
function dump(mixed $var): mixed
// print variable
mixed, you can use the new pseudotype
exclusively in union types. It arose from the need for describing the return
type of native functions, which historically return false in case of
function strpos(string $haystack, string $needle): int|false
Therefore, there is no
true type. Neither can you use
false alone, nor
static can only be used as a return type of a method.
It says that the method returns an object of the same type as the object itself
self says that it returns the class in which the method is
defined). It is excellent for describing fluent interfaces:
public function setValue($val): static
$this->value = $val;
class ItemChild extends Item
public function childMethod()
$child = new ItemChild;
This type does not exist in PHP 8 and will not be introduced in the future.
Resources are a relic from the time when PHP did not have objects. Gradually,
resources are going to be replaced by objects. Eventually, they will disappear
completely. For example, PHP 8.0 replaces the image resource with
GdImage object, and the curl resource with
It is an interface that gets automatically implemented by every object with a
public function __toString(): string
function print(Stringable|string $s)
It is possible to explicitly state
class Email implements Stringable in the class definition, but it
is not necessary.
Nette\Utils\Html also reflects this naming schema by
implementing the interface
Nette\HtmlStringable instead of the
IHtmlString. Objects of this type, for example, are not
escaped by Latte.
The Liskov Substitution Principle (LSP) states that the extension classes and
interface implementations must never require more and provide less than the
parent. That is, the child method must not require more arguments or accept a
narrower range of types for parameters than the parent, and vice versa, it must
not return a wider range of types. But it can return fewer. Why? Because
otherwise, the inheritance would break. A function would accept an object of a
specific type, but it would have no idea what parameters it can pass to its
methods and what types they would return. Any child could break it.
So in OOP, the child class can:
- accept a broader range of types in the parameters (this is called
- return a narrower range of types (covariance)
- and properties cannot change type (they are invariant)
PHP has been able to do this since version 7.4, and all newly introduced
types in PHP 8.0 also support contravariance and covariance.
In the case of
mixed, the child can narrow the return value to
any type, but not
void, as it is doesn't represent a value, but
rather its absence. The child also cannot omit a type declaration, as this also
allows for an absence of value.
public function foo(mixed $foo): mixed
class B extends A
public function foo($foo): string
Union types can also be extended in parameters and narrowed in return
public function foo(string|int $foo): string|int
class B extends A
public function foo(string|int|float $foo): string
false can be extended to
bool in the
parameter or vice versa
false in the
All offences against covariance/contravariance lead to a fatal error in
In the next parts of this series, we will show what the attributes are,
what new functions and classes have appeared in PHP and we will introduce Just
in Time Compiler.