PHP 8.0 — Новая эра
PHP 8.0 (ноябрь 2020) — мажорный релиз с JIT-компилятором, новой системой атрибутов и множеством синтаксических улучшений.
JIT Compiler (Just-In-Time)
<?php
// JIT компилирует "горячий" PHP-код в машинный код
// Настройка в php.ini:
//
// opcache.jit=1255
// opcache.jit_buffer_size=256M
//
// Формат opcache.jit = CRTO:
// C: CPU-specific optimization (0=off, 1=on)
// R: Register allocation (0=off, 1=local, 2=global)
// T: Trigger (0=script load, 1=first exec, 2=profiling, 3=hot code, 4=@jit, 5=tracing)
// O: Optimization level (0=off, 1=minimal, 2=inline, 3=optimized, 4=full, 5=full+CFG)
//
// Рекомендуемые значения:
// tracing JIT (production): opcache.jit=tracing
// function JIT: opcache.jit=function
// Отключить: opcache.jit=disable
// JIT наиболее эффективен для:
// - Математических вычислений
// - Обработки данных в циклах
// - CPU-bound задач
// JIT менее эффективен для:
// - I/O-bound приложений (web)
// - Коротких скриптов
// - Кода с частыми вызовами встроенных функций
Named Arguments
<?php
// Позиционные аргументы (старый стиль)
array_slice([1, 2, 3, 4, 5], 1, 3, true);
// Что значит true? Непонятно без документации.
// Named arguments — читаемость
array_slice([1, 2, 3, 4, 5], offset: 1, length: 3, preserve_keys: true);
// Можно пропускать необязательные параметры
function createUser(
string $name,
string $email,
string $role = 'user',
bool $active = true,
?string $avatar = null,
): User {
// ...
}
// Указываем только нужные
$user = createUser(
name: 'Alice',
email: '[email protected]',
avatar: '/img/alice.jpg', // Пропустили role и active
);
// Комбинирование позиционных и именованных
$user = createUser('Alice', '[email protected]', avatar: '/img/alice.jpg');
// Позиционные ДОЛЖНЫ идти ПЕРЕД именованными
Подвохи named arguments
<?php
// ПОДВОХ 1: Имена параметров стали частью API!
// Переименование параметра — breaking change
function divide(int $dividend, int $divisor): float
{
return $dividend / $divisor;
}
divide(dividend: 10, divisor: 3); // OK
// Если переименовать $divisor в $by — сломается вызов
// ПОДВОХ 2: Нельзя передать один аргумент и позиционно, и именованно
// divide(10, dividend: 20); // Error: Named parameter $dividend overwrites previous argument
// ПОДВОХ 3: С variadic параметрами
function test(string ...$args) {}
test(a: 'first', b: 'second');
// $args = ['a' => 'first', 'b' => 'second'] — ассоциативный массив!
// ПОДВОХ 4: Порядок именованных аргументов не важен
divide(divisor: 3, dividend: 10); // OK, результат 3.33...
Union Types
<?php
// Тип может быть ОДНИМ ИЗ перечисленных
function processInput(int|string $input): string
{
if (is_int($input)) {
return "Number: $input";
}
return "String: $input";
}
processInput(42); // OK
processInput('hello'); // OK
// processInput([]); // TypeError
// В свойствах классов
class Setting
{
public int|float $value;
public string|bool|null $option;
}
// Return type
function findUser(int $id): User|null
{
// Эквивалентно ?User, но union type более явный
return UserRepository::find($id);
}
// false как отдельный тип в union
function search(string $needle, string $haystack): string|false
{
return strpos($haystack, $needle); // Может вернуть int или false
}
Правило:
voidнельзя использовать в union types.nullможно:int|null===?int.
Match Expression
<?php
// match — замена switch с важными отличиями
$status = 'active';
// match использует СТРОГОЕ сравнение (===)
$label = match ($status) {
'active' => 'Active User',
'inactive' => 'Inactive User',
'banned' => 'Banned User',
default => 'Unknown',
};
echo $label; // "Active User"
// ПОДВОХ: match использует ===, а switch использует ==
$value = 0;
// switch (опасно!)
switch ($value) {
case 'hello': echo 'match!'; break; // Сработает! 0 == 'hello' в PHP 7
default: echo 'no match';
}
// match (безопасно)
$result = match ($value) {
'hello' => 'match!', // Не сработает: 0 !== 'hello'
0 => 'zero!', // Сработает
default => 'no match',
};
// Множественные значения
$category = match ($statusCode) {
200, 201, 204 => 'Success',
301, 302 => 'Redirect',
400, 422 => 'Client Error',
500, 503 => 'Server Error',
default => 'Unknown',
};
// match без аргумента (аналог if-elseif)
$message = match (true) {
$age < 13 => 'Child',
$age < 18 => 'Teenager',
$age < 65 => 'Adult',
default => 'Senior',
};
Отличия match от switch
| Аспект | switch |
match |
|---|---|---|
| Сравнение | Нестрогое (==) |
Строгое (===) |
| Возвращает значение | Нет | Да — это выражение |
break нужен |
Да | Нет (нет fall-through) |
default обязателен |
Нет | Нет, но без default бросает UnhandledMatchError |
| Множественные значения | Fall-through | Через запятую |
Nullsafe Operator ?->
<?php
// До PHP 8.0
$country = null;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->getCountry();
}
}
// PHP 8.0+
$country = $user?->getAddress()?->getCountry();
// Если $user === null → null (не вызывает getAddress)
// Если getAddress() === null → null (не вызывает getCountry)
// Работает с методами, свойствами, массивами
$name = $user?->profile?->name;
$first = $user?->getOrders()?->first();
// ПОДВОХ: nullsafe operator short-circuits ВСЕЙ цепочку
// Но побочные эффекты до ?-> уже выполнены
$result = getUser()?->process(); // getUser() ВЫЗЫВАЕТСЯ всегда
// Нельзя использовать для записи:
// $user?->name = 'Alice'; // Syntax error
Constructor Property Promotion
<?php
// До PHP 8.0
class Point
{
public float $x;
public float $y;
public float $z;
public function __construct(float $x, float $y, float $z)
{
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
// PHP 8.0+ — promoted properties
class Point
{
public function __construct(
public float $x,
public float $y,
public float $z,
) {
// Свойства создаются и присваиваются автоматически
}
}
// Можно смешивать promoted и обычные параметры
class User
{
public string $fullName;
public function __construct(
public readonly int $id, // promoted
public string $firstName, // promoted
public string $lastName, // promoted
string $middleName = '', // обычный параметр (не создаёт свойство!)
) {
$this->fullName = trim("$firstName $middleName $lastName");
}
}
Attributes (атрибуты)
<?php
// Атрибуты — замена doc-block аннотаций
// Структурированные метаданные, доступные через Reflection API
#[Attribute]
class Route
{
public function __construct(
public string $path,
public string $method = 'GET',
) {}
}
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Middleware
{
public function __construct(public string $name) {}
}
class UserController
{
#[Route('/users', method: 'GET')]
#[Middleware('auth')]
#[Middleware('throttle')]
public function index(): array
{
return User::all();
}
#[Route('/users/{id}', method: 'GET')]
public function show(int $id): User
{
return User::find($id);
}
}
// Чтение атрибутов через Reflection
$ref = new ReflectionMethod(UserController::class, 'index');
$attributes = $ref->getAttributes(Route::class);
foreach ($attributes as $attr) {
$route = $attr->newInstance();
echo "Path: {$route->path}, Method: {$route->method}\n";
}
Новые строковые функции
<?php
$email = '[email protected]';
// str_contains() — замена strpos() !== false
if (str_contains($email, '@')) {
echo 'Valid format';
}
// str_starts_with() — замена strncmp() === 0
if (str_starts_with($email, 'alice')) {
echo 'Starts with alice';
}
// str_ends_with() — замена substr() === '...'
if (str_ends_with($email, '.com')) {
echo 'Commercial domain';
}
// До PHP 8.0 приходилось делать:
if (strpos($email, '@') !== false) {} // Опасно! 0 == false в PHP 7
if (substr($email, 0, 5) === 'alice') {}
if (substr($email, -4) === '.com') {}
Throw Expression
<?php
// throw теперь выражение, а не statement
// Можно использовать в тернарном операторе, ??, fn()
$value = $input ?? throw new InvalidArgumentException('Input required');
$name = $user->getName() ?: throw new LogicException('Name cannot be empty');
// В arrow function
$validator = fn($value) => $value >= 0
? $value
: throw new DomainException('Negative value');
// В short ternary
$config = $options['timeout'] ?? throw new RuntimeException('Timeout not set');
WeakMap
<?php
// WeakMap — как SplObjectStorage, но не предотвращает GC
$cache = new WeakMap();
$obj1 = new stdClass();
$obj2 = new stdClass();
$cache[$obj1] = ['computed' => 'data1'];
$cache[$obj2] = ['computed' => 'data2'];
echo count($cache); // 2
unset($obj1);
echo count($cache); // 1 — данные для $obj1 автоматически удалены
// Идеально для:
// - Кэширование вычислений, привязанных к объектам
// - ORM: маппинг Entity -> метаданные
// - Декораторы: привязка дополнительных данных к объектам
Другие изменения
mixed type
<?php
// mixed = int|float|string|bool|array|object|null|callable|resource
// Эквивалент "любой тип" — но ЯВНО документирует намерение
function handleRequest(mixed $input): mixed
{
// Разработчик осознанно принимает любой тип
return $input;
}
Stringable interface
<?php
// Все классы с __toString() автоматически реализуют Stringable
class Money
{
public function __construct(
private int $amount,
private string $currency,
) {}
public function __toString(): string
{
return "{$this->amount} {$this->currency}";
}
}
function render(string|Stringable $value): string
{
return (string) $value;
}
render(new Money(100, 'USD')); // "100 USD"
render('plain text'); // "plain text"
Сводная таблица: PHP 8.0
| Возможность | Синтаксис |
|---|---|
| JIT | opcache.jit=tracing |
| Named arguments | func(name: 'value') |
| Union types | int|string |
| Match expression | match ($x) { 1 => 'one' } |
| Nullsafe operator | $obj?->method() |
| Constructor promotion | public function __construct(public int $id) |
| Attributes | #[Route('/path')] |
str_contains/starts_with/ends_with |
Да |
| Throw expression | $x ?? throw new E() |
mixed type |
Да |
| WeakMap | Да |
Stringable |
Автоматически |
Типичные вопросы на экзамене
- Какое сравнение использует
match? Строгое (===), в отличие отswitch(==). - Можно ли использовать
?->для записи в свойство? Нет, только для чтения. - Что произойдёт, если ни один case в
matchне подойдёт и нетdefault?UnhandledMatchError. - Именованные аргументы можно располагать перед позиционными? Нет, позиционные всегда первые.
- Promoted property без модификатора видимости? Ошибка. Нужен
public,protectedилиprivate.