0

Traits — horizontal code reuse, conflict resolution

Intermediate5 min read·php-07-009
interviewcompare

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
<?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"