More about the Override AttributeA few extra steps with PHP’s Override attribute

The Override attribute was introduced in PHP 8.3. It is a new attribute, that marks methods that must be overriding a parent’s class method: one of the parent must define a method with the same name. When this is the case, all is fine. If no parent method is overwritten, then PHP emits a fatal error at compilation.

<?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
}

?>

In this article, we’ll review a number of cases that diverge from the main definition of the Override attributes. This will help deal with seemingly unexpected behaviors, that are just normal PHP behaviors.

Override attribute is free until PHP 8.2

The override attribute is supported in the PHP 8.3 engine. That means PHP ignores it altogether in PHP 8.0 to 8.2. Attributes are completely ignored in PHP 7.4 and less. This is the expected behavior.

If a PHP doesn’t use Override, static analysis tools may take advantage of it. Although Exakat is currently the only one that has been found to use of it with older PHP versions.

Anticipating Override Attribute for PHP 8.3

When the code is anticipating the move to PHP 8.3 and its subsequent usage of Override, it is possible to use the attribute in older PHP version, and let it activate with the new PHP 8.3 version.

It is made easy by the nature of Override : it is mainly a compile-time check. This means that PHP won’t compile with code that doesn’t comply with the Override features.

<?php

// Compile and run in PHP 8.0 to 8.2
// Fails at compile time in PHP 8.3 and 8.4
class x {
   #[Override]
    function foo() {}
    //Fatal error: y::goo() has #[\Override] attribute, but no matching parent method exists
}

?>

Hence, it is recommended to add a compile check with PHP 8.3 (or more) as soon as the code uses the Override attribute. This means relying on PHP 8.3 to check if the attribute is well used, as the othe version won’t do it. The illustration above is a case of valid code in PHP 8.2 and not in 8.3.

Override error message works in every cases

Note the error message : Fatal error: y::goo() has #[\Override] attribute, but no matching parent method exists. This works in two cases : when the parent doesn’t exist (illustrated above); it also works when the parent exists and not the method. The same message works in both cases, although it makes the diagnostic one step furthere away.

<?php

// Compile and run in PHP 8.0 to 8.2
// Fails at compile time in PHP 8.3 and 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 attribute for interfaces

The error message in case of unsatisfied Override attribute mentions parent, not class. This is also another less known feature of the Override: it applies to the interfaces. In fact, interfaces may also have a parent, and extends it.

<?php

// Compile and run in PHP 8.0 to 8.2
// Fails at compile time in PHP 8.3 and 8.4
interface x {

}

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

?>

Using an override in an interface forces the interface to have a parent, and override its method. This looks a bit excessive, since interfaces do not have any body and cannot change the parent’s behavior.

Yet, Override ensures that the parent interface contains a specific method, and that it was not removed without regards for the children. Besides, it is possible to alter the method’s signature, while keeping it compatible with the parent method.

Override for enum

Enumerations may have methods, and they cannot have a parent. PHP doesn’t accept this usage, and reports the error. In short, no Override for enums.

<?php

// Compile and run in PHP 8.0 to 8.2
enum y {
   #[Override]
    function foo() {}
}

?>

Override for traits

Traits may have methods, and they cannot have a parent. This time, PHP does accept this usage, and postpone any error reporting until the actual inclusion of the trait in a class or enum.

<?php

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

?>

When the trait is used in a class, PHP adds the trait’s methods to the class method, and checks the Override method.

Also, note that use keyword inside a trait doesn’t generate a parent, so overriding a method in another trait with the current doesn’t remove the Override attribute.

<?php

trait x {
    function foo() {}
}

// This is not a use case of Override : there is no parent
trait y {
    use x;
   #[Override]
    function foo() {}
}

?>

Override as a compile time feature

We started the article by mentionning that Override is mostly a compile time check. This is the case, with the exception of traits, and autoload. The check for Override is postponed until the trait is actually loaded into a class. This means that Override in a trait may wait until execution time to produce an error.

Override small print

In the end, here is a list of extra details to keep in mind when using Override :

  • When running on PHP 8.2 or older, add a compile check of the code with PHP 8.3 or your favorite static analysis to prepare for PHP 8.3 migration
  • Override works on interfaces
  • Override cannot be used in enumerations
  • Override is silent in traits, until actual usage, at execution time