These are the tools for designing swappable components, which is how frameworks and testable code are built.
An interface lists methods a class promises to provide, with no implementation. A class can implement many: class FileCache implements Cache, Countable. Code that type-hints the interface works with any implementation, which is the basis of dependency injection and test doubles.
An abstract class can mix implemented methods with abstract ones that subclasses must fill in. A class extends only one parent, so use abstract classes for a shared skeleton (template method) and interfaces for capabilities.
A trait is a bundle of methods (and properties) copied into a class with use. It is PHP's answer to the lack of multiple inheritance:
trait HasTimestamps {
public function touch(): void { $this->updatedAt = new DateTimeImmutable(); }
}Traits are handy for small, orthogonal behaviour, but heavy trait use hides where methods come from. Prefer composition (an object holding another object) for anything with state or dependencies. Name clashes between traits are resolved with insteadof and as.
Built-in interfaces worth knowing: Countable (makes count($obj) work), IteratorAggregate (makes foreach work), ArrayAccess, JsonSerializable and Stringable.
Use instanceof to check a type at runtime, and mark overriding methods with the #[\Override] attribute (PHP 8.3) so the engine errors if the parent method disappears.
Example
<?php
interface Shape {
public function area(): float;
}
abstract class Named implements Shape {
abstract protected function label(): string;
public function describe(): string {
return sprintf('%s with area %.1f', $this->label(), $this->area());
}
}
trait Scales {
public function scaled(float $f): static { $c = clone $this; $c->size *= $f; return $c; }
}
class Square extends Named {
use Scales;
public function __construct(public float $size) {}
public function area(): float { return $this->size ** 2; }
protected function label(): string { return 'Square'; }
}
$s = new Square(2);
echo $s->describe(), "\n"; // Square with area 4.0
echo $s->scaled(3)->describe(), "\n"; // Square with area 36.0
var_dump($s instanceof Shape); // bool(true)Remember
- Interfaces define contracts; type-hint against them
- Abstract classes share a skeleton; a class has only one parent
- Traits copy reusable methods into classes; don't overuse them
Watch out for
- Putting stateful, dependency-heavy logic in traits instead of separate collaborating objects.