PHP 8 has a host of improvements, new functionality, and overall polish to make the web's favorite server-side language even better. We'll cover what you need to know about PHP 8. It's an exciting release, and we're not even going to be covering all of it! There's just that much going on.
First up, we'll cover the various improvements to the type system.
Union types
The biggest type system change is the introduction of union types. Union types are a virtual type that are the "union" (a logical "OR") of two other types. For example:
<?php
function mangleUsers(string|array $users): array
{
If (is_string($users)) {
$users = [$users];
}
// ...
}
Previously, if you wanted to support the "one or many" parameter style like that you couldn't type the parameter. Now, you can specify string|array
and either a string or array variable is acceptable. Integers, objects, and so on will still be rejected.
Union types work on parameters, return types, and property types, and they support any type PHP supports: primitives, objects, user-defined classes, etc. While they can be used to produce all kinds of grody, inconsistent APIs, those were already possible. At least now they can be documented. More importantly, it opens up some interesting use cases that were previously impossible to document in the type system itself.
For example, a function that requires a number but can work on both ints or floats can now say exactly that:
<?php
function doMath(int|float): int|float
{
// ...
}
It's also possible to explicitly say that a function or method may return objects of different types. For example, a function that takes a PSR-7 request object and either returns a new request or a corresponding response would look like this:
<?php
function handleRequest(RequestInterface $req): RequestInterface|ResponseInterface
{
// ...
}
In inheritance, union types follow the same covariant/contravariant rules as any other type. That is, a subclass's method parameters are allowed to accept a broader type definition than its parent (so adding a |Foo
is OK, but not removing it), while a return value is allowed to specify a narrower type definition than its parent (so removing |Foo
is OK, but not adding it).
The reflection API has also been updated to support inspecting union types on functions and properties.
More details are available in the original RFC. Thanks to Nikita Popov for this one.
Specialty unions
PHP already has a number of one-off union types defined in the language. iterable
, for instance, is effectively a union type for array|\Traversable
. Nullable types, such as ?Product
, are essentially a union type of null|Product
.
In fact, it is now possible to specify null
as a type in a union, but only in a union. So null|RequestInterface|ResponseInterface
is a legal type definition. The ?
nullable flag is now a shorthand for null|
when there's only a single other value allowed.
Another union-type-only option is false
. That is mainly to support existing PHP internal functions that, due to errors of judgement made back in the 1990s, return "a value, or false on error." Those functions can now be typed as:
<?php
function base64_decode(string $str, bool $strict = false): string|false {}
That capability is only to support legacy code that can't change its API to be less bad. Please don't use it for any new code. Like null
, false
cannot be used as a stand-alone type declaration. Also, the void
type declaration cannot be combined with anything, as it literally means "nothing at all is returned, period," so it makes little sense to combine with other types.
PHP 8 also introduces a new built-in union type. The new mixed
type is equivalent to array|bool|callable|int|float|null|object|resource|string
. That's not quite the same as omitting the type entirely. Omitting the type could mean that the developer just forgot, or that the type system is not robust enough to describe the possible allowed values. Using the mixed
type explicitly says to the engine and other developers, "I accept anything here, and I mean it." There are now extremely few cases where it's not possible to type a parameter, return, or property in PHP 8.
Thanks to Máté Kocsis and Dan Ackroid for this RFC.
The Stringable interface
PHP objects have long been able to define a way that they can be cast to a string, using the __toString() magic method. However, the string
type hint doesn't accept string-castable objects in strict mode, rendering that less useful since PHP 7.0 introduced scalar types.
PHP 8 now introduces a Stringable
interface that corresponds to an object having the __toString()
magic method. It also does so in a clever, backward-compatible way.
As of 8.0, a class may implement a Stringable
interface that defines a method public function __toString(): string
. If it doesn't, but still implements that method, the engine will add the method and return type automatically. That means any stringable object may now be type-checked, including as part of a union type, like so:
<?php
function show_message(string|Stringable $message): void
{
// ...
}
Boom! __toString
is useful again.
Because the new interface is, technically, slightly more strict than the original __toString()
method (since it enforces a string return rather than just assuming it), the Symfony team has included a polyfill for it in their symfony/polyfill-php80 package. That allows developers to start using it now to ensure their types are correct and will be forward-compatible for PHP 8 immediately.
Mind you, just because __toString()
is more usable now doesn't mean you should go overboard. Most objects should not have a __toString()
method and should have meaningful methods that return strings instead. Where __toString()
is useful is for value objects that have one and only one possible meaningful string representation, because the object is essentially just a fancy string. A good example is an IPv4Address
object, which would only make sense to cast to a string as 1.2.3.4
type strings.
Thanks to Nicolas Grekas for the Stringable RFC.
Odds and ends
Finally, there's two other small improvements to help everyday code.
First, objects now have a magic constant that specifies their class, just as class names do. $object::class
is a string containing the class name, such as App\Form\FormDef
. It's the same as get_class()
, but easier to use. Credit for this feature goes again to Nikita Popov.
Finally, a personal favorite, methods can now have a return type of static
. That's mainly useful for interfaces with chained methods. That means the following is now possible:
<?php
Interface TaskBuilder
{
public function addStep(Step $s): static;
}
class ImportantTask implements TaskBuilder
{
public function addStep(Step $s): static
{
$this->steps[] = $s;
return $this;
}
}
And now ImportantTask::addStep()
is typed to return an instance of ImportantTask
. Previously with a return type of self
it would only indicate that it's returning TaskBuilder
.
Credit for this improvement goes once again to Nikita Popov. (We'll be seeing his name a lot in this series.)
Useful links: