PHP 8.1 — Enums и Fibers
PHP 8.1 (ноябрь 2021) принёс два фундаментальных нововведения: перечисления (Enums) и Fibers для асинхронного программирования.
Enums (Перечисления)
Pure Enums
<?php
// Pure Enum — без значений
enum Suit
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}
// Использование
$suit = Suit::Hearts;
function playCard(Suit $suit): void
{
echo $suit->name; // "Hearts"
}
playCard(Suit::Hearts); // OK
// playCard('Hearts'); // TypeError!
// Сравнение
var_dump($suit === Suit::Hearts); // true
var_dump($suit === Suit::Clubs); // false
Backed Enums (с базовым типом)
<?php
// Backed Enum — string или int значения
enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
case Banned = 'banned';
}
// Получение значения
echo Status::Active->value; // "active"
echo Status::Active->name; // "Active"
// Создание из значения
$status = Status::from('active'); // Status::Active
$status = Status::tryFrom('unknown'); // null (без исключения)
$status = Status::from('unknown'); // ValueError!
// Все cases
$all = Status::cases(); // [Status::Active, Status::Inactive, Status::Banned]
// Int-backed enum
enum Priority: int
{
case Low = 1;
case Medium = 2;
case High = 3;
case Critical = 4;
}
Методы и интерфейсы в Enums
<?php
interface HasLabel
{
public function label(): string;
}
enum Color: string implements HasLabel
{
case Red = '#FF0000';
case Green = '#00FF00';
case Blue = '#0000FF';
// Enum может содержать методы
public function label(): string
{
return match ($this) {
self::Red => 'Red Color',
self::Green => 'Green Color',
self::Blue => 'Blue Color',
};
}
// Статические методы тоже
public static function fromHex(string $hex): self
{
return self::from($hex);
}
// Константы в Enum
public const DEFAULT = self::Blue;
}
echo Color::Red->label(); // "Red Color"
echo Color::DEFAULT->value; // "#0000FF"
Ограничения Enums
<?php
enum Status: string
{
case Active = 'active';
// ❌ НЕЛЬЗЯ: свойства (properties)
// public string $description; // Fatal error
// ❌ НЕЛЬЗЯ: конструктор
// public function __construct() {} // Fatal error
// ❌ НЕЛЬЗЯ: наследование
// Enums не могут наследовать друг друга
// и не могут быть extended
// ❌ НЕЛЬЗЯ: клонирование
// clone $status; // Error
// ✅ МОЖНО: константы, методы, implements, use (traits без свойств)
}
Подвох для экзамена: Enum НЕ может иметь свойства (properties). У Enum есть только cases, константы и методы. Это частый вопрос.
Enums в практике
<?php
// Отлично подходят для: статусы, роли, типы
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
public function canTransitionTo(self $new): bool
{
return match ($this) {
self::Pending => in_array($new, [self::Processing, self::Cancelled]),
self::Processing => in_array($new, [self::Shipped, self::Cancelled]),
self::Shipped => $new === self::Delivered,
self::Delivered, self::Cancelled => false,
};
}
public function isFinished(): bool
{
return in_array($this, [self::Delivered, self::Cancelled]);
}
}
// В type hints
function processOrder(Order $order, OrderStatus $status): void
{
if (!$order->status->canTransitionTo($status)) {
throw new DomainException("Invalid transition");
}
}
Fibers
<?php
// Fiber — лёгкий поток выполнения с ручным переключением
// Основа для асинхронных фреймворков (ReactPHP, Amp, Swoole)
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first'); // Приостановка, возврат 'first'
echo "Got: $value\n"; // Получили 'second' при resume
Fiber::suspend('third'); // Ещё одна приостановка
});
$result = $fiber->start(); // Запуск, получаем 'first'
echo "Fiber said: $result\n"; // "Fiber said: first"
$result = $fiber->resume('second'); // Возобновляем, передаём 'second'
// Вывод: "Got: second"
echo "Fiber said: $result\n"; // "Fiber said: third"
$fiber->resume(); // Fiber завершается
// Проверка состояния
var_dump($fiber->isRunning()); // false
var_dump($fiber->isTerminated()); // true
var_dump($fiber->isSuspended()); // false
Практический пример: неблокирующее чтение
<?php
// Fibers позволяют писать асинхронный код синхронным стилем
// Вместо callback hell или Promise chains
// Без fibers (callback style):
$client->get('/users', function ($response) use ($client) {
$users = json_decode($response);
$client->get("/users/{$users[0]->id}/orders", function ($orders) {
// Вложенные callbacks...
});
});
// С fibers (через async-фреймворк, использующий Fiber внутри):
$response = $client->get('/users'); // "Синхронный" вызов
$users = json_decode($response);
$orders = $client->get("/users/{$users[0]->id}/orders");
// Код выглядит синхронным, но внутри Fiber приостанавливается на I/O
Readonly Properties
<?php
class User
{
// readonly — свойство можно установить только ОДИН раз
public readonly int $id;
public readonly string $email;
public function __construct(int $id, string $email)
{
$this->id = $id; // Инициализация — OK
$this->email = $email;
}
}
$user = new User(1, '[email protected]');
echo $user->id; // 1
// $user->id = 2; // Error: Cannot modify readonly property
// С constructor promotion — ещё компактнее
class Product
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly float $price,
) {}
}
// Ограничения readonly
class Example
{
public readonly array $items;
public function __construct()
{
$this->items = [1, 2, 3];
// $this->items[] = 4; // Error! Нельзя модифицировать readonly
// $this->items[0] = 0; // Error!
// unset($this->items[0]); // Error!
}
}
Важно:
readonlyзащищает от переприсваивания, но объекты внутри readonly свойства можно мутировать (т.к. хранится ссылка).
Intersection Types
<?php
// Intersection type — объект должен реализовать ВСЕ типы
function processData(Countable&Iterator $data): void
{
echo "Count: " . count($data) . "\n";
foreach ($data as $item) {
echo $item . "\n";
}
}
// Только классы/интерфейсы (не скалярные типы!)
// int&string — НЕЛЬЗЯ
// Пример: фильтрация по нескольким интерфейсам
interface Cacheable
{
public function getCacheKey(): string;
}
interface Serializable
{
public function serialize(): string;
}
function store(Cacheable&Serializable $item): void
{
$key = $item->getCacheKey();
$data = $item->serialize();
// ...
}
Never Return Type
<?php
// never — функция НИКОГДА не возвращает значение
// Она всегда бросает исключение или завершает скрипт
function abort(string $message): never
{
throw new RuntimeException($message);
// Код после throw недостижим
}
function redirect(string $url): never
{
header("Location: $url");
exit;
}
function infiniteLoop(): never
{
while (true) {
processQueue();
}
}
// Отличие от void:
// void — возвращается, но без значения
// never — не возвращается НИКОГДА
First-class Callable Syntax
<?php
// Получение ссылки на функцию как Closure
$strlen = strlen(...); // Closure
$result = $strlen('hello'); // 5
// Методы объекта
$user = new User();
$getName = $user->getName(...); // Closure
// Статические методы
$create = User::create(...); // Closure
// Полезно для callback
$names = array_map(
strtolower(...),
['ALICE', 'BOB', 'CHARLIE']
);
// ['alice', 'bob', 'charlie']
// До PHP 8.1 использовали Closure::fromCallable()
$strlen = Closure::fromCallable('strlen');
// Или неудобные строки/массивы
$names = array_map('strtolower', ['ALICE', 'BOB']);
array_is_list()
<?php
// Проверяет, является ли массив "списком"
// Список = последовательные целые ключи от 0
var_dump(array_is_list([])); // true
var_dump(array_is_list([1, 2, 3])); // true
var_dump(array_is_list([0 => 'a', 1 => 'b'])); // true
var_dump(array_is_list([1 => 'a', 0 => 'b'])); // false (не по порядку)
var_dump(array_is_list(['a' => 1])); // false (строковой ключ)
// Практическое применение: JSON
$list = [1, 2, 3];
echo json_encode($list); // [1,2,3] — JSON array
$assoc = ['a' => 1, 'b' => 2];
echo json_encode($assoc); // {"a":1,"b":2} — JSON object
// array_is_list() помогает предсказать формат JSON
Spread operator в именованных аргументах
<?php
function createUser(string $name, string $email, string $role = 'user'): void
{
echo "$name ($email) - $role\n";
}
$args = ['name' => 'Alice', 'email' => '[email protected]', 'role' => 'admin'];
createUser(...$args); // Распаковка ассоциативного массива как named arguments
// PHP 8.1+: spread с ассоциативными массивами в литералах
$base = ['x' => 1, 'y' => 2];
$extended = [...$base, 'z' => 3]; // ['x' => 1, 'y' => 2, 'z' => 3]
Сводная таблица: PHP 8.1
| Возможность | Синтаксис |
|---|---|
| Pure Enums | enum Suit { case Hearts; } |
| Backed Enums | enum Status: string { case Active = 'active'; } |
| Fibers | new Fiber(function() { Fiber::suspend(); }) |
| Readonly properties | public readonly int $id |
| Intersection types | Countable&Iterator |
never return type |
function abort(): never |
| First-class callables | strlen(...) |
array_is_list() |
Да |
| Spread в ассоциативных массивах | [...$base, 'key' => 'val'] |
Типичные вопросы на экзамене
- Может ли Enum иметь свойства? Нет, только cases, константы и методы.
- Разница между
from()иtryFrom()?from()бросаетValueError,tryFrom()возвращаетnull. - Можно ли модифицировать содержимое readonly массива? Нет,
$this->items[] = xвызовет ошибку. - Могут ли intersection types содержать скалярные типы? Нет, только классы и интерфейсы.
- Разница между
voidиnever?voidвозвращает (без значения),neverне возвращает никогда.