Mixin — code reuse without a class hierarchy (Traits in PHP)
Concept
Mixin — a unit of code that can be "mixed into" a class to add functionality, without forming a class hierarchy. It's code reuse without "is-a".
In PHP: Traits ARE mixins. The term "mixin" comes from Lisp and Smalltalk. In PHP, the mechanism is called a Trait, but the concept is a mixin.
What makes a mixin different from a base class:
- No inheritance chain — the trait doesn't become a parent class.
- A class can use multiple traits (unlike single-inheritance base classes).
- The trait's methods become the class's own methods — no method resolution chain penalty.
- No
instanceofcheck — a trait is not a type.
What a trait/mixin provides:
- Concrete method implementations.
- Properties.
- Abstract method requirements (forces the using class to implement certain methods).
- Constants (PHP 8.2+).
Mixin composition: A class can compose multiple mixins together. use Timestampable, SoftDeletes, HasUuid; — three behaviors mixed in, zero inheritance.
Mixin vs interface: An interface defines a CONTRACT (what you must do). A mixin provides an IMPLEMENTATION (how to do something). They're complementary — implement the interface, use the mixin for the default implementation.
When to use traits as mixins:
- Behavior that many unrelated classes need.
- Laravel:
SoftDeletes,HasFactory,Notifiable,InteractsWithQueue,HasUuids. - The behavior doesn't represent a type relationship.
Mixin limitations: Traits are "copy-pasted" at compile time (conceptually). They can't be type-hinted directly. If a trait needs to call class methods, it uses abstract requirements or just calls $this->method() (trusting the class has it — "duck typing").
Code Example
<?php
// Mixin 1 — Timestampable
trait Timestampable
{
protected ?\DateTimeImmutable $createdAt = null;
protected ?\DateTimeImmutable $updatedAt = null;
public function touch(): void { $this->updatedAt = new \DateTimeImmutable(); }
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
public function getUpdatedAt(): ?\DateTimeImmutable { return $this->updatedAt; }
public function initTimestamps(): void
{
$this->createdAt = new \DateTimeImmutable();
$this->updatedAt = new \DateTimeImmutable();
}
}
// Mixin 2 — SoftDeletable
trait SoftDeletable
{
protected ?\DateTimeImmutable $deletedAt = null;
public function softDelete(): void { $this->deletedAt = new \DateTimeImmutable(); }
public function restore(): void { $this->deletedAt = null; }
public function isDeleted(): bool { return $this->deletedAt !== null; }
}
// Mixin 3 — HasUuid
trait HasUuid
{
protected string $uuid;
public function initUuid(): void { $this->uuid = \Illuminate\Support\Str::uuid()->toString(); }
public function getUuid(): string { return $this->uuid; }
}
// Compose multiple mixins — no class hierarchy
class Article
{
use Timestampable, SoftDeletable, HasUuid; // mixed in
public function __construct(
public readonly string $title,
public readonly string $body,
) {
$this->initTimestamps();
$this->initUuid();
}
}
class Comment
{
use Timestampable, SoftDeletable; // uses SOME of the same mixins — no class hierarchy
public function __construct(public readonly string $body, public readonly int $userId)
{
$this->initTimestamps();
}
}
$article = new Article('Hello World', 'Lorem ipsum...');
echo $article->getUuid(); // unique UUID
$article->touch(); // updates updatedAt
echo $article->isDeleted() ? 'deleted' : 'active'; // 'active'
// LARAVEL EXAMPLE — Eloquent mixins
class Post extends \Illuminate\Database\Eloquent\Model
{
use \Illuminate\Database\Eloquent\SoftDeletes; // adds deleted_at, delete(), restore()
use \Illuminate\Database\Eloquent\Factories\HasFactory; // adds factory()
use \Illuminate\Notifications\Notifiable; // adds notify(), notifications relation
use \Illuminate\Database\Eloquent\Concerns\HasUuids; // UUID primary keys
}
// Post is not "a SoftDelete" or "a Notifiable" — it HAS those capabilities mixed in