[Перевод] PHP 8: код «До» и «После» (сравнение с PHP 7.4)

73dka35dcy3bu2mlhurd-f1vixk.jpeg

Осталось всего несколько месяцев до выхода PHP 8, и в этой версии действительно есть много хорошего. Под катом расскажем, как эти нововведения уже начали менять подход автора этого материала к написанию кода.

Подписчики событий с атрибутами


Я постараюсь не злоупотреблять атрибутами, но в случае с настройкой слушателей событий, например, они очень даже кстати.

В последнее время я работал над системами, где такой настройки было очень много. Возьмём пример:

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

У атрибутов в PHP 8 есть два преимущества:

  • Код для настройки слушателей событий и обработчиков собран в одном месте, и мне не нужно прокручивать файл до начала, чтобы узнать, правильно ли настроен слушатель.
  • Мне больше не нужно беспокоиться о написании и управлении именами методов в виде строк (когда IDE не может их автозаполнить, нет статического анализа опечаток и не работает переименование методов).
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 вместо doc-блоков


Это не такое важное изменение, но я сталкиваюсь с этим каждый день. Я часто обнаруживаю, что мне всё ещё нужны doc-блоки, когда нужно указать, что функция имеет возвращаемый тип static.

Если в PHP 7.4 мне нужно было писать:

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


PHP 7.4

То теперь достаточно:

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


PHP 8

DTO, передача свойств и именованных аргументов


Я довольно много писал об использовании системы типов PHP и паттерна DTO (data transfer objects). Естественно, я часто использую DTO в своём собственном коде, поэтому можете представить, насколько я счастлив, что теперь имею возможность переписать это:

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

Вот, так-то лучше:

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

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


PHP 8

Обратите внимание на использование передачи свойств конструктора как именованных параметров. Да, их можно передавать с помощью именованных массивов и оператора Spread.

Перечисления и match


Вы используете перечисление с некоторыми методами, которые возвращают результат в зависимости от конкретного значения из перечисления?

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

Я бы сказал, что для более сложных условий вам лучше использовать паттерн Состояние, но есть случаи, когда достаточно перечисления. Этот странный синтаксис массива уже является сокращением для более громоздкого условного выражения:

/**
 * @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 — альтернативный вариант

Но в PHP 8 вместо этого мы можем использовать match.

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

Объединения вместо doc-блоков


Это работает аналогично тому, что было описано ранее для возвращаемого типа static.

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


PHP 7.4

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


PHP 8

Генерация исключений


Раньше вы не могли использовать throw в выражении, а это означало, что вам приходилось писать, например, вот такие проверки:

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

    // …
}


PHP 7.4

В PHP 8 throw стало выражением, что означает, что вы можете использовать его вот так:

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

    // …
}


PHP 8

Оператор nullsafe


Если вы знакомы с оператором null coalescing (коалесцирующий), вам известны его недостатки: он не работает с вызовами методов. Поэтому мне часто были нужны промежуточные проверки или подходящие для этой цели функции фреймворков:

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


PHP 7.4

С появлением оператора nullsafe я могу решить эту задачу гораздо проще.

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


PHP 8

А какие нововведения в PHP 8 считаете важными вы?


На правах рекламы


Серверы для разработки и размещения ваших проектов. Каждый сервер подключён к защищённому от DDoS-атак каналу в 500 Мегабит, есть возможность использовать высокоскоросную локальную сеть. Мы предлагаем большой выбор тарифных планов, смена тарифа в один клик. Очень удобная панель управления серверами и возможность использовать API. Поспешите проверить!

8p3vz47nluspfyc0axlkx88gdua.png

© Habrahabr.ru