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

PHP 7.4 — последний минор 7.x перед линией 8.0: в языке появляются типизированные свойства, arrow functions, ограниченная ковариантность/контравариантность и повседневный синтаксический сахар (??=, spread в массивах, разделители в числах). Параллельно приезжают FFI, OPcache preloading и современная пара __serialize / __unserialize. При апгрейде держите в фокусе три зоны риска: инициализация typed properties, константы алгоритмов паролей (теперь строки), сборка и расширения (SQLite/Zip больше не бандлятся, миграция на pkg-config).

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


Типизированные свойства

У свойств класса можно объявлять типы. Неинициализированное typed property нельзя читать до присваивания — будет Typed property ... must not be accessed before initialization.

final class User
{
    public int $id;
    public string $name;
    public ?string $nickname = null;
}

Из практики:

  • Если «нет значения» допустимо — используйте ?Type или значение по умолчанию.
  • Тип callable для свойств запрещён (контекстно-зависимое поведение).
  • Объектные typed properties имеют ограничения на инициализацию на уровне объявления — см. мануал.

Arrow functions

Arrow functions (fn) захватывают переменные из окружения по значению и возвращают одно выражение — удобно для коротких колбэков.

$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3]);

fn — зарезервированное слово: нельзя назвать функцию или класс fn (имена методов/констант допускаются).

Ковариантность и контравариантность

Типы возвращаемых значений могут сужаться (ковариантность), типы параметров — расширяться (контравариантность) при наследовании, с ограничениями. Полная вариантность лучше работает при autoload; в одном файле допустимы только нециклические ссылки на классы.

Оператор присваивания с null coalescing (??=)

Присваивание выполняется, если слева не задано или null:

$config['debug'] ??= false;

Оператор spread в массивах

Распаковка итерируемых значений внутри литерала массива:

$base = [1, 2];
$all = [0, ...$base, 3]; // [0, 1, 2, 3]

Хорошо сочетается с array_merge(...$arrays): array_merge() без аргументов теперь возвращает [].

Разделитель в числовых литералах

Подчёркивания улучшают читаемость и не меняют значение:

$million = 1_000_000;
$hex = 0xFF_FF_FF;

Слабые ссылки (WeakReference)

WeakReference держит ссылку, которая не удерживает объект от сборки мусора — полезно для кэшей и графов без утечек.

Исключения из __toString()

Бросать исключения из __toString() разрешено (раньше — fatal). Часть прежних recoverable fatals стала Error — проверьте пути приведения к строке.

Сериализация: __serialize / __unserialize

Новая пара методов заменяет разрозненные приёмы и со временем вытесняет Serializable:

final class Point
{
    public function __serialize(): array
    {
        return ['x' => $this->x, 'y' => $this->y];
    }

    public function __unserialize(array $data): void
    {
        $this->x = $data['x'];
        $this->y = $data['y'];
    }
}

Типы SPL вроде ArrayObject могут писать новые сериализованные представления, читаемые на 7.4+, но не на более старых PHP.

Расширения и стандартная библиотека

  • FFI: вызов нативных библиотек из PHP (мощно; нужны модель угроз и ограничения).
  • OPcache preloading: загрузка набора файлов при старте — opcache.preload, часто opcache.preload_user.
  • mb_str_split(): как str_split(), но по кодпоинтам.
  • proc_open(): команда массивом (без shell), дескрипторы redirect / null.
  • strip_tags(): разрешённые теги массивом имён.
  • PDO: логин/пароль в DSN для большего числа драйверов; ?? в SQL экранирует литеральный ? (например JSON-операторы в PostgreSQL).
  • PCRE: у preg_replace_callback* появился аргумент flags (PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL).
  • Password: Argon2 через sodium, если сборка без libargon.

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

Безопасные дефолты с ??=

$options['timeout'] ??= 30;

Arrow function в сортировке

usort($rows, fn($a, $b) => $a['score'] <=> $b['score']);

Spread-merge динамических списков

$merged = array_merge(...$chunks);

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

