PHP 8.1: Главные нововведения

Путь обновления с PHP 8.0.x: новые возможности языка, ужесточение рантайма и чеклисты для staging.

Содержание (Оглавление)


Если 8.0 «перезапустил платформу», то 8.1 наполняет язык смыслом в коде: Enums и readonly становятся повседневностью, Fibers дают библиотекам примитив кооперативной конкурентности, intersection и never дополняют типовую картину. Трение появится там, где рантайм стал строже: переприсваивание $GLOBALS, MySQLi по умолчанию с исключениями и длинный хвост deprecations, которые в следующих 8.x превратятся в ошибки.

Enums

Enums — это типобезопасный способ представлять “закрытый” набор значений.

  • Backed enums хранят скалярное значение (string|int), удобное для хранения/передачи по API.
  • Pure enums — только идентичность, хорошо подходят для доменных состояний и исчерпывающей обработки.
enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}

function canEdit(Status $status): bool
{
    return $status !== Status::Archived;
}

$status = Status::from('draft');  // Status::Draft
$value  = $status->value;         // 'draft'

Практические рекомендации

  • Используйте enums вместо “строковых флагов” ('draft'|'published') в сигнатурах и DTO.
  • Для БД и публичных API обычно удобнее backed enums; для внутренних workflow — pure enums.
  • Комбинируйте с match для исчерпывающей обработки:
function label(Status $status): string
{
    return match ($status) {
        Status::Draft => 'Draft',
        Status::Published => 'Published',
        Status::Archived => 'Archived',
    };
}

Readonly properties

Readonly properties можно присвоить только один раз — обычно в конструкторе. Это большой шаг для иммутабельных DTO, value objects и более безопасных доменных моделей.

final class UserProfile
{
    public function __construct(
        public readonly int $id,
        public readonly string $email,
    ) {}
}

Что меняется архитектурно

  • Подталкивает к объектам “сконструировали полностью — дальше не мутируем”.
  • Для DTO-подобных сущностей уменьшает необходимость в private + getter’ах.
  • Отлично сочетается с constructor property promotion.

First-class callable syntax

PHP 8.1 добавляет удобный синтаксис для создания closures из callables:

function normalize(string $s): string
{
    return strtolower(trim($s));
}

$fn = normalize(...);                 // Closure
$out = array_map($fn, [' A ', 'B ']); // ['a', 'b']

Это эквивалент Closure::fromCallable('normalize'), но короче и лучше читается в пайплайнах.

Fibers

Fibers — низкоуровневый примитив для кооперативной конкуррентности. Он сам по себе не делает код async, но позволяет библиотекам и фреймворкам строить async-рантаймы, планировщики и “structured concurrency”.

$fiber = new Fiber(function (): void {
    $value = Fiber::suspend('paused');
    echo "resumed with: {$value}\n";
});

echo $fiber->start() . "\n"; // paused
$fiber->resume('payload');   // resumed with: payload

Когда это важно

  • Если вы используете async-фреймворк/event loop: Fibers могут существенно упростить интеграции.
  • В классических request/response приложениях Fibers чаще остаются “инфраструктурой для библиотек”.

Intersection types

Intersection types выражают “должно удовлетворять всем этим интерфейсам/классам одновременно”.

function export(iterable&Countable $items): array
{
    if (count($items) === 0) {
        return [];
    }

    return iterator_to_array($items, preserve_keys: true);
}

Важно: intersection types нельзя комбинировать с union types.

never return type

Return type never означает, что функция никогда не возвращает управление: она всегда бросает исключение или вызывает exit.

function fail(string $message): never
{
    throw new RuntimeException($message);
}

Это улучшает статический анализ и делает control flow явным.

new in initializers

PHP 8.1 разрешает new выражения в большем числе мест: defaults параметров, static variables, инициализация констант и аргументы атрибутов.

final class Clock
{
    public function now(): DateTimeImmutable
    {
        return new DateTimeImmutable('now');
    }
}

function handler(Clock $clock = new Clock()): DateTimeImmutable
{
    return $clock->now();
}

Это убирает много “factory boilerplate” в простых случаях. (В DI-контейнере аккуратно с временем жизни сервисов.)

Синтаксические и языковые улучшения (0o, unpack, named args после unpack)

Octal integer literal prefix: 0o / 0O

$mask = 0o755;

Array unpacking with string keys

$a = [1, 'a' => 'b'];
$b = [...$a, 'c' => 'd']; // [1, 'a' => 'b', 'c' => 'd']

