Interfaces and Traits: Contracts, Abstract Classes, and Code Reuse

Understand interfaces as pure contracts a class promises to fulfill, how a class can implement multiple interfaces at once, how abstract classes sit between interfaces and concrete classes, and how traits share method implementations across unrelated classes — including how to resolve conflicts between traits with insteadof and as, and how to declare abstract methods inside a trait.

PHP gives you three distinct tools for describing 'what a class can do' and sharing behavior across classes that aren't related by inheritance: interfaces, abstract classes, and traits. They solve different problems and are easy to blur together if you've only seen one of them. An interface says *what* a class must be able to do, with zero implementation. An abstract class is a partial blueprint — some shared implementation, some methods left for subclasses to fill in — combined with PHP's single-inheritance rule. A trait is pure, literal code reuse: a bundle of methods you can 'paste into' multiple unrelated classes, sidestepping the single-inheritance limit entirely. This lesson covers all three, and the trickiest part of traits: what happens when two traits used by the same class define a method with the same name.

Interfaces: pure contracts

An interface declares a set of public method signatures — names, parameters, return types — with no implementation at all. A class that implements an interface promises to provide real bodies for every method the interface lists; PHP enforces this at class-definition time, refusing to let a class implement an interface it doesn't fully satisfy.

Interfaces exist to decouple *what code expects* from *which concrete class actually provides it*. A function that accepts a Comparable interface parameter doesn't care whether it's handed a Money object or a Version object — it only cares that whatever it receives has a compareTo() method, because the interface guarantees that. This is what lets you swap implementations (a real database repository for an in-memory test double, say) without touching the code that consumes them.

Interfaces can also declare const values (shared, fixed constants every implementing class inherits) and can extends other interfaces (an interface extending another adds its methods to the contract) — but they cannot declare properties or any method body.

Implementing multiple interfaces

Unlike class inheritance, which is limited to one parent, a class can implements any number of interfaces at once, comma-separated: class Invoice implements JsonSerializable, Countable, Stringable {}. Each interface is a separate, independent promise, and the class must provide real implementations for every method every listed interface requires.

This is how PHP gets multiple-inheritance-like flexibility for *contracts* (though not for shared implementation) without the ambiguity problems that multiple class inheritance causes: since interfaces carry no method bodies, there's never a question of 'which parent's version of this method wins' — the implementing class always supplies exactly one, its own.

Abstract classes: partial blueprints

An abstract class sits between a plain class and an interface: it can hold real properties, fully implemented methods (shared code every subclass inherits as-is), *and* abstract methods with no body that every concrete subclass must implement. An abstract class can never be instantiated directly — new AbstractShape() is a compile-time error — it exists purely to be extended.

Choose an abstract class over an interface when subclasses genuinely share implementation, not just a contract. A PaymentGateway abstract class might implement shared logic like logging and retry handling once, while leaving an abstract charge(float $amount): bool method for each concrete gateway (StripeGateway, PaypalGateway) to implement its own way — avoiding both duplicated retry logic *and* an interface that would force every gateway to reimplement identical boilerplate.

A class can extend at most one abstract class (the single-inheritance rule still applies) but can additionally implement any number of interfaces — the two are not mutually exclusive and are commonly combined.

Traits: sharing implementation across unrelated classes

A trait is a named bundle of methods (and optionally properties) declared with the trait keyword, meant to be mixed into classes with use TraitName; inside the class body. Unlike inheritance, using a trait is not an IS-A relationship and doesn't count against the single-inheritance limit — a class can use as many traits as it wants, and traits themselves impose no hierarchy.

