Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Встроенные атрибуты: намерения, совместимость и защита диагностики
  2. Атрибут: подписать объявление понятными программе метаданными
  3. Attribute: где разрешена пометка и можно ли её повторять
  4. AllowDynamicProperties: осознанно разрешить свободные поля
  5. Override: поймать опечатку, пока код ещё не запущен
  6. Deprecated: дать вызывающему коду понятный путь обновления
  7. NoDiscard: не потерять важный результат операции
  8. ReturnTypeWillChange: временный мост для старой библиотеки
  9. SensitiveParameter: скрыть аргумент в трассировке, сохранив работу функции
  10. DelayedTargetValidation: отложить проверку места применения
  11. Выбрать атрибут по намерению и версии PHP
  12. Функции и методы этой темы
  13. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 73 · От метки к проверяемому договору

Встроенные атрибуты: намерения, совместимость и защита диагностики

Все предопределённые атрибуты PHP, их цели, версии, конструкторы и SensitiveParameterValue с практическими примерами.

Атрибут: подписать объявление понятными программе метаданными

Наглядная модель

Атрибут описывает обработчик, а программа читает описание

Пометка Route хранит путь страницы рядом с функцией catalog.

  1. #[Route('/catalog')]

    Метаданные прикреплены к объявлению функции.

  2. ReflectionFunction('catalog')

    Программа исследует объявление.

  3. getAttributes(Route::class)

    Выбирает нужный вид метаданных.

  4. newInstance()

    Создаёт Route с переданным путём; конструктор выполняется здесь.

  5. $attribute->path

    Маршрутизатор получает /catalog и применяет своё правило.

Пользовательская Route сама не обрабатывает HTTP. Встроенные атрибуты имеют отдельные, известные PHP правила.

Сначала нужна конкретная задача: метка маршрута, предупреждение об устаревшем вызове или защита аргумента в трассировке. Атрибуты решают разные задачи и появились в разных версиях. Пользовательский Route работает только потому, что наш читатель его обрабатывает; встроенные Override/Deprecated/NoDiscard обрабатывает сам PHP.

В сервисе есть обработчик страницы. Хочется записать рядом с ним путь /catalog, чтобы маршрутизатор прочитал его автоматически. Атрибут — структурированная пометка над объявлением. В отличие от обычного комментария его имя и аргументы можно прочитать через Reflection. Но пользовательский атрибут сам не запускает маршрутизатор: нужен код, который его использует.

#[Route("/catalog")] прикрепляет метаданные Route к следующему объявлению. Класс Route помечается встроенным #[Attribute(...)], чтобы PHP разрешил использовать его как атрибут. Его конструктор получает аргументы пометки, когда Reflection создаёт экземпляр. До первого примера: public string $path — открытое строковое свойство; $this->path = $path сохраняет параметр в объекте; newInstance создаёт атрибут и вызывает конструктор.

Метаданные связываются с обработчиком
  1. Над catalog стоит #[Route("/catalog")]
  2. ReflectionFunction находит пометку
  3. ReflectionAttribute::newInstance() создаёт Route
  4. Код маршрутизатора читает path и решает, что делать
Прочитать путь из атрибута функцииСамостоятельный пример
<?php
#[Attribute(Attribute::TARGET_FUNCTION)]
class Route {
    public string $path;
    public function __construct(string $path) { $this->path = $path; }
}
#[Route('/catalog')]
function catalog(): string { return 'Каталог'; }
$reflection = new ReflectionFunction('catalog');
$attribute = $reflection->getAttributes(Route::class)[0]->newInstance();
echo $attribute->path, "\n";
echo catalog(), "\n";
Ожидаемый результат
/catalog
Каталог

Reflection отдельно прочитал метаданные, а обычный catalog() отдельно выполнил функцию. Скобки [0] выбирают первую найденную пометку; в общем загрузчике проверьте наличие и допустимое количество. Не создавайте атрибуты из непроверенного кода: их конструкторы выполняются.

Встроенные атрибуты из этой главы имеют известный PHP смысл: Override проверяет переопределение, SensitiveParameter меняет трассировку и так далее. Это отличается от Route, который ничего не маршрутизирует без вашего обработчика. В namespace используйте полное имя вроде #[\Override] или импорт use Override;. Иначе похожее имя может обозначать другой, даже несуществующий класс.

Базовый синтаксис, повторение и чтение аргументов подробнее: атрибуты и Reflection. Встроенные классы атрибутов final: их не расширяют наследованием. Создание new Override() даёт объект метаданных и само по себе не прикрепляет пометку к методу.

Attribute: где разрешена пометка и можно ли её повторять

Параметр flags у Attribute — битовая маска. Оператор | объединяет разрешения, & проверяет отдельный бит. TARGET_ALL означает все поддержанные места, но не разрешает повторение само по себе. IS_REPEATABLE добавляется отдельно. Свойство flags хранит переданную маску.

