MidТеория5 min

Встроенные атрибуты

#[Override], #[Deprecated], #[SensitiveParameter], #[NoDiscard], #[AllowDynamicProperties]

Встроенные атрибуты PHP

PHP включает несколько атрибутов из коробки. Они обрабатываются самим движком и не требуют чтения через Reflection -- PHP реагирует на них автоматически.

#[Override] -- PHP 8.3+

Атрибут #[Override] на методе гарантирует, что метод действительно переопределяет родительский метод или реализует метод интерфейса. Если родительский метод не существует -- PHP выдаст ошибку.

<?php
declare(strict_types=1);

class Base
{
    public function render(): string
    {
        return '';
    }

    public function process(): void
    {
        // business logic
    }
}

class Child extends Base
{
    // Корректно: метод существует в родителе
    #[Override]
    public function render(): string
    {
        return '<div>child content</div>';
    }

    // Fatal Error: Child::rendr() has #[Override] attribute,
    // but no matching parent method exists
    // #[Override]
    // public function rendr(): string
    // {
    //     return 'typo in method name!';
    // }
}

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

  • Защита от опечаток -- если имя метода не совпадает с родительским, сразу ошибка
  • Защита от рефакторинга -- если родительский метод переименовали или удалили, все дочерние классы с #[Override] сломаются с понятной ошибкой
  • Документирование намерения -- явно показывает, что метод переопределяет родительский

Работает с интерфейсами:

<?php
declare(strict_types=1);

interface Renderable
{
    public function render(): string;
}

class Component implements Renderable
{
    #[Override]
    public function render(): string
    {
        return '<component />';
    }
}

Работает с трейтами:

<?php
declare(strict_types=1);

trait Timestampable
{
    public function getCreatedAt(): \DateTimeImmutable
    {
        return $this->createdAt;
    }
}

class Article
{
    use Timestampable;

    private \DateTimeImmutable $createdAt;

    // Переопределение метода из трейта
    #[Override]
    public function getCreatedAt(): \DateTimeImmutable
    {
        return $this->createdAt->setTimezone(new \DateTimeZone('UTC'));
    }
}

PHP 8.5+: #[Override] также будет работать на свойствах, переопределяющих свойства из родительского класса или интерфейса (property hooks, abstract properties).

Рекомендация: Ставьте #[Override] на каждый переопределённый метод. Это спасает от тихих багов при рефакторинге родительских классов.

#[Deprecated] -- PHP 8.4+

Атрибут #[Deprecated] помечает элемент как устаревший. При использовании такого элемента PHP генерирует предупреждение E_USER_DEPRECATED.

<?php
declare(strict_types=1);

// На функции
#[Deprecated("Use calculateTotal() instead", since: "2.0")]
function calculateSum(float $a, float $b): float
{
    return $a + $b;
}

calculateSum(1, 2);
// Deprecated: Function calculateSum() is deprecated since 2.0,
// use calculateTotal() instead

// На методе
class PaymentService
{
    #[Deprecated("Use processPayment() instead", since: "3.1")]
    public function pay(float $amount): bool
    {
        return $this->processPayment($amount);
    }

    public function processPayment(float $amount): bool
    {
        // new implementation
        return true;
    }
}

// На константе класса
class Config
{
    #[Deprecated("Use DB_HOST instead")]
    public const DATABASE_HOST = 'localhost';

    public const DB_HOST = 'localhost';
}

echo Config::DATABASE_HOST;
// Deprecated: Constant Config::DATABASE_HOST is deprecated,
// use DB_HOST instead

Параметры атрибута:

  • message (первый аргумент) -- текст предупреждения, подсказка что использовать вместо
  • since -- версия, с которой элемент устарел

Отличие от trigger_error:

<?php
declare(strict_types=1);

// До PHP 8.4 -- ручное предупреждение
function oldWay(): void
{
    trigger_error('Use newWay() instead', E_USER_DEPRECATED);
}

// PHP 8.4+ -- нативный атрибут
#[Deprecated("Use newWay() instead", since: "2.0")]
function oldWayNative(): void
{
    // code
}

Преимущества #[Deprecated] перед trigger_error:

  • Срабатывает при вызове, а не при выполнении тела функции
  • Информация доступна через Reflection и статический анализ
  • IDE может подсвечивать вызовы устаревших функций
  • Стандартный формат сообщения

PHP 8.5+: #[Deprecated] также будет доступен на трейтах и константах трейтов.

Запомни: #[Deprecated] работает только на функциях, методах и константах классов. На классы, свойства и параметры -- не применяется (для классов используйте PHPDoc @deprecated).

#[SensitiveParameter] -- PHP 8.2+