Named argument after argument unpacking

foo(...$args, named: $arg);

full_path for file uploads

В $_FILES теперь появляется ключ full_path (в первую очередь для загрузок webkitdirectory).

Практические рецепты

Backed enum из ненадёжного ввода (tryFrom)

$status = Status::tryFrom($row['status']) ?? Status::Draft;

Пайплайны с first-class callable

$normalize = static fn(string $s): string => strtolower(trim($s));
$mapper = $normalize(...);
$lines = array_map($mapper, $raw);

Обратно несовместимые изменения (миграционные заметки)

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

PHP core / language

  • Ограничения на запись в $GLOBALS: запись в “целый” $GLOBALS больше не поддерживается (например, array_pop($GLOBALS) — ошибка). Доступ к элементам $GLOBALS['x'] по‑прежнему работает.
  • static variables в унаследованных методах: унаследованные (не переопределенные) методы теперь разделяют static variables с родительским методом.
  • Optional перед required параметрами: optional параметры, объявленные до required, считаются required; в PHP 8.1 это может приводить к ArgumentCountError при вызове (даже с named arguments).
  • Return type compatibility с internal classes: переопределение внутренних методов без совместимого return type вызывает deprecation; для кросс-версии используйте #[ReturnTypeWillChange].
  • Новые ключевые слова:
    • readonly теперь keyword (но все еще можно использовать как имя функции).
    • never теперь reserved word (нельзя использовать для имен class/interface/trait и запрещено в namespaces).

Resource → object migration (продолжается)

Несколько расширений перевели ресурсы в объекты (проверки is_resource() нужно заменить на проверки на false или на object types):

  • FileInfo: finfo objects вместо fileinfo resources.
  • FTP: FTP\Connection objects.
  • IMAP: IMAP\Connection objects.
  • LDAP: LDAP\Connection, LDAP\Result, LDAP\ResultEntry objects.
  • PgSQL: PgSql\Connection, PgSql\Result, PgSql\Lob objects.
  • PSpell: PSpell\Dictionary и PSpell\Config objects.

PDO / MySQLi behavior changes

  • MySQLi: default error reporting mode изменился с “silent” на “exceptions”. Чтобы вернуть старое поведение: mysqli_report(MYSQLI_REPORT_OFF);
  • PDO: изменилось поведение PDO::ATTR_STRINGIFY_FETCHES для bool; драйверы SQLite/MySQL стали стабильнее возвращать ints/floats как нативные PHP-типы.

Изменения в стандартной библиотеке

  • version_compare() больше не принимает недокументированные сокращения операторов.
  • HTML escaping функции теперь по умолчанию используют ENT_QUOTES | ENT_SUBSTITUTE (кавычки экранируются; некорректный UTF-8 заменяется).

Deprecated (что лучше исправить заранее)

Deprecations в 8.1 — это “будущие ошибки”. Исправив их сейчас, вы сильно упростите переходы на PHP 8.2+.

Core deprecations

  • Передача null в non-nullable параметры встроенных функций — deprecated.
  • Неявные “потерянные” float → int конверсии — deprecated (ключи массивов, int type declarations в coercive mode, int-операторы).
  • Вызов static элемента на trait — deprecated.
  • Autovivification из false — deprecated ($x = false; $x[] = 1;).

Важные deprecations расширений

  • Date: strftime() / gmstrftime() и strptime() deprecated (предпочитайте date() / date_parse_from_format() / IntlDateFormatter).
  • Filter: FILTER_SANITIZE_STRING и FILTER_SANITIZE_STRIPPED deprecated.
  • Hash: mhash*() deprecated (используйте hash_*()).
  • PDO: PDO::FETCH_SERIALIZE deprecated.

Extensions: важные изменения

Здесь собраны “полезно знать” изменения, которые не всегда ломают код напрямую, но влияют на поведение и эксплуатацию.

  • Reflection: ReflectionProperty::setAccessible() / ReflectionMethod::setAccessible() больше не имеют эффекта (все считается доступным через Reflection).
  • Phar: SHA256 используется по умолчанию для подписей; поддержаны OpenSSL_SHA256/OpenSSL_SHA512.
  • OpenSSL: поддержан OpenSSL 3.0; доступность шифров и валидация параметров стали строже.
  • CLI: php -a без readline теперь приводит к ошибке.
  • PHPDBG: удален remote функционал.