PHP Method Resolution Order: Class · Trait · Parent · Interface
PHP offers three different places to define a method: directly in a class, in a trait the class uses, and in a parent class it extends. Interfaces define contracts, the method signatures without a body, so they don’t contribute an implementation to the chain, though they handle other aspects. When more than one layer defines the same method name, PHP needs a clear rule for which one wins. That rule is the method resolution order, also know as MRO.
Understanding it removes an entire class of subtle bugs: the ones where you override a method and nothing seems to change, or where adding a trait silently overwrites behaviour you meant to keep from the parent.
The priority stack
Think of it as a stack of layers. PHP looks from top to bottom and uses the first definition it finds. The class sits at the top, with the highest priority, and the parent class sits at the bottom. Interfaces are not part of the resolution chain; they only enforce that a method exists somewhere.
| Priority | Layer | Description |
|---|---|---|
| 1 — highest | Class — own method | Always wins. Defined directly in the class body. |
| 2 | Trait method | Used when the class has no own definition. PHP copies it in at compile time. |
| 3 — lowest | Parent class method | Inherited via extends. Overridden by any layer above. |
Each layer overrides everything below it. Remove a layer and PHP drops down to the next one automatically.
A concrete example
Here is a minimal setup with all three layers defining the same method greet(), plus an interface that enforces the contract:
<?php
interface Greeter {
// Pure contract
public function greet(): string;
}
class BaseGreeter implements Greeter {
public function greet(): string {
return 'Hello from parent class';
}
}
trait FriendlyGreeter {
public function greet(): string {
return 'Hello from trait';
}
}
class App extends BaseGreeter {
use FriendlyGreeter;
public function greet(): string {
return 'Hello from App itself';
}
}
echo (new App)->greet();
// "Hello from App itself"
?>
Remove the own method from App and the trait takes over. Remove the trait and the parent shows up. The interface only guarantees the method must exist: it never provides an implementation, only a signature.
The rules that catch people out
Trait beats parent
PHP copies trait methods directly into the class body at compile time, so they are treated as if you wrote them there. A parent’s method is merely inherited and therefore lower in the hierarchy.
Class beats trait
An explicit definition in the class itself always takes precedence over anything brought in via use. The trait is a default the class can override without touching the trait source. There is no way to override this: trait naming resolution operator allows aliasing a method, though.
Conflicting traits are a fatal error
When two use‘d traits define the same method, PHP throws a fatal error at class definition time. You must resolve it explicitly with insteadof or alias one with as.
Interface are a contract, never an implementation
PHP interfaces only declare method signatures. If no layer in the class hierarchy provides an implementation, you get a fatal error at instantiation time. The interface enforces the shape; the class, or the trait, or the parent, anyone really, must provide the substance.
Resolving a trait conflict
When two traits collide, use insteadof to choose a winner and optionally as to keep both under different names:
<?php
trait A {
public function hello(): string { return 'A'; }
}
trait B {
public function hello(): string { return 'B'; }
}
class MyClass {
use A, B {
A::hello insteadof B; // A wins
B::hello as helloB; // B is still accessible
}
}
?>
Calling up the chain
Between classes, you can call the method from the next layer down using parent::. This is useful when you want to extend behaviour rather than replace it entirely:
<?php
class App extends BaseGreeter {
public function greet(): string {
return parent::greet() . ', and from App too';
}
}
?>
This only works on extended classes, and it will reach the first defined method, from a trait or not. This means that some classes may be skipped, if they are not providing a definition for the needed method. Ultimately, it yields an ‘undefined error’.
<?php
class AppGrandParent extends AppGreatGrandParent {
// This is not easily reachable
public function greet(): string {
return 'from AppGreatParent';
}
public function greet2(): string {
return 'from AppGreatParent';
}
}
class AppParent extends AppGrandParent {
public function greet(): string {
return 'from AppParent';
}
// No function greet2()
}
class App extends AppParent {
public function greet(): string {
return parent::greet(); // calls AppParent::greet()
}
public function greet2(): string {
return parent::greet(); // calls AppGreatParent::greet()
}
}
?>
Calling the trait on the side
If calling the parent requires using the parent keyword, there is no such keyword for the overridden trait. When an eponynous method is defined, the trait’s version is just not reachable. For that, the only solution is to alias the trait’s method, and access it from the class.
<?php
class App extends BaseGreeter {
use t {
t::greet as tGreet;
}
public function greet(): string {
return $this->tGreet() . ', and from App too';
}
}
?>
Side note: enums
Enums follow exactly the same rules as classes for traits and interfaces: they can use traits and implement interfaces, and the same resolution order applies. The one difference: enums just cannot extend a parent class. There is no parent layer for an enum, so the chain is shorter:
own method > trait
The mental model
When you call a method on an object, imagine PHP walking down the stack from the class toward the parent, stopping as soon as it finds a definition. The class is the most specific context: it knows the most about what this particular type should do. The parent is the most general implementation: it provides the baseline behaviour. The interface sits outside this chain entirely; it only enforces that the method exists somewhere. Priority follows specificity.
Keep that picture in mind and the resolution order stops being something to memorise and becomes something you can reason about from first principles.
There are some attempts at building a grandparent operator, though, it requires some courage.