Атрибут #[SensitiveParameter] скрывает значение параметра в стектрейсах и отладочной информации. Критически важен для безопасности:

<?php
declare(strict_types=1);

function authenticate(
    string $username,
    #[SensitiveParameter] string $password,
    #[SensitiveParameter] string $apiKey,
): bool {
    throw new \RuntimeException('Auth failed');
}

try {
    authenticate('admin', 'super_secret_123', 'sk-abc123xyz');
} catch (\RuntimeException $e) {
    echo $e->getTraceAsString();
}
// В стектрейсе:
// authenticate('admin', Object(SensitiveParameterValue), Object(SensitiveParameterValue))
// Пароль и API-ключ скрыты!

Без #[SensitiveParameter] пароли и токены могли бы попасть:

  • В логи при uncaught exceptions
  • В отчеты об ошибках (Sentry, Bugsnag)
  • В дампы var_dump($exception->getTrace())

Где применять:

<?php
declare(strict_types=1);

class DatabaseConnection
{
    public function __construct(
        private readonly string $host,
        private readonly string $database,
        private readonly string $user,
        #[SensitiveParameter] private readonly string $password,
    ) {}
}

class EncryptionService
{
    public function encrypt(
        string $data,
        #[SensitiveParameter] string $key,
        #[SensitiveParameter] string $iv,
    ): string {
        return openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
    }
}

class TokenService
{
    public function verify(
        #[SensitiveParameter] string $token,
    ): bool {
        // verification logic
        return true;
    }
}

Важно: Всегда ставьте #[SensitiveParameter] на параметры, содержащие: пароли, токены, API-ключи, секреты шифрования, персональные данные (номера карт, SSN). Это одна из самых полезных мер безопасности в PHP.

#[AllowDynamicProperties] -- PHP 8.2+

Начиная с PHP 8.2, динамические свойства (присвоение несуществующих свойств) генерируют предупреждение E_DEPRECATED. В PHP 9.0 они будут запрещены. Атрибут #[AllowDynamicProperties] возвращает старое поведение:

<?php
declare(strict_types=1);

// Без атрибута -- deprecated в PHP 8.2+
class Config
{
    public string $host = 'localhost';
}

$config = new Config();
$config->port = 3306;
// Deprecated: Creation of dynamic property Config::$port is deprecated

// С атрибутом -- динамические свойства разрешены
#[AllowDynamicProperties]
class DynamicConfig
{
    public string $host = 'localhost';
}

$config = new DynamicConfig();
$config->port = 3306;  // OK, no warning
$config->debug = true;  // OK

Когда используется:

  • Миграция легаси-кода -- временное решение на период рефакторинга
  • ORM/ActiveRecord -- объекты с динамическими полями из базы данных
  • Объекты-обёртки -- generic containers, stdClass-подобные
<?php
declare(strict_types=1);

// stdClass уже имеет этот атрибут неявно
$obj = new \stdClass();
$obj->anything = 'works';  // всегда OK

// Классы с __get/__set не нуждаются в атрибуте
class MagicClass
{
    private array $data = [];

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }
}

$magic = new MagicClass();
$magic->foo = 'bar';  // OK: вызывается __set, не динамическое свойство

Рекомендация: Не используйте #[AllowDynamicProperties] в новом коде. Вместо этого объявляйте свойства явно или используйте массив $data с магическими методами. Этот атрибут -- инструмент миграции, а не решение.

#[ReturnTypeWillChange] -- PHP 8.1+

Подавляет предупреждение E_DEPRECATED о несовпадении return type при реализации встроенных интерфейсов PHP:

<?php
declare(strict_types=1);

// PHP 8.1+ генерирует E_DEPRECATED, если встроенный метод
// не имеет return type, совместимого с интерфейсом

class MyCollection implements \Countable, \ArrayAccess
{
    private array $items = [];

    // Без атрибута: Deprecated: MyCollection::count(): Return type
    // should either be compatible with Countable::count(): int,
    // or the #[ReturnTypeWillChange] attribute should be used

    #[ReturnTypeWillChange]
    public function count() // намеренно без : int
    {
        return count($this->items);
    }

    // Лучше -- просто добавить return type:
    public function offsetExists(mixed $offset): bool
    {
        return isset($this->items[$offset]);
    }

    #[ReturnTypeWillChange]
    public function offsetGet(mixed $offset)
    {
        return $this->items[$offset] ?? null;
    }

    public function offsetSet(mixed $offset, mixed $value): void
    {
        $this->items[$offset] = $value;
    }

    public function offsetUnset(mixed $offset): void
    {
        unset($this->items[$offset]);
    }
}

