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
<?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
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
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
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
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:
- Methods defined in the current class (highest priority)
- Methods inserted by traits
- Methods inherited from parent classes
<?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
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.