PHP 8: before and after
Tags: php
Published: 2020-10-23 05:09:51

---
title: PHP 8: before and after
published: true
date: 2020-07-20 00:00:00 UTC
tags: php
canonical_url: https://stitcher.io/blog/php-8-before-and-after
---

It's only a few months before [PHP 8](https://stitcher.io/blog/new-in-php-8) will be released, and honestly there are so many good features. In this post I want to share the real-life impact that PHP 8 will have on my own code.

## [#](#events-subscribers-with-attributes) Events subscribers with attributes

I'm going to try not to abuse [attributes](https://stitcher.io/blog/attributes-in-php-8), but I think configuring event listeners is an example of an annotation I'll be using extensively.

You might know that I've been working on [event sourced systems](https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems) lately, and I can tell you: there's lots of event configuration to do. Take this simple projector, for example:

```
// Before

class CartsProjector implements Projector
{
    use ProjectsEvents;

    protected array $handlesEvents = [
        CartStartedEvent::class => 'onCartStarted',
        CartItemAddedEvent::class => 'onCartItemAdded',
        CartItemRemovedEvent::class => 'onCartItemRemoved',
        CartExpiredEvent::class => 'onCartExpired',
        CartCheckedOutEvent::class => 'onCartCheckedOut',
        CouponAddedToCartItemEvent::class => 'onCouponAddedToCartItem',
    ];

    public function onCartStarted(CartStartedEvent $event): void
    { /* … */ }

    public function onCartItemAdded(CartItemAddedEvent $event): void
    { /* … */ }

    public function onCartItemRemoved(CartItemRemovedEvent $event): void
    { /* … */ }

    public function onCartCheckedOut(CartCheckedOutEvent $event): void
    { /* … */ }

    public function onCartExpired(CartExpiredEvent $event): void
    { /* … */ }

    public function onCouponAddedToCartItem(CouponAddedToCartItemEvent $event): void
    { /* … */ }
}
```

_PHP 7.4_

There are two benefits attributes will give me:

- Event listener configuration and handlers are put together, I don't have to scroll to the top of the file to know whether a listener is configured correctly.
- I don't have to bother anymore writing and managing method names as strings: your IDE can't autocomplete them, there's no static analysis on typos and method renaming doesn't work.

Luckily, PHP 8 solves these problems:

```
class CartsProjector implements Projector
{
    use ProjectsEvents;

    @@SubscribesTo(CartStartedEvent::class)
    public function onCartStarted(CartStartedEvent $event): void
    { /* … */ }

    @@SubscribesTo(CartItemAddedEvent::class)
    public function onCartItemAdded(CartItemAddedEvent $event): void
    { /* … */ }

    @@SubscribesTo(CartItemRemovedEvent::class)
    public function onCartItemRemoved(CartItemRemovedEvent $event): void
    { /* … */ }

    @@SubscribesTo(CartCheckedOutEvent::class)
    public function onCartCheckedOut(CartCheckedOutEvent $event): void
    { /* … */ }

    @@SubscribesTo(CartExpiredEvent::class)
    public function onCartExpired(CartExpiredEvent $event): void
    { /* … */ }

    @@SubscribesTo(CouponAddedToCartItemEvent::class)
    public function onCouponAddedToCartItem(CouponAddedToCartItemEvent $event): void
    { /* … */ }
}
```

_PHP 8_

## [#](#static-instead-of-doc-blocks) Static instead of doc blocks

A smaller one, but this one will have a day-by-day impact. I often find myself still needing doc blocks because of two things: static return types en generics. The latter one can't be solved yet, but luckily the first one will in PHP 8!

When I'd write this in PHP 7.4:

```
/**
 * @return static
 */
public static function new()
{
    return new static();
}
```

_PHP 7.4_

I'll now be able to write:

```
public static function new(): static
{
    return new static();
}
```

_PHP 8_

## [#](#dto's,-property-promotion-and-named-arguments) DTO's, property promotion and named arguments

If you read my blog, you know I wrote quite a bit about the use of PHP's type system combined with [data transfer objects](https://stitcher.io/blog/laravel-beyond-crud-02-working-with-data). Naturally, I use lots of DTOs in my own code, so you can image how happy I am, being able to rewrite this:

```
class CustomerData extends DataTransferObject
{
    public string $name;

    public string $email;

    public int $age;
    
    public static function fromRequest(
        CustomerRequest $request
    ): self {
        return new self([
            'name' => $request->get('name'),
            'email' => $request->get('email'),
            'age' => $request->get('age'),
        ]);
    }
}

$data = CustomerData::fromRequest($customerRequest);
```

_PHP 7.4_

As this:

```
class CustomerData
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}

$data = new CustomerData(...$customerRequest->validated());
```

_PHP 8_

Note the use of both [constructor property promotion](https://stitcher.io/blog/constructor-promotion-in-php-8), as well as named arguments. Yes, they can be passed using named arrays and the spread operator!

## [#](#enums-and-the-match-expression) Enums and the match expression

Do you sometimes find yourself using an enum with some methods on it, that will give a different result based on the enum value?

```
/**
 * @method static self PENDING()
 * @method static self PAID()
 */
class InvoiceState extends Enum
{
    private const PENDING = 'pending';
    private const PAID = 'paid';

    public function getColour(): string
    {
        return [
            self::PENDING => 'orange',
            self::PAID => 'green',
        ][$this->value] ?? 'gray';   
    }
}
```

_PHP 7.4_

I would argue that for complexer conditions, you're better off using [the state pattern](https://stitcher.io/blog/laravel-beyond-crud-05-states), yet there are cases where an enum does suffice. This weird array syntax already is a shorthand for a more verbose conditional:

```
/**
 * @method static self PENDING()
 * @method static self PAID()
 */
class InvoiceState extends Enum
{
    private const PENDING = 'pending';
    private const PAID = 'paid';

    public function getColour(): string
    {
        if ($this->value === self::PENDING) {
            return 'orange';
        }
    
        if ($this->value === self::PAID) {
            return 'green'
        }

        return 'gray';
    }
}
```

_PHP 7.4 — alternative_

But with PHP 8, we can use the [`match` expression](https://stitcher.io/blog/php-8-match-or-switch) instead!

```
/**
 * @method static self PENDING()
 * @method static self PAID()
 */
class InvoiceState extends Enum
{
    private const PENDING = 'pending';
    private const PAID = 'paid';

    public function getColour(): string
    {
        return match ($this->value) {
            self::PENDING => 'orange',
            self::PAID => 'green',
            default => 'gray',
        };
}
```

_PHP 8_

## [#](#union-types-instead-of-doc-blocks) Union types instead of doc blocks

When I mentioned the `static` return type before, I forgot another use case where docblock type hints were required: union types. At least, they were required before, because PHP 8 supports them natively!

```
/**
 * @param string|int $input
 *
 * @return string 
 */
public function sanitize($input): string;
```

_PHP 7.4_

```
public function sanitize(string|int $input): string;
```

_PHP 8_

## [#](#throw-expressions) Throw expressions

Before PHP 8, you couldn't use `throw` in an expression, meaning you'd have to do explicit checks like so:

```
public function (array $input): void
{
    if (! isset($input['bar'])) {
        throw BarIsMissing::new();
    }
    
    $bar = $input['bar'];

    // …
}
```

_PHP 7.4_

In PHP 8, `throw` has become an expression, meaning you can use it like so:

```
public function (array $input): void
{
    $bar = $input['bar'] ?? throw BarIsMissing::new();

    // …
}
```

_PHP 8_

* * *

What's your favourite [PHP 8 feature](https://stitcher.io/blog/new-in-php-8)?