Когда нужен:

  • Библиотеки, поддерживающие несколько версий PHP (7.x + 8.x)
  • Реализация встроенных интерфейсов (Iterator, ArrayAccess, Countable, JsonSerializable)
  • Временная мера до добавления правильных return types

Рекомендация: В новом коде (PHP 8.1+) лучше добавить правильный return type вместо использования #[ReturnTypeWillChange]. Этот атрибут предназначен для обратной совместимости.

#[NoDiscard] -- PHP 8.5+

Атрибут #[NoDiscard] предупреждает, когда возвращаемое значение функции игнорируется. Это помогает предотвратить баги, когда разработчик забыл обработать результат:

<?php
declare(strict_types=1);

#[NoDiscard("Validate result must be checked")]
function validate(mixed $data): ValidationResult
{
    // validation logic
    return new ValidationResult(isValid: false, errors: ['Invalid data']);
}

// Warning: The return value of function validate() is expected
// to be consumed, Validate result must be checked
validate($input);  // Результат проигнорирован!

// Правильно: проверяем результат
$result = validate($input);
if (!$result->isValid) {
    throw new ValidationException($result->errors);
}

Подавление предупреждения через (void):

<?php
declare(strict_types=1);

#[NoDiscard]
function importantResult(): bool
{
    return true;
}

// Если вы осознанно игнорируете результат -- приведите к void
(void) importantResult();  // OK, no warning

Применение на классах и методах:

<?php
declare(strict_types=1);

// На классе -- все методы, возвращающие этот тип, будут проверяться
#[NoDiscard("Result object should not be ignored")]
final readonly class Result
{
    public function __construct(
        public bool $success,
        public string $message = '',
    ) {}
}

class OrderService
{
    // На конкретном методе
    #[NoDiscard("Check if order was placed successfully")]
    public function placeOrder(Order $order): Result
    {
        // process order
        return new Result(success: true);
    }
}

$service = new OrderService();
$service->placeOrder($order);  // Warning! Result ignored

Типичные случаи для #[NoDiscard]:

  • Функции валидации -- результат проверки нужно обработать
  • Операции с побочными эффектами -- результат операции (успех/ошибка)
  • Иммутабельные методы -- str_replace, array_filter -- результат всегда новый
  • Builder pattern -- каждый вызов возвращает новый объект

#[DelayedTargetValidation] -- PHP 8.5+

Этот мета-атрибут применяется к классам-атрибутам и откладывает проверку цели до момента вызова newInstance(). По умолчанию PHP проверяет цель при компиляции, что может вызывать проблемы для атрибутов, которые обрабатываются внешними инструментами.

<?php
declare(strict_types=1);

use Attribute;

// Стандартное поведение: цель проверяется при компиляции
#[Attribute(Attribute::TARGET_METHOD)]
final class StrictAttr {}

// С отложенной валидацией: цель проверяется при newInstance()
#[Attribute(Attribute::TARGET_METHOD)]
#[DelayedTargetValidation]
final class FlexibleAttr {}

// Для StrictAttr ошибка может возникнуть при компиляции
// Для FlexibleAttr ошибка возникнет только при явном чтении

Этот атрибут полезен для:

  • Атрибутов в библиотеках, которые обрабатываются внешними инструментами (Doctrine, Symfony)
  • Атрибутов, чья валидация требует загрузки зависимостей
  • Кросс-фреймворковых атрибутов с нестандартным поведением

Сводная таблица встроенных атрибутов

Атрибут Версия PHP Цель Назначение
#[Attribute] 8.0+ Классы Мета-атрибут: объявление класса-атрибута
#[ReturnTypeWillChange] 8.1+ Методы Подавление deprecation notice для return type
#[SensitiveParameter] 8.2+ Параметры Скрытие значений в стектрейсах
#[AllowDynamicProperties] 8.2+ Классы Разрешение динамических свойств
#[Override] 8.3+ Методы Проверка переопределения родительского метода
#[Deprecated] 8.4+ Функции, методы, константы Пометка устаревшего кода
#[NoDiscard] 8.5+ Функции, методы, классы Предупреждение при игнорировании возвращаемого значения
#[DelayedTargetValidation] 8.5+ Классы-атрибуты Отложенная проверка цели атрибута

Проверь себя

5 из 10

Нужно ли ставить `#[AllowDynamicProperties]` на классы с магическими методами `__get`/`__set`?

С какой версии PHP доступен атрибут `#[Override]`?

Что делает `#[DelayedTargetValidation]`?

Какой атрибут нужен для обратной совместимости return type при реализации встроенных интерфейсов?

Какой уровень ошибки генерирует `#[Deprecated]`?