New features of PHP 8 with examples of implementation and use
Hello!
It is a post prepared by the Hostman team. Our hosting platform deploys and scales web apps, so you can launch your apps in just a few clicks using a friendly interface.
Introduction
Announcement of a new version of PHP
PHP 8, a new major version, was released on November 26, 2020. The added features have been selected and discussed by the community for a long time. This post is a brief review of its features and improvements and a guide how to use them in practice.
Union type
Union types allow to specify a list of possible types for a value. If a value of the same type is passed, the type will be retained. If the type is not in the list and the strict types mode is not set, then an attempt will be made to convert it to a similar type (for example, float is converted to int). If the conversion is impossible, an exception of TypeError will be thrown. If the strict types mode is enabled, the type conversion will fail.
<?php
class TestUnion
{
public function func(int|string $a): void
{
var_dump($a);
}
}
$tu = new TestUnion();
$tu->func(1); // Output int(1)
$tu->func('1'); // Output string(1) "1"
$tu->func(1.5); // Output int(1)
$tu->func(new TestUnion()); // Fatal error
<?php
declare(strict_types=1);
class TestUnion
{
public function func(int|string $a): void
{
var_dump($a);
}
}
$tu = new TestUnion();
$tu->func(1); // Output int(1)
$tu->func('1'); // Output string(1) "1"
$tu->func(1.5); // Fatal error
$tu->func(new TestUnion()); // Fatal error
Mixed type
The new type mixed allows to set any available type for a function or a method of a class argument. In fact, it is a typing bypass that allows backward compatibility with legacy code.
<?php
class TestMixed
{
public function func(mixed $a): void
{
var_dump($a);
}
}
$tu = new TestMixed();
$tu->func(1); // Output int(1)
$tu->func('1'); // Output string(1) "1"
$tu->func(1.5); // Output float(1.5)
$tu->func(new TestMixed()); // Output object(TestMixed)#2 (0) {}
WeakMap class
WeakMap is a key-value store where the key is an object and the value can be of any type. If a key object is deleted by the garbage collector, this key will be automatically deleted from the store.
<?php
declare(strict_types=1);
class Test {}
$test1 = new Test();
$test2 = new Test();
$map = new \WeakMap();
$map[$test1] = 10;
$map[$test2] = 20;
var_dump($map[$test1]);
var_dump($map[$test2]);
var_dump($map);
unset($test2);
var_dump($map);
Output:
int(10)
int(20)
object(WeakMap)#3 (2) {
[0]=>
array(2) {
["key"]=> object(Test)#1 (0) { }
["value"]=> int(10)
}
[1]=>
array(2) {
["key"]=> object(Test)#2 (0) { }
["value"]=> int(20)
}
}
object(WeakMap)#3 (1) {
[0]=>
array(2) {
["key"]=> object(Test)#1 (0) { }
["value"]=> int(10)
}
}
Exception Type: ValueError
The new exception type ValueError is used when an invalid argument value is passed to a function, only if the type is correct.
<?php
declare(strict_types=1);
try {
json_decode('{}', true, -1);
} catch (\ValueError $e) {
echo $e::class; // Output ValueError
}
Variadic arguments
From now on, when inheriting in an overridden method, you can specify a variadic argument instead of the old set of arguments.
<?php
declare(strict_types=1);
class A {
public function method(int $arg1, int $arg2, int $arg3)
{
}
}
class B extends A {
public function method(...$allArgs)
{
var_dump($allArgs);
}
}
$b = new B();
// array(3) { [0]=> int(0) [1]=> int(1) [2]=> int(2}
$b->method(0, 1, 2);
Static return type
The static keyword can be specified as the return type of the method. Unlike the return type self, this method can return child objects.
It is convenient to use this feature for extending the functionality of a class by inheritance, when a method of the parent class returns its instance.
<?php
declare(strict_types=1);
class TestStatic
{
public function func(): static
{
return $this;
}
}
class A extends TestStatic
{
public function func2(): static
{
return $this;
}
}
$ts = new TestStatic();
$a = new A();
echo $ts->func()::class; // Output TestStatic
echo $a->func()::class; // Output A
echo $a->func2()::class; // Output A
Class keyword on an object instance
In the new version you can use the $object::class constant to get the class that owns an object instance. Please note that when this feature is used on an instance of the current class $this, we obtain the class that was instantiated.
<?php
declare(strict_types=1);
class TestClass
{
public function func(): static
{
return $this;
}
}
$tc = new TestClass();
echo $tc::class; // Output TestClass
echo $tc->func()::class; // Output TestClass
New / instanceof syntax changes
You can use an expression as arguments of the new and instanceof operators.
<?php
declare(strict_types=1);
class Test
{
public function func(): static
{
return $this;
}
}
$tc = new Test();
var_dump(new ($tc->func()) instanceof Test); // Output bool(true)
Stringable interface
The new Stringable interface allows to specify that a __toString method should be defined in the class to convert to a string type.
This interface is useful for determining the ability to convert a type to a string using the instanceof language structure.
<?php
declare(strict_types=1);
class Test implements \Stringable
{
public function __toString(): string
{
return 'hello';
}
}
echo (new Test()); // Output hello
Abstract private methods in traits
It is possible to specify abstract private methods in traits which can be defined in the class using the trait and called from the trait.
<?php
declare(strict_types=1);
trait TestTrait
{
abstract private function func1(): void;
public function func2(): void
{
$this->func1();
}
}
class TestClass
{
use TestTrait;
private function func1(): void
{
echo 'hello';
}
}
(new TestClass())->func2(); // Output hello
Throw as a part of expression
Throw can be a part of another expression, such as a ternary operator. This is the case when this feature is convenient to use.
<?php
declare(strict_types=1);
$connection = open_db_connection();
// Throw exception
$b = $connection ? $connection : (throw new \Exception());
The last comma in the function arguments list
Now you can leave the last comma in a function and a class method argument list. Changing is convenient when there are a large number of arguments and this list can be expanded further. For example, when using DI. But in general, the number of arguments should not exceed 2-3, and the last comma does not give any advantage.
<?php
declare(strict_types=1);
function test(
string $argument1,
string $argument2,
string $argument3,
): void {
}
test('string_1',
'string_2',
'string_3',
);
Catch without variable
This update makes it possible to omit the variable in the catch block if you are not going to use it. However, it is still necessary to specify the exception type.
<?php
declare(strict_types=1);
try {
throw new \Exception();
} catch (\Exception) {
}
Constructor property promotion
A feature to assign constructor properties to class properties was added. Thus, we omit the definition of the property in the class and its assignment in the constructor, and the constructor call argument automatically becomes a property of the class.
This is especially suited when using DI (dependency injection) — the class receives dependencies through the constructor.
<?php
declare(strict_types=1);
class Repository
{
public function save(): void
{
echo 'saved';
}
}
class Service
{
public function __construct(
private Repository $repository
) {}
public function save()
{
$this->repository->save();
}
}
(new Service(new Repository()))->save(); // Output 'saved'
Match expression
A new match expression is similar to the switch operator. However, it provides safer semantics and the ability to return values.
<?php
declare(strict_types=1);
echo match('2'){
'1' => 'hello1',
'2' => 'hello2',
'3' => 'hello3',
}; // Output hello2
Nullsafe operator ?->
The ?-> operator allows to safely access the properties and methods of an instance of a class or the result of an expression by automatically checking whether it is null or not.
If the check fails, the operator returns null and the property or method are not accessed.
The Nullsafe operator is useful for a chain method call, when one or more elements may return null instead of the expected object. If an interrupted call chain is not normal behavior, it is recommended to use exceptions.
<?php
declare(strict_types=1);
class Test
{
public function func1(): self
{
return $this;
}
public function func2(bool $a): ?self
{
return $a ? $this : null;
}
public function func3(): self
{
return $this;
}
}
// Output object(Test)#1 (0) {}
var_dump((new Test())->func1()?->func2(true)?->func3());
// Output NULL
var_dump((new Test())->func1()?->func2(false)?->func3());
Named arguments
Named arguments are used when calling functions and methods of a class. With their help you can specify which of the arguments is passed by its name. It is allowed to combine regular arguments with named arguments, but named arguments are only allowed after regular ones. The order of the named arguments doesn’t matter.
<?php
declare(strict_types=1);
function test(int $a, int $b = 0, int $c = 0): void
{
echo $a . ' ' . $b . ' ' . $c;
}
test(a: 1, b: 2, c: 3); // Output 1 2 3
test(1, b: 2, c: 3); // Output 1 2 3
test(1, c: 3, b: 2); // Output 1 2 3
test(1, c: 3); // Output 1 0 3
Conclusion
Is it worthwhile to update to the new version?
Let’s take into account staff time and costs, requirements for the environment.
We've covered the main new features of PHP 8. As you may notice, some of them only bring "syntactic sugar" — convenience and conciseness to the code. While others increase productivity and allow to use new approaches in development. Thus, transferring to the new version is fully justified, if the project uses a version not lower than 7.0.
The main changes that may be required in the project are related to the handling of the new exception types and changes in the processing of syntactic constructs.
The largest effort and platform upgrade requirements will be required when migrating from earlier PHP versions.
Top comments (2)
+ Performance.
Очень информативная статья, большое спасибо. Одна из немногих хороших статей про новые функции