PHP Traits

Share method implementations across unrelated classes without inheritance. Learn use, multiple traits, conflict resolution, and when traits beat composition.

Intermediate 8 min read 8 examples

What is a Trait?

A trait is a chunk of methods (and optionally properties) that gets compiled into using classes. Think of it as a copy-paste mechanism for code reuse - no inheritance involved.

Defining a Trait

PHPTimestampable.php
<?php
trait Timestampable {
    public ?DateTimeImmutable $createdAt = null;
    public ?DateTimeImmutable $updatedAt = null;

    public function markCreated(): void {
        $this->createdAt = new DateTimeImmutable();
    }

    public function markUpdated(): void {
        $this->updatedAt = new DateTimeImmutable();
    }
}

Using a Trait

PHP
<?php
class Article {
    use Timestampable;

    public function __construct(public string $title) {
        $this->markCreated();
    }
}

class Comment {
    use Timestampable;        // same code, no inheritance needed

    public function __construct(public string $body) {
        $this->markCreated();
    }
}

$a = new Article("Hello");
echo $a->createdAt->format("Y-m-d H:i:s");

Multiple Traits

PHP
<?php
trait SoftDeletes {
    public ?DateTimeImmutable $deletedAt = null;
    public function softDelete(): void {
        $this->deletedAt = new DateTimeImmutable();
    }
    public function isDeleted(): bool {
        return $this->deletedAt !== null;
    }
}

trait HasUuid {
    public string $uuid;
    public function generateUuid(): void {
        $this->uuid = bin2hex(random_bytes(16));
    }
}

class Post {
    use Timestampable, SoftDeletes, HasUuid;

    public function __construct(public string $title) {
        $this->generateUuid();
        $this->markCreated();
    }
}

Resolving Conflicts

When two traits provide the same method name, PHP raises a fatal error. Resolve with insteadof and optional aliasing with as:

PHP
<?php
trait A {
    public function hello(): string { return "Hello from A"; }
}

trait B {
    public function hello(): string { return "Hello from B"; }
}

class Greeter {
    use A, B {
        A::hello insteadof B;     // use A's by default
        B::hello as helloB;       // alias B's as helloB()
    }
}

$g = new Greeter();
echo $g->hello();    // Hello from A
echo $g->helloB();   // Hello from B

Abstract Methods in Traits

Traits can require the using class to implement specific methods:

PHP
<?php
trait Cacheable {
    public function getCacheKey(): string {
        return static::cachePrefix() . ":" . $this->getId();
    }

    // Force using class to provide this
    abstract public function getId(): int;
    abstract public static function cachePrefix(): string;
}

class Product {
    use Cacheable;

    public function __construct(private int $id) {}

    public function getId(): int { return $this->id; }
    public static function cachePrefix(): string { return "prod"; }
}

echo (new Product(42))->getCacheKey();   // prod:42

Method Resolution Order

When the same method is defined in multiple places, PHP picks in this order:

  1. Methods defined in the current class (highest priority)
  2. Methods inserted by traits
  3. Methods inherited from parent classes
PHP
<?php
class Base {
    public function name(): string { return "Base"; }
}

trait NamedT {
    public function name(): string { return "Trait"; }
}

class Child extends Base {
    use NamedT;
    // Trait beats parent

    public function name(): string { return "Class"; }
    // Class beats trait
}

echo (new Child())->name();   // "Class"

When to Use Traits

Good fits:

  • Stateless helpers used across unrelated classes (slugs, hashing)
  • Cross-cutting concerns that are too small for composition (timestamps, soft deletes)
  • Adding interface implementations to many classes (Stringable, JsonSerializable)

Avoid traits when:

  • The behavior holds external dependencies (databases, HTTP clients) - use composition
  • The behavior could need to be swapped at runtime - use composition
  • You'd need three+ traits cooperating - the result is hard to reason about
Traits make tests harder

Trait methods can't be mocked in isolation. If the trait does anything non-trivial (network, time, randomness), you'll regret not using composition. Reserve traits for trivial code.

Next Steps

Frequently Asked Questions

An interface defines what methods exist - no bodies. A trait provides actual implementations that get copy-pasted into the using class at compile time. Use both together: interface for the contract, trait for the default implementation.

Yes, but it's a code smell. The using class often defines its own constructor that overrides the trait's. If a trait needs initialization, give it an initSomething() method that the using class calls explicitly.

Methods added by a trait are inherited by subclasses (because they're effectively part of the using class). But the trait itself is not "extended" - to use the trait in a subclass independently, the subclass must also use it.

When the shared behavior owns state, makes external calls, or could be swapped out. Composition is more testable - you can inject a fake. Traits are best for tiny, stateless helpers (string sluggers, simple logging) where injection would be overkill.

Because the same method can come from multiple sources (the class, parent, traits, more traits). The resolution order (class > trait > parent) and conflict-resolution syntax (insteadof, as) is non-obvious. Keep trait usage shallow.