Подробнее об атрибуте Override

Источник: «More about the Override Attribute»
Подробнее об атрибуте Override: Override имеет несколько специфических, менее известных вариантов поведения с классами, трейтами и интерфейсами.

Несколько дополнительных шагов с PHP атрибутом Override

Атрибут Override был представлен в PHP 8.3. Это новый атрибут, отмечающий методы, которые должны переопределять метод родительского класса: один из родителей должен определять метод с тем же именем. Если это так, то все в порядке. Если ни один из родительских методов не перезаписан, то PHP выдаёт фатальную ошибку при компиляции.

<?php

class x {
function foo() {}
}

class y extends x {
#[Override]
function foo() {}

#[Override]
function goo() {}
//Fatal error: y::goo() has #[\Override] attribute, but no matching parent method exists
}

?>

В статье рассмотрим несколько вариантов, которые расходятся с основным определением атрибутов Override. Это поможет разобраться с неожиданным на первый взгляд поведением, которое на самом деле является обычным поведением PHP.

Атрибут Override игнорируется до версии PHP 8.3

Атрибут Override поддерживается в движке PHP 8.3. Это означает, что PHP полностью игнорирует его в PHP 8.0 — 8.2. Атрибуты полностью игнорируются в PHP 7.4 и старше. Это ожидаемое поведение.

Даже если PHP не использует Override, инструменты статического анализа могут его использовать. Хотя на данный момент только Exakat использует его в старых версиях PHP.

Ожидание атрибута Override в PHP 8.3

Если ожидается переход кода на PHP 8.3 с последующим использованием Override, можно использовать атрибут в старой версии PHP и позволить ему срабатывать в новой версии PHP 8.3.

Это легко сделать благодаря природе Override: в основном это проверка во время компиляции. Это означает, что PHP не будет компилировать код, не удовлетворяющий требованиям Override.

<?php

// Компилируется и запускается в PHP 8.0 - 8.2
// Ошибка во время компиляции в PHP 8.3 и 8.4
class x {
#[Override]
function foo() {}
//Fatal error: y::goo() has #[\Override] attribute, but no matching parent method exists
}

?>

Поэтому рекомендуется добавить проверку компиляции в PHP 8.3 (или более поздней версии), как только код использует атрибут Override. Это означает, что PHP 8.3 будет проверять правильность использования атрибута, так как другие версии этого не сделают. Иллюстрация выше — это случай валидного кода в PHP 8.2 и не валидного в 8.3.

Сообщение об ошибке Override появляется во всех случаях

Обратите внимание на сообщение об ошибке: Fatal error: y::goo() has #[Override] attribute, but no matching parent method exists. Оно выдаётся в двух случаях: если родитель не существует (иллюстрация выше); также, если родитель существует, а метод — нет. Одно и то же сообщение выдаётся в обоих случаях, хотя это добавляет ещё один шаг к диагностике.

<?php

// Компилируется и запускается в PHP 8.0 - 8.2
// Ошибка во время компиляции в PHP 8.3 и 8.4
class x {

}

class y extends x {
#[Override]
function foo() {}
//Fatal error: y::goo() has #[\Override] attribute, but no matching parent method exists
}

?>

Атрибут Override в интерфейсах

В сообщении об ошибке в случае недопустимого атрибута Override упоминается родитель, а не класс. Это ещё одна менее известная особенность Override: он применим к интерфейсам. На самом деле, интерфейсы тоже могут иметь родителя и расширять его.

<?php

// Компилируется и запускается в PHP 8.0 - 8.2
// Ошибка во время компиляции в PHP 8.3 и 8.4
interface x {

}

interface y extends x {
#[Override]
function foo();
//Fatal error: y::goo() has #[\Override] attribute, but no matching parent method exists
}

?>

Использование переопределения в интерфейсе заставляет интерфейс имеющий родителя, переопределять его метод. Это выглядит несколько чрезмерно, поскольку интерфейсы не имеют тела и не могут изменить поведение родителя.

Тем не менее Override гарантирует, что родительский интерфейс содержит конкретный метод и что он не был удалён без учёта потомков. Кроме того, можно изменить сигнатуру метода, сохранив его совместимость с родительским методом.

Override в перечислениях

У перечислений могут быть методы, но они не могут иметь родителя. PHP не принимает такое использование и сообщает об ошибке. Короче говоря, никакого Override для перечислений.

<?php

// Компилируется и запускается в PHP 8.0 - 8.2
enum y {
#[Override]
function foo() {}
}

?>

Override в трейтах

У трейтов могут быть методы, и не может быть родителя. В этот раз PHP принимает такое использование и откладывает сообщение об ошибке до фактического включения трейта в класс или перечисление.

<?php

trait y {
#[Override]
function foo() {}
}

?>

Когда трейт используется в классе, PHP добавляет методы трейта в метод класса и проверяет Override метода.

Также обратите внимание, что ключевое слово use внутри трейта не генерирует родителя, поэтому переопределение метода в другом трейте не снимает атрибут Override с текущего.

<?php

trait x {
function foo() {}
}

// Это не вариант использования Override: здесь нет родителя.
trait y {
use x;
#[Override]
function foo() {}
}

?>

Override как особенность времени компиляции

В начале статьи упоминалось, что Override — это в основном проверка на этапе компиляции. Так оно и есть, за исключением трейтов и автозагрузки. Проверка на переопределение откладывается до тех пор, пока трейт не будет загружен в класс. Это означает, что Override в трейте может дождаться времени выполнения, чтобы выдать ошибку.

Override мелким шрифтом

В заключение приведу список дополнительных деталей, которые следует учитывать при использовании Override:

Дополнительные материалы

Предыдущая Статья

Как использовать Git submodule

Следующая Статья

Новое в Symfony 7.1