Mixins via Traits — composition over inheritance
Concept
Mixins via Traits are the PHP pattern for horizontal composition — sharing behavior across classes that have no inheritance relationship. The phrase "composition over inheritance" is directly embodied by traits: instead of extending a base class to gain behavior, you mix in a trait.
The inheritance problem traits solve: If User, Product, and Order all need audit logging, you can't make them all extend AuditableBase — they likely already extend different parents (Eloquent Model, etc.). A trait HasAuditLog can be used by all three without touching their inheritance chains.
Interface + Trait combination: The idiomatic Laravel/PHP pattern: define an interface for the contract, a trait for the default implementation. The class implements the interface (for type-checking) and uses the trait (for the implementation). The trait can reference abstract methods it expects the using class to provide.
Trait limitations vs full inheritance: Traits cannot define their own constants (PHP < 8.2). They don't participate in instanceof checks (a class using HasTimestamps is not "instanceof HasTimestamps"). They create implicit coupling — a trait that calls $this->id assumes the class has an id property, but the compiler doesn't verify this. This is why abstract requirements in traits are valuable.
Eloquent's use of traits: SoftDeletes, HasTimestamps, Notifiable, HasFactory, Searchable, HasApiTokens — all implemented as traits mixed into models. This lets models opt into features without inheritance coupling.
Code Example
<?php
declare(strict_types=1);
// Interface contract
interface Versionable
{
public function getVersion(): int;
public function incrementVersion(): void;
}
// Trait provides default implementation
trait HasVersioning
{
private int $version = 1;
public function getVersion(): int
{
return $this->version;
}
public function incrementVersion(): void
{
$this->version++;
}
public function resetVersion(): void
{
$this->version = 1;
}
}
// Document and Product both "have" versioning — no inheritance needed
class Document implements Versionable
{
use HasVersioning;
public function __construct(public string $title) {}
}
class Product implements Versionable
{
use HasVersioning;
public function __construct(public string $name, public float $price) {}
}
$doc = new Document('PHP Guide');
$doc->incrementVersion();
$doc->incrementVersion();
echo $doc->getVersion(); // 3
// Trait with abstract method requirement
trait HasSlug
{
abstract public function getSlugSource(): string; // implementing class must have this
private ?string $slug = null;
public function getSlug(): string
{
if ($this->slug === null) {
$source = $this->getSlugSource(); // uses abstract from host class
$this->slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $source));
}
return $this->slug;
}
}
class BlogPost
{
use HasSlug;
public function __construct(private string $title) {}
public function getSlugSource(): string { return $this->title; }
}
$post = new BlogPost('Hello World! PHP 8.4');
echo $post->getSlug(); // "hello-world-php-8-4"