КонстантаК чему можно прикреплять пользовательский атрибут
TARGET_CLASSКласс
TARGET_FUNCTIONФункция
TARGET_METHODМетод
TARGET_PROPERTYСвойство
TARGET_CLASS_CONSTANTКонстанта класса
TARGET_PARAMETERПараметр
TARGET_CONSTANTКонстанта вне класса, PHP 8.5+
TARGET_ALLВсе допустимые места в данной версии
IS_REPEATABLEДополнительный флаг: разрешить повторение на одном объявлении
Разрешить несколько меток на функцииСамостоятельный пример
<?php
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
class Tag {
    public function __construct(public string $name) {}
}
#[Tag('catalog'), Tag('public')]
function listProducts(): void {}
foreach ((new ReflectionFunction('listProducts'))->getAttributes(Tag::class) as $metadata) {
    echo $metadata->newInstance()->name, "\n";
}
Ожидаемый результат
catalog
public

public string $name в параметре конструктора одновременно объявляет и заполняет свойство. В реальном приложении метки могут выбрать обработчики кеширования или документации. Пока вы не написали обработчик, это только данные.

Для пользовательского атрибута нарушение цели или повторения проверяется при создании экземпляра через Reflection. Встроенные атрибуты могут иметь проверки на этапе компиляции. С PHP 8.5 само #[Attribute] на abstract class, enum, interface или trait вызывает ошибку компиляции; раньше ошибка обнаруживалась при попытке newInstance. Пользовательский класс атрибута должен быть пригоден для создания экземпляра.

Объявление читается, но неправильная цель отклоняется при созданииСамостоятельный пример
<?php
#[Attribute(Attribute::TARGET_FUNCTION)]
class FunctionTag {}
#[FunctionTag]
class Product {}
$metadata = (new ReflectionClass(Product::class))->getAttributes(FunctionTag::class)[0];
echo $metadata->getName(), "\n";
try { $metadata->newInstance(); }
catch (Error $error) { echo "Неподходящая цель\n"; }
Ожидаемый результат
FunctionTag
Неподходящая цель

Чтение имени атрибута и создание его объекта — разные операции. Поэтому система, которая проверяет конфигурацию при запуске, должна проверять не только наличие метки, но и корректность её использования.

AllowDynamicProperties: осознанно разрешить свободные поля

С PHP 8.2 запись нового, не объявленного свойства обычного класса вызывает deprecated. Это помогает обнаружить опечатку вроде $order->stauts вместо status. Если контейнер по замыслу принимает произвольные поля, AllowDynamicProperties разрешает их явно. Если поля заранее известны, объявите их в классе — это полезнее подавления предупреждения.

Разрешение действует и у потомкаСамостоятельный пример
<?php
#[\AllowDynamicProperties]
class FlexibleRecord {}
class ImportedRecord extends FlexibleRecord {}
$record = new ImportedRecord();
$record->externalCode = 'EXT-7';
echo $record->externalCode, "\n";
Ожидаемый результат
EXT-7

Наследуется эффект разрешения динамических свойств, а не общая способность всех атрибутов наследоваться. В namespace обратный слеш у \AllowDynamicProperties выбирает именно встроенный класс.

stdClass уже разрешает динамические поля. Обработчик __set может перенаправить запись в заранее объявленный внутренний массив; тогда новое свойство самого объекта не создаётся. Но запись $this->$name = $value внутри __set снова создаёт динамическое свойство и не становится автоматически корректной. Один лишь __get не решает запись неизвестных полей.

Известное поле лучше объявитьСамостоятельный пример
<?php
class Order {
    public string $status = 'new';
}
$order = new Order();
$order->status = 'paid';
echo $order->status, "\n";
Ожидаемый результат
paid

Для заранее определённого статуса отдельный атрибут не нужен. Тип string ограничивает тип значения, но не набор допустимых статусов: для этого можно использовать enum из главы о перечислениях.

Override: поймать опечатку, пока код ещё не запущен

Вы переопределяете метод отправки уведомления. Если ошибиться в имени, без проверки получится новый метод, который никто не вызовет. #[Override] сообщает намерение: в родителе или реализуемом интерфейсе должно быть соответствующее объявление. При его отсутствии PHP выдаёт ошибку компиляции. Атрибут не делает несовместимую сигнатуру совместимой.

Подтвердить реализацию метода интерфейсаСамостоятельный пример
<?php
interface Sender { public function send(string $text): string; }
class ConsoleSender implements Sender {
    #[\Override]
    public function send(string $text): string { return 'Отправлено: ' . $text; }
}
echo (new ConsoleSender())->send('Заказ готов'), "\n";
Ожидаемый результат
Отправлено: Заказ готов

Если назвать метод snd, требуемый send не будет реализован; если пометить лишний метод без совпадения, Override тоже обнаружит ошибку. Компиляционная ошибка такого объявления не является обычным возвращаемым результатом вызова.

Проверить намерение переопределить свойство: PHP 8.5+Самостоятельный пример
<?php
class BaseView { public string $title = 'Страница'; }
class CatalogView extends BaseView {
    #[\Override]
    public string $title = 'Каталог';
}
echo (new CatalogView())->title, "\n";
Ожидаемый результат
Каталог

