PHPTraits

Traits

PHP classes can only extend one parent class. That single-inheritance rule keeps class hierarchies easy to reason about, but it leaves a real gap: what do you do when two unrelated classes need the exact same chunk of behavior, and neither one is a natural parent of the other? Traits are PHP's answer. A trait is a named block of methods (and properties) that you can pull into any class with a use statement, as if you had copy-pasted its contents directly into the class body. It is code reuse without inheritance — sometimes called "horizontal" reuse, because instead of climbing up a parent/child chain, you are pasting the same slab of code sideways into however many classes need it.

Declaring and using a trait

A trait looks like a class but is declared with the trait keyword and can never be instantiated on its own — new SomeTrait() is a fatal error. A class pulls it in with use TraitName; inside the class body.

A logging trait shared by two unrelated classes

PHP
<?php
trait Loggable {
    protected array $logs = [];

    public function log(string $message): void {
        $this->logs[] = $message;
    }

    public function getLogs(): array {
        return $this->logs;
    }
}

class Order {
    use Loggable;

    public function ship(): void {
        $this->log('Order shipped');
    }
}

class Invoice {
    use Loggable;

    public function send(): void {
        $this->log('Invoice sent');
    }
}

$order = new Order();
$order->ship();

$invoice = new Invoice();
$invoice->send();

print_r($order->getLogs());
print_r($invoice->getLogs());
Array
(
    [0] => Order shipped
)
Array
(
    [0] => Invoice sent
)

Order and Invoice share no common ancestor besides the built-in base of every class, yet both got a fully working log() method and their own private $logs array. Each class that uses Loggable gets its own independent copy of the trait's properties — $order->logs and $invoice->logs do not interfere with each other.

Using multiple traits at once

A class can pull in several traits in a single use statement, separated by commas. This is where traits earn their keep: you can assemble a class out of small, independently testable behavior blocks instead of writing one big parent class that tries to cover every combination.

Combining two traits in one class

PHP
<?php
trait Timestampable {
    public function touch(): void {
        echo "Updated at: " . date('Y-m-d') . "\n";
    }
}

trait Loggable {
    public function log(string $message): void {
        echo "LOG: {$message}\n";
    }
}

class Article {
    use Timestampable, Loggable;
}

$article = new Article();
$article->touch();
$article->log('Article saved');
Updated at: 2026-07-02
LOG: Article saved
Resolving conflicts: insteadof and as

Two traits used in the same class might define a method with the same name. PHP will not silently pick one — it throws a fatal error unless you resolve the collision explicitly with an insteadof clause, and optionally give the loser an alias with as so you can still reach it under a different name.

Two traits both define greet(); resolve which one wins

PHP
<?php
trait English {
    public function greet(): string {
        return 'Hello';
    }
}

trait Spanish {
    public function greet(): string {
        return 'Hola';
    }
}

class Greeter {
    use English, Spanish {
        English::greet insteadof Spanish;
        Spanish::greet as greetInSpanish;
    }
}

$greeter = new Greeter();
echo $greeter->greet(), "\n";
echo $greeter->greetInSpanish(), "\n";
Hello
Hola

English::greet insteadof Spanish tells PHP which implementation becomes the class's actual greet() method. The as line does not create a second, competing method — it just gives the shadowed Spanish::greet a new local name, greetInSpanish(), so nothing is lost. as can also be used purely to change visibility, e.g. English::greet as protected;, without renaming anything.

Abstract and static members in traits

Traits can declare abstract methods that force the using class to provide an implementation, and they can hold their own static properties and methods, each independent per class that uses them.

A trait that demands an implementation from its user

PHP
<?php
trait Comparable {
    abstract public function getValue(): int;

    public function isGreaterThan(self $other): bool {
        return $this->getValue() > $other->getValue();
    }
}

class Money {
    use Comparable;

    public function __construct(private int $cents) {}

    public function getValue(): int {
        return $this->cents;
    }
}

$a = new Money(500);
$b = new Money(300);

var_dump($a->isGreaterThan($b));
bool(true)
Traits vs interfaces vs abstract classes

These three tools solve related but distinct problems, and mixing them up is one of the more common design mistakes in PHP codebases:

  • Interface — a contract only. It says what a class can do (method signatures) but supplies zero implementation. Use it when unrelated classes need to be treated the same way through a common type, e.g. anything Countable or JsonSerializable.

  • Abstract class — a partial implementation plus a contract, but a class can extend only one. Use it when you have a real "is-a" relationship and want to share both state and behavior down a single inheritance line.

  • Trait — implementation only, no type. A trait does not satisfy instanceof checks and cannot be used as a type hint. Use it purely to share behavior across classes that have no "is-a" relationship at all.

A practical pattern is to combine them: define an interface for the public contract, then use one or more traits to provide a default implementation of that contract, and let classes implements the interface while pulling in the trait for the boilerplate.

Traits are not free of state, and constructors collide too
If two traits used in the same class each declare a property with the same name but different default values, or each declare a `__construct()` method, PHP raises a fatal error at class-declaration time — the same `insteadof`/`as` machinery is needed to resolve colliding methods, but colliding properties with incompatible defaults cannot be resolved at all and simply break the class. Keep trait property names namespaced or unique to avoid this.
Traits share properties, not object identity
Each class that uses a trait gets its own copy of that trait's properties. There is no shared, static-across-classes storage unless you deliberately declare the property as `static` inside the trait, and even then each *using class* gets an independent static value — it is not shared between, say, `Order` and `Invoice` from the example above.
Tip
Reach for a trait when you catch yourself wanting to copy-paste the same three methods into five otherwise-unrelated classes. If you find yourself also wanting those five classes to be interchangeable through a common type, pair the trait with an interface — the trait supplies the shared code, the interface supplies the shared type.