MidТеория4 min

PHP 7.4

Typed properties, arrow functions, null coalescing assignment, spread operator, preloading

PHP 7.4 — Ключевой релиз

PHP 7.4 (ноябрь 2019) — один из самых важных релизов ветки 7.x. Typed properties, arrow functions и spread operator кардинально изменили стиль написания кода.

Typed Properties

Самое значительное нововведение PHP 7.4. Свойства классов теперь могут иметь явное объявление типа.

Базовый синтаксис

<?php
declare(strict_types=1);

class User
{
    public int $id;
    public string $name;
    public string $email;
    public ?string $phone = null;  // Nullable с default
    public bool $active = true;     // Default value
    public array $roles = [];       // Default array

    public function __construct(int $id, string $name, string $email)
    {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
    }
}

$user = new User(1, 'Alice', '[email protected]');
$user->name = 'Bob';     // OK
// $user->name = 123;    // TypeError: Cannot assign int to string
// $user->name = null;   // TypeError: Cannot assign null to string (non-nullable)
$user->phone = null;     // OK — phone nullable

Поддерживаемые типы

<?php

class TypeShowcase
{
    // Скалярные
    public int $count;
    public float $price;
    public string $title;
    public bool $enabled;

    // Составные
    public array $items;
    public object $config;
    public iterable $data;

    // Классы и интерфейсы
    public \DateTime $createdAt;
    public \DateTimeInterface $updatedAt;

    // Nullable
    public ?string $description;

    // Self и parent
    public self $next;
    public ?self $previous = null;

    // НЕ поддерживаются в 7.4:
    // public void $value;       // void — только для return type
    // public callable $handler; // callable — нельзя для свойств!
}

Подвох: callable НЕ может использоваться как тип свойства. Причина: callable зависит от контекста видимости (private метод callable изнутри класса, но не снаружи). Используйте \Closure вместо callable.

Состояние свойства: initialized vs uninitialized

<?php

class Product
{
    public int $id;         // Uninitialized
    public string $name;    // Uninitialized
    public float $price = 0.0;  // Initialized (default)
    public ?string $sku = null;  // Initialized (null)
}

$product = new Product();

// Обращение к uninitialized свойству — TypeError
// echo $product->id; // Error: Typed property must not be accessed before initialization

// Проверка через isset()
var_dump(isset($product->id));    // false (uninitialized)
var_dump(isset($product->price)); // true
var_dump(isset($product->sku));   // false (null — isset возвращает false)

// Правильная инициализация
$product->id = 1;
$product->name = 'Widget';
echo $product->id; // 1 — теперь OK

Для экзамена: uninitialized — это новое состояние, отличное от null. Свойство ?string $x без присвоения — uninitialized. Свойство ?string $x = null — initialized со значением null.

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

<?php

class Animal
{
    public string $name;
    public int $legs;
}

class Dog extends Animal
{
    // Нельзя изменить тип свойства!
    // public float $legs; // Fatal error

    // Но можно добавить новые свойства
    public string $breed;
}

Arrow Functions (короткие замыкания)

<?php

// До PHP 7.4 — verbose синтаксис
$doubled = array_map(function ($n) {
    return $n * 2;
}, [1, 2, 3]);

// PHP 7.4+ — arrow function
$doubled = array_map(fn($n) => $n * 2, [1, 2, 3]);

// Arrow functions автоматически захватывают переменные из scope (by value)
$multiplier = 3;
$result = array_map(fn($n) => $n * $multiplier, [1, 2, 3]);
// [3, 6, 9]

// Без fn нужен use:
$result = array_map(function ($n) use ($multiplier) {
    return $n * $multiplier;
}, [1, 2, 3]);

Особенности arrow functions

<?php

// Только одно выражение (нет тела с фигурными скобками)
$fn = fn($x) => $x * 2; // OK
// $fn = fn($x) => { return $x * 2; }; // Syntax error!

// Поддерживают типы
$fn = fn(int $x): int => $x * 2;

// Вложенные arrow functions
$addThenMultiply = fn($a) => fn($b) => ($a + $b) * 2;
echo $addThenMultiply(3)(4); // 14

// Захват по значению, НЕ по ссылке
$counter = 0;
$increment = fn() => $counter + 1;
echo $increment(); // 1
echo $counter;     // 0 — не изменился!

// Для изменения нужна обычная функция с use (&$var)
$increment = function () use (&$counter) {
    return ++$counter;
};

Null Coalescing Assignment Operator ??=

<?php

// До PHP 7.4
$data['key'] = $data['key'] ?? 'default';
// или
if (!isset($data['key'])) {
    $data['key'] = 'default';
}

// PHP 7.4+
$data['key'] ??= 'default';

// Практические примеры
class Config
{
    private array $settings = [];

    public function get(string $key, mixed $default = null): mixed
    {
        // Кешируем default при первом обращении
        return $this->settings[$key] ??= $default;
    }
}

// Инициализация массива
$counts = [];
foreach ($items as $item) {
    $counts[$item->category] ??= 0;
    $counts[$item->category]++;
}

Spread Operator в массивах

<?php