Override доступен для методов с PHP 8.3, для свойств — с PHP 8.5. Он не применяется к __construct, который имеет особые правила сигнатуры. Пометка на совершенно новом свойстве не подходит: требуется совпадение в родительском/интерфейсном договоре.

Deprecated: дать вызывающему коду понятный путь обновления

Библиотека заменяет старый метод, но не может сразу сломать клиентов. #[Deprecated] с PHP 8.4 позволяет выдать E_USER_DEPRECATED при использовании старой функции/метода или константы класса. Тело функции всё ещё выполняется. Сообщение должно говорить, что вызвать вместо неё, а since — с какой версии вашего API началась миграция.

Предупредить о старом имени и продолжить переходный периодСамостоятельный пример
<?php
set_error_handler(function (int $level, string $message): bool {
    if ($level !== E_USER_DEPRECATED) { return false; }
    echo "Устаревший вызов\n";
    return true;
});
#[\Deprecated(message: 'Используйте currentPrice()', since: '2.0')]
function oldPrice(): int { return 100; }
echo oldPrice(), "\n";
restore_error_handler();
Ожидаемый результат
Устаревший вызов
100

Обработчик перехватывает ожидаемую диагностику только для воспроизводимого примера и затем восстанавливается. В приложении такую диагностику фиксируют в журнале разработки и исправляют вызовы; глобальное подавление скроет план миграции.

message и since — публичные readonly ?string, оба по умолчанию null. PHP не проверяет, что since является номером версии: это может быть дата или принятый в проекте текст. С PHP 8.5 Deprecated разрешён также на trait и константе вне класса, объявленной через const. Разрешение TARGET_CLASS здесь не означает, что можно так пометить любой обычный класс: для этой возможности речь о trait.

Устаревающая константа вне класса: PHP 8.5+Самостоятельный пример
<?php
set_error_handler(function (int $level): bool {
    if ($level !== E_USER_DEPRECATED) { return false; }
    echo "Старое имя константы\n";
    return true;
});
#[\Deprecated('Используйте API_TIMEOUT', '2.0')]
const OLD_TIMEOUT = 10;
echo OLD_TIMEOUT, "\n";
restore_error_handler();
Ожидаемый результат
Старое имя константы
10

Атрибут записан при объявлении const, а предупреждение связано с использованием. Для константы, создаваемой обычным вызовом define, такой синтаксис пометки не применяется.

NoDiscard: не потерять важный результат операции

Проверка пачки заказов вернула список отклонённых элементов. Если вызывающий код отбросил результат, он может ошибочно считать все заказы пригодными к оформлению. С PHP 8.5 #[NoDiscard] на функции/методе предупреждает, когда результат вообще не используется. Это помощь против пропуска проверки, а не доказательство, что результат обработан правильно.

Получить список заказов, которые не прошли импортСамостоятельный пример
<?php
declare(strict_types=1);

set_error_handler(function (int $level): bool {
    if ($level !== E_USER_WARNING) { return false; }
    echo "Не потеряйте список отклонённых заказов\n";
    return true;
});
#[NoDiscard('Проверьте отклонённые элементы')]
function rejectedItems(): array { return ['Заказ A-42: неизвестный артикул']; }
rejectedItems();
$rejected = rejectedItems();
foreach ($rejected as $message) { echo $message, "\n"; }
restore_error_handler();
Ожидаемый результат
Не потеряйте список отклонённых заказов
Заказ A-42: неизвестный артикул

PHP 8.5+. Первый вызов намеренно выбрасывает важный результат, и NoDiscard предупреждает. Второй сохраняет список и даёт полезное сообщение для исправления импорта. Атрибут не заставляет программу правильно обработать список: присвоение само по себе тоже ещё не отчёт об ошибках.

Явно отбросить только ненужный прошлый результат — PHP 8.5Самостоятельный пример
<?php
declare(strict_types=1);

#[NoDiscard]
function replaceCaption(array &$preview, string $caption): string
{
    $previous = $preview['caption'];
    $preview['caption'] = $caption;
    return $previous;
}
$preview = ['caption' => 'Черновик'];
(void) replaceCaption($preview, 'Готовый отчёт');
echo $preview['caption'], "\n";
$_ = replaceCaption($preview, 'Отчёт за октябрь');
echo $preview['caption'], "\n";
Ожидаемый результат
Готовый отчёт
Отчёт за октябрь

Функция заменяет подпись и возвращает прежний текст. В первом вызове (void) явно отбрасывает прошлую подпись; во втором результат присвоен переменной $_ и потому считается использованным. $_ — обычное имя, не встроенная защита. Так нельзя обосновывать игнорирование списка ошибок: сначала определите, действительно ли возвращённые данные не нужны.

Срабатывает атрибут фактически вызванного объявления. Пометка только в interface или abstract-методе не переносит предупреждение на реализацию. Переопределённый метод должен быть помечен отдельно, если ему нужна та же проверка. Метод, скопированный из trait, сохраняет атрибут. На PHP 8.4 и ниже неизвестная пометка NoDiscard не добавляет эту проверку; это не повод писать неподдерживаемый там (void).

ReturnTypeWillChange: временный мост для старой библиотеки

