[Перевод] PHP 8: код «До» и «После» (сравнение с PHP 7.4)
Осталось всего несколько месяцев до выхода 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. Поспешите проверить!