In PHP, how do insteadof and as resolve two traits that define the same method, and what else can the as operator change?
answer
- has not been applied ... because of collision
- A::m insteadof B picks one
- as adds a name, never renames
- as protected / as private alias
- as final since 8.3
basics
~20 sTwo traits defining the same concrete method make PHP fail until insteadof picks one. The as operator adds an alias so the excluded body stays callable, and can also change visibility or, since 8.3, make the method final.
solid answer
~40 sWhen `Timestamps` and `Auditable` both define `touch()` and a class uses both, the declaration fails with `Trait method Auditable::touch has not been applied as Article::touch, because of collision with Timestamps::touch`. A conflict block fixes it: `Timestamps::touch insteadof Auditable;` chooses which body becomes `touch()`, and `Auditable::touch as auditTouch;` keeps the other body reachable under a **new additional name**. `as` never renames: the original name still exists unless `insteadof` excluded it. `as` also adjusts **visibility**, either in place (`touch as protected;`) or on an alias only (`touch as private innerTouch;`), and since PHP 8.3 `as final` stops subclasses from overriding the imported method. A method the class declares itself wins over both traits, so no conflict block is needed then. Abstract trait methods never collide with a concrete one.
code
php · 33 lines<?php
declare(strict_types=1);
trait Timestamps
{
public function touch(): string
{
return 'updatedAt set';
}
}
trait Auditable
{
public function touch(): string
{
return 'audit line written';
}
}
final class Article
{
use Timestamps, Auditable {
Timestamps::touch insteadof Auditable;
Auditable::touch as private audit;
}
public function publish(): string
{
return $this->touch() . ', ' . $this->audit();
}
}
echo (new Article())->publish(); // updatedAt set, audit line writtengo deeper
Recall that two traits with the same method cause a fatal error, insteadof picks one, and as adds an alias.
Explain that as adds a name rather than renaming, how visibility changes apply per class, as final from PHP 8.3, and which cases never conflict.
Recognise repeated conflict blocks as a design smell, and prefer explicit class methods that call aliased trait bodies so the order of effects is visible.
Decide naming conventions for shared traits so that conflict resolution stays rare across teams that compose them independently.
## When a conflict happens Traits are composed into a class member by member. If two traits used by the same class insert a **concrete** method with the same name, the engine refuses to guess: `Trait method Auditable::touch has not been applied as Article::touch, because of collision with Timestamps::touch` Three situations do **not** conflict: - The class **declares the method itself**: class members override trait methods, so both trait versions are skipped. - One of the colliding methods is **abstract**: an abstract trait method is a requirement, and a concrete method satisfies it. - The **same** trait method arrives twice with the same visibility, for example through two traits that both use a third one: the engine treats it as one definition. ## insteadof: choose one body A conflict block follows the `use` statement: ```php use Timestamps, Auditable { Timestamps::touch insteadof Auditable; } ``` `insteadof` means: for this method name, use `Timestamps`' body and **exclude** `Auditable`'s. It only excludes; it does not make the excluded body available elsewhere. Naming a class that is not a trait in the block fails with `Only traits may be used in 'as' and 'insteadof' statements`. ## as: add a name To keep the excluded body callable, add an alias: ```php use Timestamps, Auditable { Timestamps::touch insteadof Auditable; Auditable::touch as auditTouch; } ``` Now `touch()` runs `Timestamps`' body and `auditTouch()` runs `Auditable`'s. The manual stresses that `as` **does not rename** a method: it adds another name for it. Without `insteadof`, aliasing one side alone does not resolve the conflict, because both originals still try to become `touch()`. Aliasing mistakes have their own errors: 1. Aliasing a method that no used trait has: `An alias was defined for T::m but this method does not exist`. 2. Aliasing an unqualified name that exists in two traits: `An alias was defined for method touch(), which exists in both Timestamps and Auditable. Use Timestamps::touch or Auditable::touch to resolve the ambiguity`. ## as: change visibility or finality | Clause | Effect | |---|---| | `touch as protected;` | the method keeps its name and becomes `protected` in this class | | `touch as private innerTouch;` | adds `innerTouch()` as a `private` alias; `touch()` keeps its visibility | | `touch as final;` (PHP 8.3+) | subclasses of this class cannot override `touch()` | | `Auditable::touch as auditTouch;` | adds a second public name for `Auditable`'s body | Visibility changes are per using class: another class using the same trait sees the trait's original visibility. With `as final`, only **child classes** are blocked; the using class could still have declared its own method. ## A CMS example In a CMS, `Timestamps` updates `updatedAt` on `touch()`, and `Auditable` writes an audit line on `touch()`. An `Article` wants both: 1. `Timestamps::touch insteadof Auditable;` makes the timestamp version the public `touch()`. 2. `Auditable::touch as private audit;` keeps the audit body as a private helper. 3. The class's own `touch()`, if it declares one, can call both steps; declaring it would even make step 1 unnecessary, since class methods win. ## Reading the collision error The message names everything needed to fix it: `Trait method Auditable::touch has not been applied as Article::touch, because of collision with Timestamps::touch`. 1. The **class** being composed is `Article`. 2. The **method name** in dispute is `touch`. 3. The two **traits** involved are `Auditable` and `Timestamps`. Decide which body should answer to `touch()`, write that choice as an `insteadof` clause, and add an `as` alias for the other body only if some code still needs it. If neither body should be the class's `touch()`, declare `touch()` in the class and call aliased trait bodies from it. ## Guidance - A conflict block is a sign two traits compete for one name. If it happens repeatedly, rename one trait's method instead of resolving it in every class. - Prefer a class-level method that calls aliased trait bodies explicitly; readers see the order of operations in one place.
- Why does Auditable::touch as auditTouch; alone not fix the collision?`as` adds a name without removing the original. Both traits still try to insert a method called `touch()`, so the collision remains. `insteadof` is what excludes one side from the `touch` name; the alias only makes the excluded body reachable.
- Does touch as protected; change the method for other classes that use the same trait?No. Conflict and visibility clauses apply to the class whose `use` block contains them. Another class using `Timestamps` gets `touch()` with the visibility the trait declared, unless its own `use` block changes it.
saying these in an interview costs you the question
- as renames a trait method, so the original name disappears
- PHP silently keeps the first listed trait's method on a collision
- insteadof also makes the excluded method callable under its old name
- An alias with as private hides the original method too
- A class must use insteadof even when it declares the method itself