PHP 8 is already at the release candidate stage, version RC 3 was released on October 29, and the full release is scheduled for November 26. So it's time to take a look at the new features that await us in PHP 8. The release schedule can be viewed here . And the official guide for updating to a new version is here .
Added support for union types ( RFC )
The union type accepts values โโof various other types, and not just one.
<?php
declare(strict_types=1);
class Number {
private int|float $number;
public function setNumber(int|float $number): void {
$this->number = $number;
}
public function getNumber(): int|float {
return $this->number;
}
}
/**
* We can pass both floats or integer values
* to the number object. Try passing a string.
*/
$number = new Number();
$number->setNumber(5);
dump($number->getNumber());
$number->setNumber(11.54);
dump($number->getNumber());
exit;
Added WeakMap ( RFC )
Weak maps allow you to create relationships between objects and arbitrary values โโ(as well as SplObjectStorage
), while the objects used as keys are not protected from the garbage collector. If the collector destroys such an object, it is simply removed from the map.
This is a very useful feature. It allows us to think even less about memory leaks in our code. While this shouldn't be a problem for most PHP developers, it's worth looking into when creating long running processes, for example using ReactPHP. With WeakMaps, object references are automatically collected by the garbage collector when the object becomes unavailable.
If you do the same with an array, the object references will persist, leading to a memory leak.
<?php
declare(strict_types=1);
class FooBar {
public WeakMap $cache;
public function __construct() {
$this->cache = new WeakMap();
}
public function getSomethingWithCaching(object $obj) {
return $this->cache[$obj] ??= $this->computeSomethingExpensive($obj);
}
public function computeSomethingExpensive(object $obj) {
dump("I got called");
return rand(1, 100);
}
}
$cacheObject = new stdClass;
$obj = new FooBar;
// "I got called" only will be printed once
$obj->getSomethingWithCaching($cacheObject);
$obj->getSomethingWithCaching($cacheObject);
dump(count($obj->cache));
// When unsetting our object, the WeakMap frees up memory
unset($cacheObject);
dump(count($obj->cache));
exit;
New exception ValueError
PHP 8 introduces a new built-in exception class
ValueError
. It complements itself \Exception
. PHP throws this exception every time you pass a value of the correct type to a function, but it cannot be used in this operation. Previously, a warning was issued in such cases. Examples:
<?php
declare(strict_types=1);
/**
* We pass an array to array_rand,
* which is of the correct type. But
* array_rand expects non-empty arrays.
*
* This throws a ValueError exception.
*/
array_rand([], 0);
/**
* The depth argument for json_decode is a
* valid integer, but it must be greater than 0
*/
json_decode('{}', true, -1);
When defining functions, you can use a variadic argument
Any number of function parameters can now be replaced with a variadic argument if their types are compatible. For example, the following code is incorrect:
<?php
declare(strict_types=1);
class A {
public function method(int $many, string $parameters, $here) {
}
}
class B extends A {
public function method(...$everything) {
dd($everything);
}
}
$b = new B();
$b->method('i can be overwritten!');
exit;
Return type static ( RFC )
The static return type can now be used to determine if a method returns the class for which this method was called, even if it was inherited (late static binding).
<?php
declare(strict_types=1);
class Test {
public function doWhatever(): static {
// Do whatever.
return $this;
}
}
exit;
Object Class Name Literal ( RFC )
Now you can retrieve the class name of an object using
$object::class
. The result will be the same as with get_class($object)
.
<?php
declare(strict_types=1);
auth()->loginUsingId(1);
dump(auth()->user()::class);
// Or with a temporary variable
$user = auth()->user();
dump($user::class);
exit;
Variable Syntax Settings ( RFC )
New
and instanceof
can now be used with arbitrary expressions: new ()(...$args)
and $obj instanceof ()
.
<?php
declare(strict_types=1);
class Foo {}
class Bar {}
$class = new (collect(['Foo', 'Bar'])->random());
dd($class);
exit;
Stringable Interface ( RFC )
PHP 8 introduces a new interface
Stringable
that is added automatically as soon as a class implements a method __toString
. You don't need to explicitly implement this interface.
<?php
declare(strict_types=1);
class Foo {
public function __toString() {
return 'I am a class';
}
}
$obj = new Foo;
dump($obj instanceof Stringable);
exit;
Traits can now define abstract private methods ( RFCs )
<?php
declare(strict_types=1);
trait MyTrait {
abstract private function neededByTheTrait(): string;
public function doSomething() {
return strlen($this->neededByTheTrait());
}
}
class TraitUser {
use MyTrait;
// This is allowed:
private function neededByTheTrait(): string { }
// This is forbidden (incorrect return type)
// private function neededByTheTrait(): stdClass { }
// This is forbidden (non-static changed to static)
// private static function neededByTheTrait(): string { }
}
exit;
throw can now be used as an expression ( RFC )
Expression
throw
can now be used where only expressions are allowed: in arrow functions, coalesce operators, ternary conditional operators (ternary / elvis).
<?php
declare(strict_types=1);
$callable = fn() => throw new Exception();
$nullableValue = null;
// $value is non-nullable.
$value = $nullableValue ?? throw new \InvalidArgumentException();
exit;
Optional hanging comma ( RFC ) is now allowed in list parameters
By analogy with the hanging comma in arrays, now you can define it in the list parameters.
<?php
declare(strict_types=1);
function method_with_many_arguments(
$a,
$b,
$c,
$d,
) {
dump("this is valid syntax");
}
method_with_many_arguments(
1,
2,
3,
4,
);
exit;
Catching Exceptions Without Storing in a Variable ( RFC )
Now you can write
catch ()
to catch exceptions without storing them in a variable.
<?php
declare(strict_types=1);
$nullableValue = null;
try {
$value = $nullableValue ?? throw new \InvalidArgumentException();
} catch (\InvalidArgumentException) {
dump("Something went wrong");
}
exit;
Added support for mixed ( RFC ) type
PHP 8 introduces a new type called mixed. It can be equivalent to the types array, bool, callable, int, float, null, object, resource, string.
<?php
declare(strict_types=1);
function debug_function(mixed ...$data) {
dump($data);
}
debug_function(1, 'string', []);
exit;
Added support for attributes
There are several suggestions for implementing attributes in PHP 8:
- https://wiki.php.net/rfc/attributes_v2
- https://wiki.php.net/rfc/attribute_amendments
- https://wiki.php.net/rfc/shorter_attribute_syntax
- https://wiki.php.net/rfc/shorter_attribute_syntax_change
This is one of the biggest changes in PHP 8. It may not be so easy to figure out at first. In short, attributes allow you to add metadata to PHP functions, parameters, classes, etc. This metadata can then be retrieved programmatically. If in PHP 7 or lower you need to parse doclocks, attributes will help you access this information, deeply integrated into PHP itself.
To make it clearer, imagine that you want your users to be able to add middleware to a class or method controller by using an attribute.
<?php
declare(strict_types=1);
// First, we need to define the attribute. An Attribute itself is just a plain PHP class, that is annotated as an Attribute itself.
#[Attribute]
class ApplyMiddleware
{
public array $middleware = [];
public function __construct(...$middleware) {
$this->middleware = $middleware;
}
}
// This adds the attribute to the MyController class, with the "auth" middleware as an argument.
#[ApplyMiddleware('auth')]
class MyController
{
public function index() {}
}
// We can then retrieve all ApplyMiddleware attributes on our class using reflection
// And read the given middleware arguments.
$reflectionClass = new ReflectionClass(MyController::class);
$attributes = $reflectionClass->getAttributes(ApplyMiddleware::class);
foreach ($attributes as $attribute) {
$middlewareAttribute = $attribute->newInstance();
dump($middlewareAttribute->middleware);
}
exit;
Added support for forwarding constructor properties ( RFCs )
It is proposed to add a simple syntax to combine constructor with property definition:
<?php
declare(strict_types=1);
class User {
public function __construct(
public int $id,
public string $name,
) {}
}
$user = new User(1, 'Marcel');
dump($user->id);
dump($user->name);
exit;
Added support for the match expression ( RFC )
It is proposed to add a new expression
match
, which is similar switch
, only with safer semantics and the ability to return values.
<?php
declare(strict_types=1);
echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};
exit;
Added support for the nullsafe operator (? ->) ( RFC )
When the result of the left side of the operator is null, execution of the entire chain is stopped, and its result is set to null. Otherwise, the chain behaves like a normal operator ->
.
<?php
declare(strict_types=1);
class User {
public function getAddress() {}
}
$user = new User();
$country = $user?->getAddress()?->country?->iso_code;
dump($country);
exit;
Added support for named arguments ( RFCs )
Naming allows you to pass arguments to a function based on the name of the parameter, not on its position. That is, the values โโof the arguments become self-documenting, and the arguments no longer depend on the enumeration order, so you can arbitrarily skip the default values.
<?php
declare(strict_types=1);
array_fill(start_index: 0, num: 100, value: 50);
exit;