// Распаковка массивов
$first = [1, 2, 3];
$second = [4, 5, 6];
$merged = [...$first, ...$second]; // [1, 2, 3, 4, 5, 6]

// Можно комбинировать с обычными элементами
$result = [0, ...$first, 99, ...$second];
// [0, 1, 2, 3, 99, 4, 5, 6]

// Распаковка результата функции
function getDefaults(): array
{
    return ['color' => 'blue', 'size' => 'M'];
}

// ВНИМАНИЕ: в PHP 7.4 spread работает ТОЛЬКО с числовыми ключами!
$numbers = [...[1, 2], ...[3, 4]]; // OK
// $assoc = [...['a' => 1], ...['b' => 2]]; // Fatal error в 7.4!
// Ассоциативные массивы поддерживаются с PHP 8.1

// Распаковка генераторов
function fibonacci(): Generator
{
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

// Нельзя распаковать бесконечный генератор!
// $all = [...fibonacci()]; // Fatal: memory

Числовой разделитель

<?php

// Подчёркивание для читаемости больших чисел
$million = 1_000_000;
$pi = 3.141_592_653;
$hex = 0xFF_AA_BB;
$binary = 0b1010_0011;
$octal = 0177_777;

// Не влияет на значение
var_dump(1_000_000 === 1000000); // true

// Нельзя:
// $bad = _100;        // Это имя переменной
// $bad = 100_;        // Trailing underscore
// $bad = 1__000;      // Двойной underscore
// $bad = 1_.0;        // Рядом с точкой

FFI (Foreign Function Interface)

<?php

// FFI позволяет вызывать C-функции и использовать C-структуры
// Без написания PHP-расширений!

$ffi = FFI::cdef("
    int printf(const char *format, ...);
    int rand(void);
    typedef struct {
        int x;
        int y;
    } Point;
", "libc.so.6"); // Linux

echo $ffi->rand(); // Случайное число из C stdlib

// Создание C-структуры
$point = $ffi->new("Point");
$point->x = 10;
$point->y = 20;

// FFI полезен для:
// - Вызова системных библиотек
// - Работы с бинарными данными
// - Прототипирования расширений

Безопасность: FFI по умолчанию доступен только в CLI. Для веб-среды нужна опция ffi.enable=preload и загрузка через preloading.


Preloading

<?php

// php.ini:
// opcache.preload=/path/to/preload.php
// opcache.preload_user=www-data

// preload.php — загружается один раз при старте PHP-FPM
// Все классы и функции становятся доступны во всех запросах

// preload.php
$files = glob('/var/www/app/vendor/autoload_classmap.php');
$classmap = require $files[0];

foreach ($classmap as $class => $file) {
    if (str_starts_with($class, 'App\\Domain\\')) {
        opcache_compile_file($file);
    }
}

// Преимущества:
// - Классы загружаются ОДИН раз, не на каждый запрос
// - Нет overhead от autoload
// - Меньше disk I/O

// Ограничения:
// - Изменения в preloaded файлах требуют перезапуска PHP-FPM
// - Нельзя preload файлы с side effects (echo, define и т.д.)
// - Все зависимости должны быть preloaded тоже

WeakReference

<?php

// Слабая ссылка не предотвращает сборку мусора
$object = new stdClass();
$object->data = 'important';

$weakRef = WeakReference::create($object);

echo $weakRef->get()->data; // "important"

// Удаляем оригинальный объект
unset($object);

var_dump($weakRef->get()); // null — объект собран GC

// Полезно для:
// - Кэшей (объект живёт, пока на него есть сильные ссылки)
// - Observer pattern (наблюдатель не удерживает наблюдаемый объект)
// - ORM identity map

Covariant returns и contravariant parameters

<?php

// PHP 7.4 добавил полную поддержку ковариантности/контравариантности

interface Factory
{
    public function create(ChildClass $input): ParentClass;
}

class ConcreteFactory implements Factory
{
    // Параметр расширен (contravariant): ChildClass -> ParentClass
    // Return сужен (covariant): ParentClass -> ChildClass
    public function create(ParentClass $input): ChildClass
    {
        return new ChildClass();
    }
}

Сводная таблица: PHP 7.4

Возможность Описание
Typed properties public int $id;
Arrow functions fn($x) => $x * 2
Null coalescing assignment $x ??= 'default'
Spread in arrays [...$arr1, ...$arr2] (только числовые ключи)
Numeric literal separator 1_000_000
FFI Вызов C-функций
Preloading opcache.preload
WeakReference Слабые ссылки
Covariance/Contravariance Полная поддержка

Типичные вопросы на экзамене

  1. Можно ли использовать callable как тип свойства? Нет, используйте \Closure.
  2. Что произойдёт при обращении к uninitialized typed property? TypeError (ранее Error).
  3. Arrow functions захватывают переменные по ссылке? Нет, по значению.
  4. Можно ли распаковать ассоциативный массив через ... в PHP 7.4? Нет, только с числовыми ключами.
  5. Разница между uninitialized и null? ?string $x без присвоения — uninitialized; ?string $x = null — initialized с null.