Advantages of migrating to PHP 8

hostman_com

Hostman

Posted on December 10, 2020

Advantages of migrating to PHP 8

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
Enter fullscreen mode Exit fullscreen mode
<?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
Enter fullscreen mode Exit fullscreen mode

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) {}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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',
);
Enter fullscreen mode Exit fullscreen mode

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) {
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()); 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
hostman_com
Hostman

Posted on December 10, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related