С PHP 8.1 у многих переопределяемых внутренних методов появились предварительные возвращаемые типы. Старый класс без подходящего типа получает deprecated при проверке наследования. Если библиотека ещё обязана поддерживать старый синтаксис PHP и не может сразу указать тип, #[ReturnTypeWillChange] временно подавляет именно эту диагностику.

Старый счётчик корзины: временная совместимостьСамостоятельный пример
<?php
declare(strict_types=1);

class LegacyBasket implements Countable
{
    private array $items = ['Чай', 'Кофе'];
    #[ReturnTypeWillChange]
    public function count() { return count($this->items); }
}
echo 'В старой корзине: ', count(new LegacyBasket()), " позиции\n";
Ожидаемый результат
В старой корзине: 2 позиции

ReturnTypeWillChange временно подавляет deprecation из-за отсутствующего ожидаемого типа результата внутреннего договора. Он не исправляет ошибочный алгоритм и не обещает будущую совместимость. Следующий пример показывает конечную цель миграции.

Явно выполнить современный договор CountableСамостоятельный пример
<?php
declare(strict_types=1);

class Basket implements Countable
{
    private array $items = ['Чай', 'Кофе'];
    public function count(): int { return count($this->items); }
}
echo 'В новой корзине: ', count(new Basket()), " позиции\n";
Ожидаемый результат
В новой корзине: 2 позиции

Добавлен совместимый : int, поэтому временный атрибут не нужен. Число по-прежнему обозначает позиции, а не суммарные единицы. Миграция сохраняет видимый результат и делает договор проверяемым.

SensitiveParameter: скрыть аргумент в трассировке, сохранив работу функции

Что скрывается при исключении
  1. authenticate получает обычную строку пароля
  2. При построении trace PHP заменяет помеченный аргумент оболочкой SensitiveParameterValue
  3. Обычный дамп этой оболочки не показывает исходную строку
  4. Сам пароль внутри функции остаётся доступен её коду
  5. Поэтому явное логирование секрета всё равно запрещает договор приложения

При сбое входа в сервис стек вызовов может включать пароль или токен. #[SensitiveParameter] ставится на параметр функции/метода: в трассировке его значение заменяется объектом SensitiveParameterValue. Внутри функции параметр остаётся обычным исходным значением. Это защита от случайного вывода аргументов стека, не шифрование и не запрет доступа к значению.

Один параметр имеет два представления
  1. В функцию пришёл учебный пароль
  2. Тело получает обычную строку
  3. При создании трассировки аргумент оборачивается
  4. Диагностический вывод оболочки не показывает содержимое
Проверить скрытие без печати секретаСамостоятельный пример
<?php
ini_set('zend.exception_ignore_args', '0');
function authenticate(#[\SensitiveParameter] string $password): void {
    throw new RuntimeException('Вход не выполнен');
}
try { authenticate('demo-secret'); }
catch (RuntimeException $error) {
    $argument = $error->getTrace()[0]['args'][0];
    echo get_class($argument), "\n";
    var_dump($argument->__debugInfo());
}
Ожидаемый результат
SensitiveParameterValue
array(0) {
}

Настройка включает аргументы только внутри самостоятельного опыта, чтобы показать обёртку. При zend.exception_ignore_args=1 аргументы исключения вообще не сохраняются. Во многих рабочих окружениях это подходящий более общий способ не включать их в диагностику.

Пометка не шифрует вход внутри функцииСамостоятельный пример
<?php
declare(strict_types=1);