Ядро

  • Доступ как к массиву у не-массивов: для null, bool, int, float, resource вида $x['k']notice.
  • get_declared_classes(): не возвращает анонимные классы, пока они не инстанциированы.
  • fn: зарезервировано для имён функций/классов.
  • <?php в конце файла без перевода строки: теперь это открывающий тег (раньше поведение зависело от short_open_tag).
  • Stream wrappers: при include/require по stream может вызываться stream_set_option(STREAM_OPTION_READ_BUFFER) — реализуйте метод или верните false, чтобы не ловить warning.
  • Сериализация: формат o удалён (актуально для искусственных строк).
  • Константы паролей: PASSWORD_*строки ('2y', 'argon2id', …), не целые — код сравнения с 1/2/3 ломается.
  • htmlentities(): notice, если кодировка сводится к поведению htmlspecialchars.
  • fread/fwrite: при ошибке false (не ''/0); возможен notice.
  • BCMath: предупреждение при «кривых» числовых строках (как раньше, интерпретируется как ноль).
  • CURL: сериализация CURLFile бросает раньше; нестандартный аргумент curl_version() — предупреждение/игнор.
  • Date: дамп DateTime* не оставляет «лишних» свойств; сравнение DateInterval предупреждает и даёт false.
  • Intl: дефолт для idn_to_ascii / idn_to_utf8INTL_IDNA_VARIANT_UTS46.
  • MySQLi: убран embedded server; свойство $mysqli->stat — используйте mysqli::stat().
  • OpenSSL: openssl_random_pseudo_bytes бросает как random_bytes при ошибках; $crypto_strong гарантированно true, если исключения не было.
  • PCRE: с PREG_UNMATCHED_AS_NULL хвостовые несовпавшие группы — null (стабильный размер $matches).
  • PDO: сериализация PDO/PDOStatementException (не PDOException).
  • Reflection: сериализация объектов reflection запрещена явно; изменились числовые значения некоторых констант модификаторов.
  • SPL / ArrayObject: изменилось поведение get_object_vars без STD_PROP_LIST; SplPriorityQueue::setExtractFlags(0) бросает сразу.
  • Tokenizer: неожиданные байты — T_BAD_CHARACTER.
  • Cookies (с 7.4.11): имена cookie не URL-декодируются.

Deprecated (исправить заранее)

Главное:

  • Вложенные тернарные операторы без явных скобок (кроме однозначной формы с вложением в средний операнд).
  • Фигурные скобки для смещения $str{0}[].
  • (real) / is_real()(float) / is_float().
  • array_key_exists() для объектовisset() / property_exists().
  • implode($parts, $glue)implode($glue, $parts).
  • ReflectionType::__toString() и Reflection*::export() → API вроде ReflectionNamedType::getName() и строковое приведение reflection-объектов.
  • allow_url_include, FILTER_SANITIZE_MAGIC_QUOTES, устаревшие LDAP paged helper’ы, ряд функций (money_format(), hebrevc(), …) — полный список: migration74 deprecated.

Прочие изменения и эксплуатация (сборка, INI, perf)

  • zend.exception_ignore_args: новый INI (по умолчанию может скрывать аргументы в stack trace — проверьте логирование ошибок).
  • opcache.preload_user: пользователь для preload не из-под root.
  • Миграция на pkg-config: многие ./configure больше не принимают =DIR; используйте PKG_CONFIG_PATH или FOO_CFLAGS/FOO_LIBS.
  • Убраны бандлы: SQLite3, Zip, onig (mbregex) — нужны системные библиотеки; hash всегда включён.
  • CSV: пустая строка $escape отключает escape-механизм PHP; поведение str_getcsv() согласовано.
  • GD: поведение imagecropauto() приведено к system libgd; изменён дефолтный режим; для imagescale() -1 по ширине/высоте сохраняет пропорции.
  • PEAR: по умолчанию не ставится (--with-pear опционально и deprecated).
  • Производительность: opcode для array_key_exists() при статическом разрешении; UTF-8 preg_match валидирует строку один раз при повторных вызовах.

Итог

Считайте PHP 7.4 мостом к 8.x: внедряйте typed properties и современную сериализацию до жёстких ошибок восьмёрки. Параллельно прогоните поиск по целочисленным PASSWORD_*, именам fn, синтаксису $var{idx} и шаблонам с ArrayObject — и прогоните интеграционные тесты для streams, OpenSSL random и сериализации PDO/Reflection.