/ #web 

PHP 8 Is here! A step in the right direction?

post photo by pexels

Introduction

PHP 8 is finally here!

PHP 8.0 is a major update of the PHP language. It contains many new features and optimizations including named arguments, union types, attributes, constructor property promotion, match expression, nullsafe operator, JIT, and improvements in the type system, error handling, and consistency.

In this article, we will review all the new features and changes, and share some thoughts about each of the changes, as well as on the roadmap that PHP seems to be carving ahead.

You can read all about these in the official release announcement as well.

New features

Named arguments

In PHP 8, when calling a function, you can omit the non-required arguments, and pass only what is desired.

Named Parameters example

function my_awesome_function(string $name, string $value = "", int $expires = 0) {
    ...
}

Let’s say now that we want to call this function, but only specify the $name and $expires attributes.

In PHP 7:

// calling the function. but we only want to specify $name and $expires
my_awesome_function('test', '', time() + 60 * 60 * 2)

In PHP 8:

// calling the function. but we only want to specify $name and $expires
my_awesome_function(name: 'test', expires: time() + 60 * 60 * 2)

My thoughts on named parameters

This is a nice and handy touch. Many languages nowadays support this kind of method calling.
However, this design can lead to functions that break the Single-responsibility_principle, by resulting in methods that “do too much” by having many parameters. So, as always, use with caution ;-)

Attributes

Attributes are the new kid in the block. It is essentially a configuration language embedded directly into code.
Attributes is a native PHP syntax that offers the ability to add structured, machine-readable metadata information on declarations in code: Classes, methods, functions, parameters, properties, etc.

Attributes Example

In PHP 7 (With PHPDocs):

class BookController
{
    /**
     * @Route("/api/books/{id}", methods={"GET"})
     */
    public function get($id) { /* ... */ }
}

In PHP 8:

class BookController
{
    #[Route("/api/books/{id}", methods: ["GET"])]
    public function get($id) { /* ... */ }
}

Constructor property promotion

The basic idea is simple: ditch all the class properties and the variable assignments, and prefix the constructor parameters with public, protected, or private. PHP will take that new syntax, and transform it to normal syntax under the hood, before actually executing the code.

Constructor properties example

In PHP 7:

class Point {
  public float $x;
  public float $y;
  public float $z;

  public function __construct(
    float $x = 0.0,
    float $y = 0.0,
    float $z = 0.0
  ) {
    $this->x = $x;
    $this->y = $y;
    $this->z = $z;
  }
}

In PHP 8:

class Point {
  public function __construct(
    public float $x = 0.0,
    public float $y = 0.0,
    public float $z = 0.0,
  ) {}
}

My thoughts on Constructor property promotion

Constructor property promotion reduces the amount of code that is required, leading to smaller and cleaner classes.
If you still want to have class properties that are not part of the constructor parameters, you can declare them in the old way, and instantiate them (or nor), inside the constructor.

Union types

Union types are a way of declaring multiple types for a property/variable. So if a function parameter can take either string or int values, you can now declare it as string|int.
This is something that you could not do in PHP 7, only using PHPDocs (so it was not part of the PHP core libraries, but existed in the PHPDocs).
Instead of PHPDoc annotations for a combination of types, you can use native union type declarations that are validated at runtime.

Union types example

In PHP 7:

class Book {
  /** @var int|float */
  private $price;

  /**
   * @param float|int $price
   */
  public function __construct($price) {
    $this->price = $price;
  }
}

new Book('test'); // OK at runtime

In PHP 8, since Union types are part of the PHP runtime library and compiler, this will throw an error:

class Book {
  public function __construct(
    private int|float $price
  ) {}
}

new Book('test'); // TypeError

My thoughts on Union types

As a SOLID Principles advocate, I am not a big fan of this change.
I can understand that it can help speed things up and adds a level of control over my code, but I believe that if your code gets often to the point that you need to use union type for a property, then there is definitely something you need to think about.

Match expression

Match expression syntax is one of the nicest features in PHP 8 that improves the switch syntax in multiple ways.

Let’s start by comparing the two. Here’s a classic switch example (example from this article):

In PHP 7 (using switch):

switch ($statusCode) {
    case 200:
    case 300:
        $message = null;
        break;
    case 400:
        $message = 'not found';
        break;
    case 500:
        $message = 'server error';
        break;
    default:
        $message = 'unknown status code';
        break;
}

In PHP 8 (using match):

$message = match ($statusCode) {
    200, 300 => null,
    400 => 'not found',
    500 => 'server error',
    default => 'unknown status code',
};

My thoughts on the match expression feature

match will do strict type checks instead of loose ones. It’s like using === instead of ==. In my opinion, this is a good chance, since it makes the code stricter and more expressive.
Also, If you forget to check for a value, and when there’s no default arm specified, PHP will throw an UnhandledMatchError exception.
Again more strictness, but it will prevent subtle bugs from going unnoticed.
To fix this, you should wrap the match expression inside a separate method, and deal with the UnhandledMatchError exception there, by returning a default value.

Nullsafe operator

Instead of null check conditions, you can now use a chain of calls with the new nullsafe operator.
When the evaluation of one element in the chain fails, the execution of the entire chain aborts, and the entire chain evaluates to null.

Example of nullsafe operator

In PHP 7:

$country =  null;

if ($book !== null) {
  $author = $book->author;

  if ($author !== null) {
    $address = $author->getAddress();
 
    if ($address !== null) {
      $country = $address->country;
    }
  }
}

In PHP 8:

$country = $book?->author?->getAddress()?->country;

My thoughts on nullsafe operator

I have mixed feelings about this.
Of course, it is a handy touch since it leads to less and definitely more readable code.
But again, by looking at the big picture, why would our code even need to check so deeply and greedily?
Should our code have Business rules that restrain such behavior? So, again a handy new feature, but use with caution…

Saner string to number comparisons

When comparing to a numeric string, PHP 8 uses a number comparison. Otherwise, it converts the number to a string and uses a string comparison.

In PHP 7:

0 == 'foobar' // true

In PHP 8:

0 == 'foobar' // false

This is definitely a step in the right direction, but only given that PHP is trying to be a stricter language.
When we say equals, we should mean equals!

My overall thoughts on PHP 8

PHP is undoubtedly trying to become a stricter, more “serious” language.
As a big fan of OOP and SOLID Principles principles, I am totally happy and on board with this direction.
Also, as you will see in the upgrade guide, PHP 8 is generally backwards-friendly, since it does not break a lot of functionality from the previous major releases. Cool!

So let’s try to use all the new features of PHP as consciously as possible, and build awesome things!

If you are content with the new PHP version, take a look at the migration guide.
What are your thoughts on PHP 8?