PHP 8: “Before” and “After” code (comparison with PHP 7.4)

There are only a few months left until PHP 8 is out, and there is really a lot of good stuff in this release. Under the cut, we will tell you how these innovations have already begun to change the author’s approach to writing code.

Event subscribers with attributes

I will try not to overuse attributes, but in the case of setting up event listeners, for example, they are very useful.

Recently I have been working on systems where there was a lot of such a setting. Let’s take an example:

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

Attributes in PHP 8 have two advantages:

  • The code for setting up event listeners and handlers is in one place and I don’t have to scroll to the beginning to see if the listener is set up correctly.
  • I no longer need to worry about writing and manipulating method names as strings (when the IDE cannot autocomplete them, there is no static analysis of typos and renaming of methods does not work).
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

This is not a big change, but I see it every day. I often find that I still need doc blocks when I need to specify that a function has a static return type.

If in PHP 7.4 I needed to write:

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

PHP 7.4

Now it’s enough:

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

PHP 8

DTO, passing properties and named arguments

I’ve written quite a bit about using the PHP type system and the DTO pattern (data transfer objects). Naturally, I use DTOs a lot in my own code, so you can imagine how happy I am to now be 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

That’s much better:

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 passing constructor properties as named parameters. Yes, they can be passed using named arrays and the Spread operator.

Enumerations and match

Are you using an enum with some methods that return a result depending on the specific value from the enumeration?

/**
 * @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 say that for more complex conditions, you are better off using the State pattern, but there are cases where enumeration is sufficient. This weird array syntax is already a shorthand for a more cumbersome conditional expression:

/**
 * @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 option

But in PHP 8, we can use match 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

Joins instead of doc blocks

This works in a similar way to what was described earlier for the static return type.

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

PHP 7.4

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

PHP 8

Throwing Exceptions

Previously, you could not use throw in an expression, which meant that you had to write, for example, the following checks:

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, which means you can use it like this:

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

    // …
}

PHP 8

Nullsafe operator

If you are familiar with the null coalescing operator, you know its drawbacks: it does not work with method calls. Therefore, I often needed intermediate checks or framework functions suitable for this purpose:

$startDate = $booking->getStartDate();
$dateAsString = $startDate ? $startDate->asDateTimeString() : null;

PHP 7.4

With the introduction of the nullsafe operator, I can solve this problem much easier.

$dateAsString = $booking->getStartDate()?->asDateTimeString();

PHP 8

What innovations in PHP 8 do you consider important?


Advertising

Development servers and posting your projects. Each server is connected to a 500 Megabit channel protected from DDoS attacks, it is possible to use a high-speed local network. We offer a wide range of tariff plans, tariff change in one click. Very convenient server control panel and the ability to use the API. Hurry up to check!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *