An average web dev.

What's new in PHP 8

Because PHP 8 will be officially released in November 2020 I thought it will be a good idea to take a look at what’s new. Because this is a major version, there are some breaking changes that will need your attention when you will want to upgrade.

I would rather not go through all of them but analyze the most important ones and leave my thoughts on them. You can check out the full list here.

Union types

Until now, if you wanted to declare multiple value types for a method parameter or a return value of a function you were stuck using doc-block annotations. The new version introduce the ability to use one or more types for variables or methods.

class Number {
    private int|float $number;
 
    public function setNumber(int|float $number): void {
        $this->number = $number;
    }
 
    public function getNumber(): int|float {
        return $this->number;
    }
}

In my opinion this change is more of a double-edged sword. The advantage is that you can now strictly enforce multiple types rather then leaving a comment describing what was your intention(we all know that comments are easily missed). The disadvantage is that you will tend to assign multiple responsibilities for a function or a variable. There are cases when you are forced to return two types from a method, but in general it is a bad idea. Let’s see an example:

private array|bool $posts;

The problem is that you don’t know what $post === false means. It can mean that you don’t have any posts or that the database fetch errored out. Here you are inclined to not check for any errors when constructing the posts list and leave any checks for when somebody actually uses this variable.

Parameter list trailing comma

An optional trailing comma is now allowed in parameter lists. This is a big one for when the code gets reviewed because right now you will get some confusing diffs. Consider this code:

    public function __construct(
        $logger,
        $userService,
        $mailer
    ) {}

If you would add a 4th parameter to that constructor, you would get a diff like this:

     public function __construct(
         $logger,
         $userService,
-        $mailer
+        $mailer,
+        $otherService
     ) {}

If you put a comma after $mailer, it will not be included in the diff and you will be able to concentrate on what was actually changed.

    public function __construct(
        $logger,
        $userService,
        $mailer,
    ) {}

Catch exceptions without storing them in variables

There are many cases where handling an exception means that you need to exit with a message without needing to read what that exception contains because you already know what it means. You can now skip storing the exception in a variable:

try{
    // do something with the user input
}catch (InvalidInputException){
    return new InvalidInputResponse();
}

Attributes

Attributes, known in other languages as annotations or decorators, are used to add metadata to classes, methods, properties, etc. This is a big step forward because, until now, we were stuck using doc-block annotation. Who worked with Symfony framework knows very well what I am talking about.

Now, instead of this:

class PostsController{

    /**
     * @Route(
     *     "/posts",
     *     methods={"POST"},
     * )
     */
    public function create(){}
}

We can do this:

class PostsController{

    #[Route("/posts", ["methods" => ["POST"])]
    public function create(){}
}

Because attributes will be directly supported by the language, you will get rid of the problems that come with using doc-block annotations, like the lack of syntax validation, highlighting and formatting issues. There is a lot to talk about attributes but I am not going to do that now. Feel free to check out these RFC: Attributes V2, Attributes amendments and Attributes shorter syntax.

Constructor Property Promotion

In general, there is a lot o boilerplate when defining a constructor. An example would be something like this:

class PostsService{

    private PostsRepository $repository;
    private Logger $logger;

    public function __construct(PostsRepository $repository, Logger $logger)
    {
        $this->repository = $repository;
        $this->logger = $logger;
    }
}

As you can see, we’ve defined $repository and $logger in three places: constructor parameters, constructor body and PostService properties. Because this is so common, PHP 8 introduces the ability to directly promote constructor properties. The new version would look like this:

class PostsService{

    public function __construct(
        private PostsRepository $repository, 
        private Logger $logger
    ) {}
}

The match expression

The new match expression is just an enhanced version of a switch. It is easier to read and it has the ability to directly return a value. Now, we can write this:

 switch ($tag) {
    case 'new':
        $posts = $this->getNewPosts();
        break;
    case 'trending':
        $posts = $this->getTrendingPosts();
        break;
    case 'fresh':
        $posts = $this->getFreshPosts();
        break;
    default:
        throw new UndefinedTypeException();
}

Like this:

$statement = match ($tag) {
    'tag' => $this->getNewPosts(),
    'trending' => $this->getTrendingPosts(),
    'fresh' => $this->getFreshPosts(),
    default => throw new UndefinedTypeException(),
};

Nullsafe operator

The nullsafe operator is kind of a version of the null coalescing operator, but for methods. I am sure you are all familiar with the checks that you need to make when calling methods in chain. This is a silly example, but you get the idea:

$email = null;
$loggedUser = $context->getLoggedUser();
if($loggedUser !== null){
    $profile = $loggedUser->getUserProfile();
    if($profile !== null){
        $email = $profile->getEmail();
    }
}

As you can see, in order to not get an error, we need to check the variables against null for every call. Now we can write something like this:

$email = $context->getLoggedUser()?->getUserProfile()?->getEmail();

While this is a very good addition to the language, you should not abuse it. If you are inclined to put question marks everywhere, it means that maybe you need to refactor a little.

Named arguments

Name arguments are allowing for passing arguments by name rather than position. The order of arguments will not matter. This is especially helpful when calling functions or methods that have lots of nullable arguments. Let’s see a basic example for strpos:

strpos(string $haystack , mixed $needle [, int $offset = 0 ]): int

Now can be called like:

strpos(needle: 'a', haystack: 'abc');

In most cases, you will want to omit some parameters that have default values:

function test($param1 = null, $param2 = null, $param3 = null)

We can call the test function like this:

function test(param3: 5)

New string checking functions

We all know by now that checking if a string contains another string should be taken with caution. The current solution is to use strpos, but it is prone to subtle bugs. You can read more here . PHP 8 introduced some new, more robust functions for working with strings:

str_contains(string $haystack, string $needle): bool
str_starts_with(string $haystack, string $needle): bool
str_ends_with(string $haystack, string $needle): bool

As you can see, the major advantage is that these are returning a bool value. This means that you don’t need to explicitly check int|bool return types anymore.

Conclusion

PHP 8 will have some great additions to the language. Some of them will make your life a lot easier and will help you write code that is more readable. There are still lots of things that need improvement, but we’ve come a long way from “a fractal of bad design”.