Mid📖Теория4 min

PHP 8.3

Typed class constants, #[Override], json_validate(), dynamic class constant fetch, readonly amendments

PHP 8.3 — Типизированные константы и #[Override]

PHP 8.3 (ноябрь 2023) добавил типизацию для констант классов и атрибут для безопасного переопределения методов.

Typed Class Constants

<?php

// До PHP 8.3 — константы без типа
class OldConfig
{
    const VERSION = '1.0';  // Тип не указан
    const MAX_RETRIES = 3;  // Может быть что угодно
}

// PHP 8.3+ — типизированные константы
class Config
{
    public const string VERSION = '1.0.0';
    public const int MAX_RETRIES = 3;
    public const float TAX_RATE = 0.20;
    public const bool DEBUG = false;
    public const array ALLOWED_HOSTS = ['localhost', '127.0.0.1'];
}

// В интерфейсах — контроль типа при имплементации
interface HasVersion
{
    public const string VERSION = '1.0';
}

class App implements HasVersion
{
    // Тип должен совпадать
    public const string VERSION = '2.0'; // OK
    // public const int VERSION = 2;      // Fatal error: тип не совпадает
}

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

<?php

class Base
{
    public const string TYPE = 'base';
    protected const int PRIORITY = 0;
}

class Child extends Base
{
    // Можно переопределить значение, но тип должен быть совместимым
    public const string TYPE = 'child';      // OK
    protected const int PRIORITY = 10;       // OK

    // Нельзя изменить тип
    // public const int TYPE = 42;           // Fatal error
}

// Enum constants тоже типизируемы
enum Status: string
{
    case Active = 'active';

    // Типизированные константы в Enum
    public const string DEFAULT_LABEL = 'Unknown';
    public const int TIMEOUT = 30;
}

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

<?php

class TypeShowcase
{
    // Скалярные
    public const int COUNT = 42;
    public const float PI = 3.14159;
    public const string NAME = 'app';
    public const bool ENABLED = true;

    // Составные
    public const array CONFIG = ['key' => 'value'];
    public const mixed ANYTHING = null;  // mixed допускает любое значение

    // Nullable
    public const ?string OPTIONAL = null;

    // Union types
    public const int|string ID = 42;

    // НЕ поддерживаются:
    // public const object OBJ = ???;  // Нельзя: object
    // public const void NONE = ???;   // Нельзя: void
    // public const never NO = ???;    // Нельзя: never
}

#[Override] Attribute

<?php

// Атрибут #[\Override] гарантирует, что метод действительно переопределяет родительский
class ParentClass
{
    public function processData(): void
    {
        // ...
    }
}

class ChildClass extends ParentClass
{
    #[\Override]
    public function processData(): void
    {
        parent::processData();
        // Дополнительная обработка
    }

    // Если родительский метод переименован или удалён:
    // #[\Override]
    // public function proceesData(): void {} // Fatal error: method does not override
}

Зачем нужен #[Override]

<?php

// Сценарий: библиотека обновилась, метод переименован
// Без #[Override]: молча создастся новый метод, старый не вызывается
// С #[Override]: Fatal error при загрузке класса — баг обнаружен сразу

interface EventListener
{
    public function onEvent(Event $event): void;
}

class UserListener implements EventListener
{
    #[\Override]
    public function onEvent(Event $event): void
    {
        // Гарантируем, что метод существует в интерфейсе
    }
}

// Работает с:
// - Методами родительских классов
// - Методами интерфейсов
// - Абстрактными методами
// - Методами трейтов

// НЕ работает с:
// - Конструкторами (__construct)
// - Магическими методами (__toString, __get и т.д.)

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

<?php

abstract class Repository
{
    abstract public function find(int $id): ?object;
    abstract public function save(object $entity): void;
    abstract public function delete(int $id): void;
}

class UserRepository extends Repository
{
    #[\Override]
    public function find(int $id): ?User
    {
        // Covariant return type OK
        return User::find($id);
    }

    #[\Override]
    public function save(object $entity): void
    {
        // ...
    }

    #[\Override]
    public function delete(int $id): void
    {
        // ...
    }

    // Этот метод НЕ переопределяет — без #[Override] всё тихо
    // С #[Override] — ошибка:
    // #[\Override]
    // public function softDelete(int $id): void {} // Fatal error
}

json_validate()

<?php

// Проверка JSON без декодирования — быстрее и экономнее по памяти
$json = '{"name": "Alice", "age": 30}';

// До PHP 8.3
json_decode($json);
$isValid = json_last_error() === JSON_ERROR_NONE;

// PHP 8.3+
$isValid = json_validate($json); // true

// С указанием глубины
json_validate($json, 512); // max depth = 512

// С флагами
json_validate($json, 512, JSON_INVALID_UTF8_IGNORE);

