0

Mixin — code reuse without a class hierarchy (Traits in PHP)

Beginner5 min read·eng-12-011
compare

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 instanceof check — 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
<?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