Traits — horizontal code reuse, conflict resolution
Concept
Traits solve PHP's limitation of single inheritance by enabling horizontal code reuse — functionality that can be mixed into multiple classes without a parent-child relationship. A trait is like a copy-paste mechanism at the language level: methods defined in a trait are inserted into each class that uses it.
Conflict resolution: When two traits used by the same class define a method with the same name, PHP throws a fatal error — you must explicitly resolve the conflict with insteadof (choose one) and optionally as (alias the other under a different name).
Abstract methods in traits: Traits can declare abstract methods, requiring the class that uses the trait to implement them. This lets a trait define an algorithm that depends on a hook the consuming class provides.
Trait properties: Traits can define properties. If the using class also defines a property with the same name, the types and default values must be compatible (PHP 8 enforces this strictly).
Traits vs interfaces: A trait provides implementation; an interface provides a contract. They're complementary: use an interface to define the contract and a trait to provide a default implementation of that contract.
When to use traits: Logging behavior shared across multiple unrelated classes, timestamping (created_at/updated_at mutators), soft-delete logic, common validation helpers. Laravel's Eloquent extensively uses traits: SoftDeletes, HasTimestamps, Notifiable, Searchable.
Code Example
<?php
declare(strict_types=1);
// Basic trait
trait HasTimestamps
{
private ?\DateTimeImmutable $createdAt = null;
private ?\DateTimeImmutable $updatedAt = null;
public function setCreatedAt(): void
{
$this->createdAt ??= new \DateTimeImmutable();
}
public function touch(): void
{
$this->updatedAt = new \DateTimeImmutable();
}
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
}
trait HasSoftDelete
{
private ?\DateTimeImmutable $deletedAt = null;
public function softDelete(): void
{
$this->deletedAt = new \DateTimeImmutable();
}
public function isDeleted(): bool { return $this->deletedAt !== null; }
public function restore(): void { $this->deletedAt = null; }
}
// Trait with abstract requirement
trait Auditable
{
abstract public function getAuditContext(): array; // class must provide this
public function logAudit(string $action): void
{
$context = $this->getAuditContext(); // uses the abstract
error_log("[$action] " . json_encode($context));
}
}
// Using multiple traits
class Post
{
use HasTimestamps, HasSoftDelete, Auditable;
public function __construct(private string $title) {}
public function getAuditContext(): array // required by Auditable trait
{
return ['title' => $this->title];
}
}
// Conflict resolution
trait A { public function hello(): string { return 'A'; } }
trait B { public function hello(): string { return 'B'; } }
class MyClass
{
use A, B {
A::hello insteadof B; // use A's version
B::hello as helloFromB; // alias B's version under a new name
}
}
$obj = new MyClass();
echo $obj->hello(); // "A"
echo $obj->helloFromB(); // "B"