Kodokon kodokon.com

Современный PHP: строгие типы, enum, match

Освой сегодняшний PHP: строгие типы, стрелочные функции, оператор nullsafe, перечисления enum и выражение match.

9 мин · 3 вопросов

Открыть этот урок в Kodokon

Современный PHP делает ставку на типизацию. declare(strict_types=1), поставленный первой инструкцией файла, отключает неявные преобразования: передача строки '10' в параметр типа float вызывает TypeError вместо молчаливого преобразования. Типизируй параметры и возвращаемые значения: ошибки всплывут рано и в нужном месте, а не тремя файлами позже.

PHP
<?php

declare(strict_types=1);

function applyDiscount(
    float $price,
    float $rate = 0.1
): float {
    return round($price * (1 - $rate), 2);
}

echo applyDiscount(49.99);
echo PHP_EOL;
echo applyDiscount(100.0, 0.25);
Типизированная сигнатура: контракт функции задан явно.

Стрелочные функции fn сжимают анонимные функции из одного выражения. Главное преимущество перед классическими замыканиями: они сами захватывают переменные внешней области видимости, без конструкции use. Особенно хороши они с array_map() и array_filter().

PHP
<?php

declare(strict_types=1);

$prices = [12.5, 49.9, 8.0, 120.0];
$rate = 0.2;

$discounted = array_map(
    fn (float $p): float => round($p * (1 - $rate), 2),
    $prices
);

$affordable = array_filter(
    $discounted,
    fn (float $p): bool => $p < 50
);

print_r($affordable);
Переменная rate захватывается без конструкции use.

Оператор nullsafe ?-> прерывает вызов, как только очередное звено цепочки оказывается null, вместо того чтобы вызвать фатальную ошибку. В паре с ?? он заменяет пирамиды из if одной читаемой строкой.

PHP
<?php

declare(strict_types=1);

class Address
{
    public function __construct(
        public readonly string $city
    ) {
    }
}

class User
{
    public function __construct(
        public readonly ?Address $address = null
    ) {
    }
}

$alice = new User(new Address('Lyon'));
$bob = new User();

echo $alice->address?->city ?? 'Unknown city';
echo PHP_EOL;
echo $bob->address?->city ?? 'Unknown city';
Для Bob ошибки нет: цепочка останавливается на первом null.

Перечисления enum дают настоящий тип закрытым наборам значений (статусы, роли): создать несуществующий статус становится невозможно. А match - явное улучшение по сравнению со switch: строгое сравнение, результат возвращается как значение, и UnhandledMatchError, если ни один случай не подошёл - защита от забывчивости.

PHP
<?php

declare(strict_types=1);

enum OrderStatus: string
{
    case Pending = 'pending';
    case Shipped = 'shipped';
    case Delivered = 'delivered';

    public function label(): string
    {
        return match ($this) {
            self::Pending => 'Pending',
            self::Shipped => 'Shipped',
            self::Delivered => 'Delivered',
        };
    }
}

$status = OrderStatus::from('shipped');
echo $status->label();
Перечисление на основе строк: идеально для базы данных.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Что произойдёт при declare(strict_types=1), если передать строку '10' в параметр типа float?
    • PHP молча преобразует строку в 10.0
    • PHP выдаёт всего лишь предупреждение и продолжает
    • PHP выбрасывает TypeError
  2. Чем стрелочная функция fn() отличается от классического замыкания function()?
    • Она может содержать несколько инструкций
    • Она сама захватывает переменные внешней области видимости, без конструкции use
    • Она работает быстрее
    • Она не может объявлять типы
  3. Какое преимущество у выражения match перед switch?
    • Оно сравнивает строго (===), возвращает значение и выбрасывает ошибку, если ни один случай не подошёл
    • Оно допускает нестрогие сравнения (==) ради большей гибкости
    • Оно позволяет проваливаться из одного случая в следующий