// Примеры
var_dump(json_validate(''));              // false
var_dump(json_validate('null'));          // true (null — валидный JSON)
var_dump(json_validate('"hello"'));       // true (строка — валидный JSON)
var_dump(json_validate('{invalid}'));     // false
var_dump(json_validate('{"a": 1}'));      // true
var_dump(json_validate('[1, 2, 3]'));     // true

Преимущество: json_validate() не создаёт PHP-структуру из JSON, что значительно быстрее и потребляет меньше памяти при простой валидации.


Dynamic Class Constant Fetch

<?php

class Permissions
{
    const READ = 1;
    const WRITE = 2;
    const DELETE = 4;
    const ADMIN = 7;
}

// До PHP 8.3 — constant() или Reflection
$name = 'READ';
$value = constant(Permissions::class . '::' . $name);

// PHP 8.3+ — динамический доступ
$value = Permissions::{$name}; // 1

// Практическое применение
function hasPermission(object $user, string $permission): bool
{
    $required = Permissions::{strtoupper($permission)};
    return ($user->permissions & $required) === $required;
}

// С Enum
enum Color: string
{
    case Red = '#FF0000';
    case Green = '#00FF00';
    case Blue = '#0000FF';
}

$colorName = 'Red';
$color = Color::{$colorName}; // Color::Red
echo $color->value; // "#FF0000"

Randomizer: дополнения

<?php

use Random\Randomizer;

$random = new Randomizer();

// getBytesFromString — случайные байты из заданного алфавита
$code = $random->getBytesFromString('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 6);
echo $code; // Например: "K7X2QM"

// getFloat — случайное float значение
$value = $random->getFloat(0.0, 1.0);    // от 0.0 до 1.0
$temp = $random->getFloat(-10.0, 40.0);  // температура от -10 до 40

// nextFloat — случайное float от 0 до 1 (исключая 1)
$probability = $random->nextFloat(); // [0.0, 1.0)

// Практический пример: генерация промо-кода
function generatePromoCode(int $length = 8): string
{
    $random = new Randomizer();
    return $random->getBytesFromString(
        'ABCDEFGHJKLMNPQRSTUVWXYZ23456789', // Убрали I, O, 1, 0 (путаются)
        $length
    );
}

echo generatePromoCode(); // "KX7V3QMN"

Readonly Amendments

<?php

// PHP 8.3 разрешает повторную инициализацию readonly в __clone()
readonly class Point
{
    public function __construct(
        public float $x,
        public float $y,
    ) {}

    // PHP 8.3: можно переинициализировать readonly свойства в __clone
    public function __clone(): void
    {
        // Это работает ТОЛЬКО в __clone()
    }

    public function withX(float $x): self
    {
        $clone = clone $this;
        // До PHP 8.3 — нельзя было модифицировать readonly в клоне
        // PHP 8.3+ — можно через рефлексию внутри __clone

        // Для чистого with-pattern используйте:
        return new self($x, $this->y);
    }
}

Другие изменения PHP 8.3

Улучшенная обработка DateTimeImmutable

<?php

// Новый метод createFromTimestamp
$dt = DateTimeImmutable::createFromTimestamp(1700000000);
$dt = DateTimeImmutable::createFromTimestamp(1700000000.123456); // С микросекундами

Улучшения stack trace

<?php

// PHP 8.3 редактирует чувствительные данные в stack traces
// Параметр #[\SensitiveParameter] скрывает значение в trace

function authenticate(
    string $username,
    #[\SensitiveParameter] string $password,
): bool {
    throw new RuntimeException('Auth failed');
    // В stack trace: password = Object(SensitiveParameterValue)
}

mb_str_pad()

<?php

// Многобайтовая версия str_pad — корректно работает с UTF-8
$name = 'Алиса';

echo str_pad($name, 10);        // Неправильно для UTF-8 (считает байты)
echo mb_str_pad($name, 10);     // "Алиса     " — правильно (считает символы)
echo mb_str_pad($name, 10, '.'); // "Алиса....."
echo mb_str_pad($name, 10, '.', STR_PAD_LEFT);  // ".....Алиса"
echo mb_str_pad($name, 10, '.', STR_PAD_BOTH);  // "..Алиса..."

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

Возможность Синтаксис
Typed class constants public const string VERSION = '1.0'
#[\Override] #[\Override] public function method()
json_validate() json_validate($json)
Dynamic constant fetch ClassName::{$var}
Randomizer additions getBytesFromString(), getFloat()
Readonly in __clone Переинициализация в __clone()
mb_str_pad() UTF-8 версия str_pad()

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

  1. Можно ли использовать object как тип константы? Нет, только скалярные типы, массивы, mixed и union types.
  2. С чем работает #[Override]? С методами родительских классов, интерфейсов и трейтов. Не с конструкторами.
  3. Что вернёт json_validate('null')? truenull является валидным JSON.
  4. Что такое dynamic constant fetch? ClassName::{$variable} — доступ к константе по динамическому имени.
  5. Зачем json_validate(), если есть json_decode()? Быстрее и не потребляет память на создание структуры.