Conceptually, use TraitName; behaves like PHP copy-pasting the trait's methods directly into the class at compile time. This makes traits the right tool when several *unrelated* classes need the exact same behavior — a Loggable trait giving any class a log() method, a Cacheable trait giving any class simple get/set caching — where an inheritance relationship between those classes wouldn't make conceptual sense (a User and an Order aren't related by IS-A, but both might want logging).

A class can override a method it got from a trait simply by declaring its own method of the same name — the class's own definition always wins over anything pulled in from a trait.

Resolving trait conflicts: insteadof and as

The situation traits can't avoid: if a class uses two traits that both declare a method with the same name, PHP cannot silently pick one — that's a fatal conflict, and the class definition fails to compile unless you resolve it explicitly. PHP's resolution syntax is a use block with insteadof and as.

TraitA::method insteadof TraitB; tells PHP: when both TraitA and TraitB define method, use TraitA's version for this class, and discard TraitB's. This resolves the conflict but you lose access to TraitB's version entirely — unless you also alias it.

TraitB::method as methodFromB; creates an additional, differently-named entry point to TraitB's original version, so the class can still call both versions under different names even after insteadof decided which one plain method() refers to. as can also be used on its own (without insteadof) just to change a trait method's *visibility* in the using class, e.g. TraitA::method as protected;.

Abstract methods inside a trait

A trait can declare an abstract method — a signature with no body — exactly like an abstract class can. This does not make the trait itself instantiable-preventing in the same sense as an abstract class (traits are never instantiated at all, used only), but it does enforce a requirement: any class that uses the trait must supply its own implementation of that abstract method, or PHP raises a fatal error at class-definition time.

This pattern lets a trait provide shared logic that depends on a piece of information the trait itself can't know — the trait supplies the general algorithm, and declares an abstract method for the one class-specific piece it needs the using class to fill in, similar in spirit to the Template Method design pattern but achieved through composition (use) instead of inheritance.

Interfaces, an abstract class, and a trait conflict resolution

Transaction::log() now runs Loggable's version; Transaction::auditLog() runs Auditable's original version. PaymentGateway::process() is shared, concrete logic; charge() is the one piece each subclass must supply.

interface Payable
{
    public function amountDue(): float;
}

abstract class PaymentGateway
{
    public function process(Payable $item): bool
    {
        // shared logic every gateway gets for free
        return $this->charge($item->amountDue());
    }

    abstract protected function charge(float $amount): bool;
}

trait Loggable
{
    public function log(string $msg): void { echo "[LOG] $msg\n"; }
}

trait Auditable
{
    public function log(string $msg): void { echo "[AUDIT] $msg\n"; }
}

class Transaction
{
    use Loggable, Auditable {
        Loggable::log insteadof Auditable;  // resolve the name clash
        Auditable::log as auditLog;          // keep Auditable's version too, renamed
    }
}

Code Examples

StripeGateway extends one abstract class (getting process() for free) while implementing one interface (Refundable, adding refund()). Invoice implements Payable so it can be passed anywhere a Payable is expected — including into process(), which only knows about the Payable contract, not the concrete Invoice class. Expected result: Charging $42.5 via Stripe Refunding $10 via Stripe class StripeGateway extends PaymentGateway implements Refundable

Both Greets and Farewells declare announce(), which is a fatal conflict on its own. Greets::announce insteadof Farewells; says Greets wins for the plain announce() call; Farewells::announce as sayFarewell; keeps Farewells' version reachable under a new name instead of losing it. Separately, Farewells declares abstract public function name(): string; — Receptionist is required to implement name() itself, which is what lets farewellFor() (defined once, inside the trait) work no matter which class eventually uses Farewells. Expected result: Greets: Hello! Farewells: Goodbye! Jun says goodbye.

Greets::announce insteadof Farewells;

Common mistake: Omitting the insteadof/as conflict block entirely when two used traits share a method name — PHP raises 'Fatal error: Trait method announce has not been applied, because there are collisions with other trait methods' at class-definition time.

interface Payable { public function amountDue(): float; }
interface Refundable { public function refund(float $amount): bool; }

abstract class PaymentGateway
{
    public function process(Payable $item): bool
    {
        return $this->charge($item->amountDue());
    }

    abstract protected function charge(float $amount): bool;
}

class StripeGateway extends PaymentGateway implements Refundable
{
    protected function charge(float $amount): bool
    {
        echo "Charging \${$amount} via Stripe\n";
        return true;
    }

    public function refund(float $amount): bool
    {
        echo "Refunding \${$amount} via Stripe\n";
        return true;
    }
}

class Invoice implements Payable
{
    public function __construct(private float $total) {}
    public function amountDue(): float { return $this->total; }
}

$gateway = new StripeGateway();
$gateway->process(new Invoice(42.50));
$gateway->refund(10.00);
trait Greets
{
    public function announce(): string { return 'Greets: Hello!'; }
}

trait Farewells
{
    public function announce(): string { return 'Farewells: Goodbye!'; }

    // Abstract trait method: any using class must supply this.
    abstract public function name(): string;

    public function farewellFor(): string
    {
        return $this->name() . ' says goodbye.';
    }
}

class Receptionist
{
    use Greets, Farewells {
        Greets::announce insteadof Farewells;   // conflict: both define announce()
        Farewells::announce as sayFarewell;      // keep Farewells' version under a new name
    }

    public function __construct(private string $name) {}

    // Required because Farewells declares an abstract name()
    public function name(): string
    {
        return $this->name;
    }
}

$r = new Receptionist('Jun');
echo $r->announce(), PHP_EOL;      // Greets' version won the conflict
echo $r->sayFarewell(), PHP_EOL;   // Farewells' version, reachable under its alias
echo $r->farewellFor(), PHP_EOL;   // uses $this->name(), fulfilled by Receptionist

Interface vs abstract class vs trait

Interfaces: many, zero implementation. Abstract classes: one, partial implementation. Traits: many, pasted-in implementation.

interface Payable          abstract class PaymentGateway     trait Loggable
  +--------------------+     +---------------------------+    +-----------------+
  | amountDue(): float |     | process() { concrete }    |    | log() { concrete}|
  |  (no body, ever)   |     | charge()  { abstract }    |    | (pasted into any |
  +--------------------+     +---------------------------+    |  using class)    |
         ^ implements                 ^ extends (only 1)      +-----------------+
         | (any number)               |                              ^ use (any number)
         |                             |                              |
         +------------- class StripeGateway ---------------------------+
                          (implements Payable indirectly via Invoice,
                           extends PaymentGateway, could use Loggable too)

Important Rules

Use an interface when you only need to guarantee 'this class can do X', with no shared implementation.

Use an abstract class when subclasses share real implementation, not just a signature, and single inheritance is acceptable.

Use a trait when unrelated classes need the same behavior and an IS-A inheritance relationship wouldn't make sense.

A class can implement any number of interfaces, but extend only one class (abstract or not).

When two used traits define the same method name, you must resolve the conflict explicitly with insteadof (and usually as) or the class fails to compile.

An abstract method declared inside a trait must be implemented by whatever class ends up using that trait.

Why this matters in real codebases

Real PHP frameworks lean on all three constructs at once, often in the same class: a Laravel Eloquent model implements framework interfaces like Arrayable and Jsonable (pure contracts other code can rely on), extends a base Model class (shared persistence implementation), and commonly uses traits like SoftDeletes or HasFactory to mix in optional, reusable behavior without forcing every model into a deeper single-inheritance chain. Knowing which tool to reach for — contract, shared base, or mixed-in behavior — is what keeps a framework's own codebase (and the application code built on it) composable instead of tangled.

class Receptionist { use Greets, Farewells; // both define announce() -- no insteadof/as } class Receptionist { use Greets, Farewells { Greets::announce insteadof Farewells; Farewells::announce as sayFarewell; } }

Using two traits that both declare announce() without a conflict-resolution block is a fatal error at class-definition time ('has not been applied, because there are collisions'); PHP will not guess which trait's method you meant.

interface Shape { public function area(): float { return 0; } } interface Shape { public function area(): float; }

Interface methods can never have a body — an interface is a pure contract. Writing { return 0; } inside an interface method is a syntax error; a default/shared implementation belongs in an abstract class or a trait instead.

[multiple-choice] How many interfaces can a single PHP class implement, and how many classes can it extend? A. Any number of interfaces; any number of classes B. Exactly one interface; exactly one class C. Any number of interfaces; exactly one class D. Exactly one interface; any number of classes

C. Any number of interfaces; exactly one class — A class may implements as many interfaces as needed (comma-separated), but PHP restricts class inheritance to a single parent via extends — traits are the mechanism for reusing implementation from more than one source.

[multiple-choice] Two traits used by the same class both declare a method named format(). What happens if you write use TraitA, TraitB; with no conflict-resolution block? A. PHP silently uses TraitA's version because it was listed first B. PHP silently uses TraitB's version because it was listed last C. PHP raises a fatal error at class-definition time because of the collision D. PHP merges both method bodies into one

C. PHP raises a fatal error at class-definition time because of the collision — PHP never silently picks a winner between conflicting trait methods — it raises a fatal 'has not been applied, because there are collisions' error unless the class explicitly resolves the conflict with an insteadof (and usually as) block.

[predict-output] trait A { public function who(): string { return 'A'; } } trait B { public function who(): string { return 'B'; } } class C { use A, B { A::who insteadof B; B::who as whoB; } } $c = new C(); echo $c->who() . $c->whoB(); A. AA B. BB C. AB D. BA

C. AB — A::who insteadof B; makes who() resolve to A's version, returning 'A'. B::who as whoB; keeps B's original version reachable under the name whoB(), returning 'B'. Concatenated: 'A' . 'B' = 'AB'.

Define an interface Speaks with method speak(): string. Define two traits, Bark (a speak() returning 'Woof!') and Meow (a speak() returning 'Meow!'). Write a class RoboPet implements Speaks that uses both traits, resolves the speak() conflict so Bark's version is the default speak(), and keeps Meow's version accessible as meowSpeak().

interface Speaks { public function speak(): string; } trait Bark { public function speak(): string { return 'Woof!'; } } trait Meow { public function speak(): string { return 'Meow!'; } } class RoboPet implements Speaks { use Bark, Meow { Bark::speak insteadof Meow; Meow::speak as meowSpeak; } }

The use block needs Bark::speak insteadof Meow; followed by Meow::speak as meowSpeak;. Because RoboPet implements Speaks, it still needs a real speak() method available — which insteadof provides by keeping Bark's version active.