Advantages of migrating to PHP 8
Tags: php, programming
Published: 2020-12-10 15:33:58

*New features of PHP 8 with examples of implementation and use*

> Hello!
It is a post prepared by the [Hostman](https://bit.ly/3mhvSyv) 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 
<?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 
<?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
<?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
<?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: 

```php
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
<?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 
<?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
<?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
<?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
<?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 
<?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 
<?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
<?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
<?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
<?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
<?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 
<?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
<?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
<?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"](https://en.wikipedia.org/wiki/Syntactic_sugar#:~:text=In%20computer%20science%2C%20syntactic%20sugar,style%20that%20some%20may%20prefer) — 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.