skip to content

In PHP 8.3 and later, what does the #[\Override] attribute check, and which bug does it catch that plain overriding does not?

level: middleimportance: should knowfreq 30%

answer

  1. added in PHP 8.3
  2. parent class or implemented interface
  3. checked when the class is declared
  4. private parent method does not count
  5. properties too since 8.5

basics

~20 s

#[\Override] asserts that a method overrides a parent-class method or implements an interface method. If none exists, PHP fails when the class is declared, which catches overrides silently orphaned by a renamed or removed parent method.

solid answer

~40 s

`#[\Override]`, added in PHP 8.3, marks a method as **intended** to override a method of a parent class or implement one from an interface. When the class is declared, the engine checks that such a method exists; if not, it fails with `X::m() has #[\Override] attribute, but no matching parent method exists`. Without it, renaming or removing the parent method in a library upgrade leaves the child's method in place as a new, never-called method, and the parent's default behaviour runs silently. A parent method that is `private` does not count as a match, and on a trait method the check happens in the class that uses the trait. Since PHP 8.5 the attribute can also be placed on properties.

code

php · 22 lines
php
<?php
declare(strict_types=1);

class Notification
{
    // was format() before the library's 2.0 release
    protected function renderBody(string $text): string
    {
        return $text;
    }
}

final class EmailNotification extends Notification
{
    #[\Override]
    protected function format(string $text): string
    {
        return "<p>{$text}</p>";
    }
}
// Fatal error: EmailNotification::format() has #[\Override] attribute,
// but no matching parent method exists

go deeper

for a junior

Recall that #[\Override] from PHP 8.3 fails the class declaration when no parent or interface method with that name exists.

for a middle

Explain the orphaned-override bug it prevents, and the edge cases: private parent methods and concrete constructors do not match, interface methods do, and properties are covered since 8.5.

for a senior

Decide where the attribute pays off, mostly overrides of third-party classes, and how to roll it out on a codebase that still supports PHP versions before 8.3.

for a principal

Frame it as a policy question: making override intent explicit across teams, and how that interacts with upgrading shared base libraries.

## The silent-orphan bug PHP needs no keyword to override: a child method with the same name as a parent method simply replaces it. That convenience has a failure mode. Suppose a library's `Notification::format()` is renamed to `renderBody()` in a new major version. Every subclass that overrode `format()` still compiles, because a child is allowed to declare new methods. But nothing calls `format()` any more, so the parent's default `renderBody()` runs, and emails go out unformatted. No error, no warning: the override has been **orphaned**. The same thing happens with a typo in the method name, or when a parent method is deleted. ## What #[\Override] checks `#[\Override]` is a built-in attribute, added in **PHP 8.3**, that turns intent into a check. On a method it asserts: a method with this name exists in a parent class, or in an interface this class implements. The check runs when the class is declared (linked), not when the method is called, and a failure is a fatal compile error: `EmailNotification::format() has #[\Override] attribute, but no matching parent method exists` The attribute does **not** replace the ordinary signature rules; those run for every override anyway. It only adds the existence check. ## What counts as a match The engine's own test suite pins the edge cases: | Situation | Result | |---|---| | parent class has a `public` or `protected` method of that name | passes | | an implemented interface declares the method | passes | | a static method overriding a parent static method | passes | | parent method of that name is `private` | error: private methods are not inherited for overriding | | no parent class and no matching interface | error | | `__construct()` over a concrete parent constructor | error: constructors are exempt from override checks | | `__construct()` over an `abstract` parent constructor | passes | | on a trait method | checked in each class that uses the trait | Two rules are easy to miss: 1. The check is made **per declaring class**. If class `P` marks `i()` with `#[\Override]` and only its subclass `C` implements the interface that declares `i()`, `P` still fails. 2. A trait method marked `#[\Override]` is not checked on the trait itself. Each class using the trait must have a matching parent or interface method, or it fails. ## Properties since PHP 8.5 PHP 8.5 extended the attribute to **properties**. The attribute class now targets both methods and properties, and a property marked `#[\Override]` with no parent property of the same name fails with `X::$name has #[\Override] attribute, but no matching parent property exists`. ## Why a declaration-time check matters Because the check runs when the class is linked, the failure appears the moment the class is autoloaded: in the first test that touches it, in a smoke run after `composer update`, or when the application boots. That is much earlier than the orphaned-override bug itself, which only shows up as wrong output in whatever path relied on the child's behaviour. The attribute effectively converts a silent behavioural regression into a loud load-time error. ## Adoption notes - On a codebase that must also run on PHP 8.0 to 8.2, the attribute names a class that does not exist there. Attributes are not validated unless code reflects on them and instantiates them, so it is harmless on those versions but checks nothing. - The attribute is most valuable on overrides of **third-party** classes, where the parent can change without your review. - Adding it to every override is a team convention, not an engine rule; PHP never complains when it is missing. - It also documents intent for readers: a method carrying `#[\Override]` is part of a contract defined elsewhere, so renaming it on its own breaks the override. - When the error fires after an upgrade, read the parent's changelog for the new method name, move the body there and keep the attribute.

  • Why does #[\Override] on a method whose parent counterpart is private fail?
    A private method is not inherited for overriding. The child's same-named method is a separate method that the parent's code never calls, which is exactly the orphaned situation the attribute exists to detect, so PHP reports that no matching parent method exists.
  • Does #[\Override] do anything on PHP 8.2?
    It checks nothing there. PHP 8.2 has no built-in `Override` class, and attributes are only resolved when code reflects on them and instantiates them, so the attribute sits unused. The existence check starts with PHP 8.3.

saying these in an interview costs you the question

  • #[\Override] is checked lazily, the first time the method is called
  • #[\Override] makes PHP skip the normal signature compatibility checks
  • Overriding a private parent method satisfies #[\Override]
  • PHP warns about every override that lacks #[\Override]
  • #[\Override] only matches parent classes, never interface methods