function tokenStatus(#[SensitiveParameter] string $token): string
{
    return $token === 'demo-token' ? 'Учебный токен принят' : 'Учебный токен отклонён';
}
echo tokenStatus('demo-token'), "\n";
echo tokenStatus('other'), "\n";
Ожидаемый результат
Учебный токен принят
Учебный токен отклонён

SensitiveParameter меняет представление аргумента в трассировке, но внутри функции token остаётся обычной строкой. Это демонстрационное сравнение, не готовая авторизация. Не выводите и не логируйте сам токен; атрибут не защитит от вашего echo или записи в файл.

Пометка относится к конкретному параметру конкретного вызова. Если дальше передать секрет в другую функцию без пометки, её кадр может раскрыть значение. У promoted-параметра конструктора атрибут защищает аргумент, а не произвольный вывод свойства объекта. Обёртка SensitiveParameterValue хранит private readonly mixed value, но предоставляет явный getValue: это не граница безопасности от кода с доступом к объекту.

DelayedTargetValidation: отложить проверку места применения

Библиотека может поставлять код с атрибутом, для которого будущая версия PHP разрешит новое место применения. С PHP 8.5 DelayedTargetValidation откладывает проверку допустимой цели встроенных атрибутов на том же объявлении до ReflectionAttribute::newInstance(). Это специализированный инструмент совместимости: он не делает ошибочную цель корректной.

Проверка цели выполняется при создании через Reflection: PHP 8.5+Самостоятельный пример
<?php
class Feature {
    #[\DelayedTargetValidation]
    #[\Override]
    public const LABEL = 'Каталог';
}
echo Feature::LABEL, "\n";
$reflection = new ReflectionClassConstant(Feature::class, 'LABEL');
$metadata = $reflection->getAttributes(Override::class)[0];
try { $metadata->newInstance(); }
catch (Error $error) { echo "Override не допускает эту цель\n"; }
Ожидаемый результат
Каталог
Override не допускает эту цель

В PHP 8.5 Override допускает метод/свойство, но не константу класса. Отложенная проверка позволила загрузить объявление, затем newInstance обнаружил несоответствие. Это контролируемая иллюстрация, а не рекомендуемое оформление константы.

Атрибут разрешён на всех целях, не имеет собственных документированных методов и относится только к target validation встроенных атрибутов. Он не подавляет смысловую проверку: метод с Override всё равно обязан переопределять существующее объявление. Без учёта этой границы можно ошибочно принять отсрочку за разрешение любых атрибутов.

Выбрать атрибут по намерению и версии PHP

ПотребностьАтрибутВерсия и граница
Создать собственные метаданныеAttribute8.0; константы вне класса с 8.5
Осознанно разрешить свободные свойстваAllowDynamicProperties8.2; не обнаружит опечатки полей
Отложить проверку цели встроенной пометкиDelayedTargetValidation8.5; не отменяет смысловую проверку
Предупредить об устаревшем APIDeprecated8.4; trait и внешние const с 8.5
Не потерять важный результатNoDiscard8.5; не доказывает корректную обработку
Проверить намерение переопределитьOverride8.3; свойства с 8.5
Временно поддержать старую сигнатуруReturnTypeWillChange8.1; только tentative-типы внутренних методов
Не показывать параметр в стекеSensitiveParameter8.2; не чистит ручные логи/сообщения

Перед обновлением библиотеки проверьте используемый PHP и минимальную поддерживаемую версию. Неизвестный атрибут может не повлиять на выполнение старого PHP, но его Reflection::newInstance всё равно не создаст отсутствующий класс. Метаданные, синтаксис и фактические проверки имеют разные границы совместимости.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

Attribute::__constructСоздать описание разрешённых целей пользовательского атрибута.

PHP 8.0+ · Core

public function __construct(int $flags = Attribute::TARGET_ALL)

Создать описание разрешённых целей пользовательского атрибута.

Параметр / результатДоговор
flagsБитовая маска TARGET_* и, при необходимости, IS_REPEATABLE. По умолчанию TARGET_ALL.
СвойствоПубличное int flags сохраняет маску.
Результат newОбъект Attribute. Прямое создание не помечает автоматически другой класс.
Маска сообщает разрешения
  1. TARGET_FUNCTION | IS_REPEATABLE
  2. new Attribute(flags)
  3. Свойство flags позволяет прочитать настройку
Умолчание разрешает цели, но не повторение одной меткиСамостоятельный пример
<?php
declare(strict_types=1);

$metadata = new Attribute();
echo $metadata->flags === Attribute::TARGET_ALL ? "Цели по умолчанию: все допустимые объявления\n" : "Цели ограничены\n";
echo ($metadata->flags & Attribute::IS_REPEATABLE) !== 0 ? "Повторение разрешено\n" : "Повторение надо разрешить отдельно\n";
Ожидаемый результат
Цели по умолчанию: все допустимые объявления
Повторение надо разрешить отдельно

flags — битовая маска разрешений. TARGET_ALL объединяет цели, но не IS_REPEATABLE. Сам new Attribute только создаёт объект метаданных; чтобы пометить класс пользовательского атрибута, применяется #[Attribute(...)]. Реальное применение показано в начале главы.

Разрешить несколько меток только у функцийСамостоятельный пример
<?php
declare(strict_types=1);

$metadata = new Attribute(Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE);
echo ($metadata->flags & Attribute::TARGET_FUNCTION) !== 0 ? "Можно помечать функции\n" : "Функции запрещены\n";
echo ($metadata->flags & Attribute::TARGET_METHOD) !== 0 ? "Можно помечать методы\n" : "Методы не входят в разрешённые цели\n";
echo ($metadata->flags & Attribute::IS_REPEATABLE) !== 0 ? "Несколько меток разрешены\n" : "Только одна метка\n";
Ожидаемый результат
Можно помечать функции
Методы не входят в разрешённые цели
Несколько меток разрешены

| объединяет выбранные биты, & выделяет проверяемый бит. TARGET_FUNCTION и TARGET_METHOD различаются: метод — функция внутри класса, но у атрибута это отдельная цель. Результат разрешений нужен сборщику конфигурации, а не заменяет фактическую проверку newInstance.

Рядом: Override::__construct, Deprecated::__construct. Карта встроенных атрибутов.

AllowDynamicProperties::__constructСоздать экземпляр маркера разрешённых динамических свойств.

PHP 8.2+ · Core

public function __construct()

Создать экземпляр маркера разрешённых динамических свойств.

Параметр / результатДоговор
АргументыНет.
Свойства настройкиНет.
Применение#[AllowDynamicProperties] на классе, PHP 8.2+. Эффект действует у его потомков.
Маркер находится на объявлении
  1. #[AllowDynamicProperties] над FlexibleRecord
  2. PHP разрешает новые поля этой ветви классов
  3. Reflection может отдельно создать объект маркера
Прочитать маркер через ReflectionСамостоятельный пример
<?php
#[\AllowDynamicProperties]
class FlexibleRecord {}
$reflection = new ReflectionClass(FlexibleRecord::class);
$marker = $reflection->getAttributes(AllowDynamicProperties::class)[0]->newInstance();
echo get_class($marker), "\n";
Ожидаемый результат
AllowDynamicProperties

Маркер не содержит список разрешённых имён: он разрешает динамические свойства вообще. Для отдельных известных полей лучше объявление свойств.

Правильное полное имя внутри namespaceСамостоятельный пример
<?php
namespace Import;
#[\AllowDynamicProperties]
class Row {}
$row = new Row();
$row->remoteId = 'A-7';
echo $row->remoteId, "\n";
Ожидаемый результат
A-7

Без обратного слеша или импорта AllowDynamicProperties в namespace Import означал бы Import\AllowDynamicProperties. Неизвестное имя атрибута само по себе не обязано вызвать ошибку до Reflection, поэтому неверный импорт легко принять за неработающее разрешение.

Рядом: Attribute::__construct. Карта встроенных атрибутов.

Deprecated::__constructСохранить сообщение о замене и отметку начала устаревания.

PHP 8.4+ · Core

public function __construct(?string $message = null, ?string $since = null)

Сохранить сообщение о замене и отметку начала устаревания.

Параметр / результатДоговор
messageПричина/рекомендуемая замена или null.
sinceСтрока версии, даты или другого принятого обозначения; PHP её формат не проверяет.
СвойстваПубличные readonly ?string message и since.
Информация для миграции
  1. message: используйте новый метод
  2. since: версия вашего API
  3. Диагностика устаревшего вызова содержит эти сведения
Получить параметры пометкиСамостоятельный пример
<?php
$metadata = new Deprecated('Используйте findOrder()', '2.0');
echo $metadata->message, "\n", $metadata->since, "\n";
Ожидаемый результат
Используйте findOrder()
2.0

Создание объекта не вызывает deprecated: оно только создаёт метаданные. Предупреждение относится к использованию помеченного API.

Показать читателю документации путь миграции старого отчётаСамостоятельный пример
<?php
#[\Deprecated(message: 'Используйте newReport()', since: '2026-10')]
function oldReport(): string { return 'Старый отчёт'; }
$reflection = new ReflectionFunction('oldReport');
$metadata = $reflection->getAttributes(Deprecated::class)[0]->newInstance();
echo $reflection->isDeprecated() ? "Старый вызов отмечен устаревшим\n" : "Вызов актуален\n";
echo $metadata->message, "\n";
echo $metadata->since, "\n";
Ожидаемый результат
Старый вызов отмечен устаревшим
Используйте newReport()
2026-10

Reflection прочитал метку, не выполняя oldReport. message даёт действие для миграции, since — версию/дату, принятую библиотекой. Значение since не является встроенным условием автоматического удаления функции.

Рядом: NoDiscard::__construct, Attribute::__construct. Карта встроенных атрибутов.

NoDiscard::__constructЗадать объяснение, почему результат нельзя терять.

PHP 8.5+ · Core

public function __construct(?string $message = null)

Задать объяснение, почему результат нельзя терять.

Параметр / результатДоговор
messageНеобязательная строка пояснения, по умолчанию null.
СвойствоПубличное readonly ?string message.
ДоступностьPHP 8.5+, цели — функция и метод.
Смысл предупреждения
  1. Операция возвращает сведения об отказах
  2. NoDiscard хранит причину важности результата
  3. Отброшенный вызов даёт warning с пояснением
Прочитать пояснение и значение по умолчаниюСамостоятельный пример
<?php
$withReason = new NoDiscard('Обработайте элементы с ошибками');
$withoutReason = new NoDiscard();
echo $withReason->message, "\n";
var_dump($withoutReason->message);
Ожидаемый результат
Обработайте элементы с ошибками
NULL

Сформулируйте причину с точки зрения вызывающего кода. «Потому что надо» не поможет исправить потерянную обработку.

Использовать результат помеченной функцииСамостоятельный пример
<?php
#[\NoDiscard('Проверьте успешность локальной проверки')]
function validateLocalRecord(): bool { return false; }
if (!validateLocalRecord()) { echo "Запись отклонена\n"; }
Ожидаемый результат
Запись отклонена

Использование в условии считается использованием результата. Атрибут не требует присваивать его именно переменной и не гарантирует, что ваша логика if правильна.

Рядом: Deprecated::__construct, Attribute::__construct. Карта встроенных атрибутов.

Override::__constructСоздать маркер намеренного переопределения.

PHP 8.3+ · Core

public function __construct()

Создать маркер намеренного переопределения.

Параметр / результатДоговор
АргументыНет.
ЦелиМетод с PHP 8.3; свойство с PHP 8.5. Конструктор __construct исключён.
ПроверкаСовпадающее объявление должно существовать у родителя/интерфейса; нарушение обнаруживается при компиляции.
Намерение связано с конкретным методом
  1. Родитель определил title()
  2. Потомок пометил свой title()
  3. PHP проверяет существование переопределяемого объявления
Прочитать маркер у переопределённого методаСамостоятельный пример
<?php
class BaseLabel { public function label(): string { return 'База'; } }
class ProductLabel extends BaseLabel {
    #[\Override]
    public function label(): string { return 'Товар'; }
}
$method = new ReflectionMethod(ProductLabel::class, 'label');
echo get_class($method->getAttributes(Override::class)[0]->newInstance()), "\n";
echo (new ProductLabel())->label(), "\n";
Ожидаемый результат
Override
Товар

Маркер уже участвовал в проверке объявления; Reflection только создал его объект. Он не переключает вызов на родителя и не выполняет parent::label.

Создание маркера само не прикрепляет его к классуСамостоятельный пример
<?php
$marker = new Override();
class PlainLabel { public function label(): string { return 'Обычный'; } }
echo get_class($marker), "\n";
echo count((new ReflectionMethod(PlainLabel::class, 'label'))->getAttributes()), "\n";
Ожидаемый результат
Override
0

Объект маркера — отдельное значение. Для проверки метода пометка должна находиться в его объявлении. Это общее различие new AttributeClass и синтаксиса #[AttributeClass].

Рядом: Attribute::__construct, ReturnTypeWillChange::__construct. Карта встроенных атрибутов.

ReturnTypeWillChange::__constructСоздать переходный маркер для предварительных возвращаемых типов внутренних методов.

PHP 8.1+ · Core

public function __construct()

Создать переходный маркер для предварительных возвращаемых типов внутренних методов.

Параметр / результатДоговор
АргументыНет.
ЦельМетод, PHP 8.1+.
ГраницаПодавляет tentative-return-type deprecated, но не несовместимость строгого пользовательского типа.
Временная совместимость библиотеки
  1. Старый count() пока без : int
  2. ReturnTypeWillChange подавляет конкретную диагностику
  3. После обновления минимального PHP объявите совместимый тип
Увидеть маркер на старой реализацииСамостоятельный пример
<?php
class OldItems implements Countable {
    #[\ReturnTypeWillChange]
    public function count() { return 3; }
}
$method = new ReflectionMethod(OldItems::class, 'count');
echo get_class($method->getAttributes(ReturnTypeWillChange::class)[0]->newInstance()), "\n";
echo count(new OldItems()), "\n";
Ожидаемый результат
ReturnTypeWillChange
3

Это пример миграции, а не рекомендуемый старт нового класса. Подходящий современный тип исключает необходимость маркера.

Посмотреть предварительный тип внутреннего методаСамостоятельный пример
<?php
$method = new ReflectionMethod(Countable::class, 'count');
var_dump($method->hasTentativeReturnType());
echo $method->getTentativeReturnType(), "\n";
Ожидаемый результат
bool(true)
int

Так инструмент анализа отличает tentative-договор от отсутствия сведений о результате. Проверка выполнена на PHP 8.5.11; переход к строгому типу в будущей версии потребует обновления библиотечного кода.

Рядом: Override::__construct, Attribute::__construct. Карта встроенных атрибутов.

SensitiveParameter::__constructСоздать маркер параметра, скрываемого в трассировках.

PHP 8.2+ · Core

public function __construct()

Создать маркер параметра, скрываемого в трассировках.

Параметр / результатДоговор
АргументыНет.
ЦельПараметр функции/метода, PHP 8.2+.
ЭффектЗначение аргумента в стеке заменяется SensitiveParameterValue, само тело получает исходное значение.
Пометка находится на параметре
  1. #[SensitiveParameter] string $token
  2. Вызов получает обычную строку
  3. В трассировке аргумент представлен оболочкой
Прочитать намерение скрывать токенСамостоятельный пример
<?php
function connectService(#[\SensitiveParameter] string $token): void {}
$parameter = (new ReflectionFunction('connectService'))->getParameters()[0];
echo get_class($parameter->getAttributes(SensitiveParameter::class)[0]->newInstance()), "\n";
Ожидаемый результат
SensitiveParameter

Метаданные не содержат конкретный токен; это свойство объявления параметра. Не смешивайте объект маркера и объект SensitiveParameterValue, который хранит конкретное значение.

Скрыть параметр в debug_backtraceСамостоятельный пример
<?php
function inspectCall(#[\SensitiveParameter] string $token): string {
    $argument = debug_backtrace()[0]['args'][0];
    return get_class($argument);
}
echo inspectCall('demo-token'), "\n";
Ожидаемый результат
SensitiveParameterValue

Пометка действует и в обычной трассировке вызовов. Если выбран режим DEBUG_BACKTRACE_IGNORE_ARGS, аргументы вообще отсутствуют; нельзя читать [args][0] без учёта настроек используемого сборщика.

Рядом: SensitiveParameterValue::__construct, SensitiveParameterValue::__debugInfo, SensitiveParameterValue::getValue. Карта встроенных атрибутов.

SensitiveParameterValue::__constructОбернуть конкретное значение для безопасного обычного отладочного представления.

PHP 8.2+ · Core

public function __construct(mixed $value)

Обернуть конкретное значение для безопасного обычного отладочного представления.

Параметр / результатДоговор
valueЗначение любого типа.
Хранениеprivate readonly mixed value внутри объекта.
ГраницаОбёртка не шифрует содержимое; getValue может его вернуть.
Обёртка и содержимое различаются
  1. Исходная учебная строка
  2. new SensitiveParameterValue(value)
  3. Обычный var_dump показывает оболочку, не её секрет
Создать обёртку без вывода содержимогоСамостоятельный пример
<?php
$protected = new SensitiveParameterValue('demo-secret');
echo get_class($protected), "\n";
var_dump($protected->__debugInfo());
Ожидаемый результат
SensitiveParameterValue
array(0) {
}

Класс final. Пустой отладочный массив скрывает поле от стандартного var_dump; он не делает само значение недоступным доверенному коду.

Обёртка не создаёт независимую копию переданного состоянияСамостоятельный пример
<?php
declare(strict_types=1);

$data = (object) ['enabled' => false];
$protected = new SensitiveParameterValue($data);
$protected->getValue()->enabled = true;
echo $data->enabled ? "Исходная настройка тоже включена\n" : "Исходная настройка не изменилась\n";
Ожидаемый результат
Исходная настройка тоже включена

Переданный объект и объект из getValue — один экземпляр. Изменение через один доступ видно через другой. Для опыта взято несекретное поле enabled: содержимое защищённого токена выводить для проверки идентичности не требуется.

Рядом: SensitiveParameterValue::__debugInfo, SensitiveParameterValue::getValue, SensitiveParameter::__construct. Карта встроенных атрибутов.

SensitiveParameterValue::__debugInfoВернуть пустое отладочное представление.

PHP 8.2+ · Core

public function __debugInfo(): array

Вернуть пустое отладочное представление.

Параметр / результатДоговор
АргументыНет.
ВозвратПустой массив.
Автоматический вызовvar_dump использует этот метод для представления объекта.
Защита от случайного вывода
  1. var_dump получает оболочку
  2. __debugInfo() возвращает []
  3. Содержимое private value не попадает в этот вывод
Получить пустой массив явноСамостоятельный пример
<?php
$protected = new SensitiveParameterValue(['password' => 'demo-secret']);
var_dump($protected->__debugInfo());
Ожидаемый результат
array(0) {
}

Метод одинаково скрывает строку, массив и другие значения. Он не выполняет выборочное маскирование полей: отладочное представление оболочки целиком пустое.

Проверить вывод, не привязываясь к номеру объектаСамостоятельный пример
<?php
$protected = new SensitiveParameterValue('demo-secret');
ob_start();
var_dump($protected);
$dump = ob_get_clean();
var_dump(str_contains($dump, 'SensitiveParameterValue'));
var_dump(str_contains($dump, 'demo-secret'));
Ожидаемый результат
bool(true)
bool(false)

Буфер вывода здесь позволяет проверить диагностический текст, не печатая его. Номер объекта меняется между запусками, поэтому проверяем смысловые признаки. Касты, Reflection и ручное извлечение не являются тем же самым отладочным представлением.

Рядом: SensitiveParameterValue::getValue, SensitiveParameterValue::__construct. Карта встроенных атрибутов.

SensitiveParameterValue::getValueЯвно получить исходное чувствительное значение.

PHP 8.2+ · Core

public function getValue(): mixed

Явно получить исходное чувствительное значение.

Параметр / результатДоговор
АргументыНет.
ВозвратИзначально сохранённое значение с исходным типом.
Риск использованияПосле извлечения его легко снова вывести в лог; автоматическая защита оболочки на новую переменную не распространяется.
Осознанное извлечение
  1. Обёртка хранит значение
  2. getValue() возвращает его
  3. Дальнейшая обработка снова отвечает за конфиденциальность
Использовать защищённый аргумент без печати самого значенияСамостоятельный пример
<?php
declare(strict_types=1);

$protected = new SensitiveParameterValue('demo-token');
$matches = $protected->getValue() === 'demo-token';
echo $matches ? "Учебная проверка токена прошла\n" : "Учебная проверка токена отклонена\n";
Ожидаемый результат
Учебная проверка токена прошла

getValue возвращает исходное значение, поэтому обёртка не является шифрованием или запретом доступа. Здесь выводится только решение, токен не печатается. Это локальная демонстрация API, а не полноценная схема аутентификации.

Сохранить тип обёрнутого значенияСамостоятельный пример
<?php
$protected = new SensitiveParameterValue(['id' => 7]);
$value = $protected->getValue();
echo get_debug_type($value), "\n";
echo $value['id'], "\n";
Ожидаемый результат
array
7

Возврат mixed не преобразует всё в строку. При массиве действуют обычные правила значений PHP, при объекте возвращается тот же объект. Не сериализуйте извлечённые пароли ради удобства отладки.

Рядом: SensitiveParameterValue::__debugInfo, SensitiveParameterValue::__construct. Карта встроенных атрибутов.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.