Traits
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against PHP 8.5, checked on 2026-10-05. A trait is a group of methods and properties that is
copied into a class with use, a mechanism for horizontal reuse that sidesteps the single-inheritance limit.
Traits are compiled into each class that uses them, as if the code had been written there.
Declaring and using a trait
<?php
declare(strict_types=1);
trait HasTimestamps
{
private ?DateTimeImmutable $createdAt = null;
public function touch(DateTimeImmutable $now): void
{
$this->createdAt ??= $now;
}
public function createdAt(): ?DateTimeImmutable
{
return $this->createdAt;
}
}
final class Book
{
use HasTimestamps;
public function __construct(public readonly string $title) {}
}
final class Member
{
use HasTimestamps;
}
$book = new Book('Kindred');
$book->touch(new DateTimeImmutable('2026-10-05'));
echo $book->createdAt()?->format('Y-m-d'), "\n"; // 2026-10-05
var_dump(in_array('HasTimestamps', class_uses($book), true)); // bool(true)
var_dump($book instanceof HasTimestamps); // bool(false): a trait is not a type
A trait cannot be instantiated, is not a type (it cannot be used in declarations or with instanceof), and
class_uses() lists the traits of a class (not of its parents).
Source: Traits
Precedence
When names collide, members of the class itself override members from a trait, which override members inherited from the parent class:
<?php
declare(strict_types=1);
class Base
{
public function describe(): string { return 'base'; }
}
trait Describes
{
public function describe(): string { return 'trait, then ' . parent::describe(); }
}
class FromTrait extends Base { use Describes; }
class Overrides extends Base
{
use Describes;
public function describe(): string { return 'class'; } // the class's own method wins
}
echo (new FromTrait())->describe(), "\n"; // trait, then base
echo (new Overrides())->describe(), "\n"; // class
Source: Precedence
Conflict resolution: insteadof and as
If two traits provide a method with the same name, using both is a fatal error unless the conflict is resolved
explicitly: insteadof chooses which implementation wins, and as gives the other one an alias. as can also change
a method’s visibility, with or without aliasing it.
<?php
declare(strict_types=1);
trait CsvExport
{
public function export(array $rows): string
{
return implode("\n", array_map(fn (array $r): string => implode(',', $r), $rows));
}
}
trait JsonExport
{
public function export(array $rows): string
{
return json_encode($rows, JSON_THROW_ON_ERROR);
}
}
trait Audits
{
public function audit(string $event): string { return "audit: {$event}"; }
}
final class LoanReport
{
use CsvExport, JsonExport {
CsvExport::export insteadof JsonExport; // CSV wins for export()
JsonExport::export as exportJson; // keep JSON under another name
}
use Audits {
audit as protected; // change visibility
}
public function run(): string { return $this->audit('report run'); }
}
$r = new LoanReport();
$rows = [['Kindred', 3], ['Solaris', 1]];
echo $r->export($rows), "\n";
echo $r->exportJson($rows), "\n";
echo $r->run(), "\n";
Kindred,3
Solaris,1
[["Kindred",3],["Solaris",1]]
audit: report run
Source: Conflict Resolution and Changing Method Visibility
Abstract members, statics, constants and properties
-
Abstract methods: a trait can declare
abstractmethods that the using class must implement (with a compatible signature, checked since 8.0). Private abstract methods are allowed in traits. -
Static methods and properties: allowed. Each class that uses the trait gets its own copy of a static property (and of the static variables in the trait’s methods), and since 8.1 calling a static method or property directly on the trait (
Countable::$count) is deprecated. -
Constants: traits can declare constants since PHP 8.2; they are accessed through the using class, not through the trait name.
-
Properties: a class may declare a property that a trait also declares only if both are compatible (same visibility, type, readonly modifier and default value); otherwise it is a fatal error.
<?php
declare(strict_types=1);
trait CountsInstances
{
public const string LABEL = 'instances'; // 8.2: constants in traits
private static int $instances = 0; // each using class gets its own counter
public static function created(): int { return self::$instances; }
private function register(): void { self::$instances++; }
abstract public function id(): string; // the class must provide this
}
final class Book
{
use CountsInstances;
public function __construct(private string $isbn) { $this->register(); }
public function id(): string { return $this->isbn; }
}
final class Member
{
use CountsInstances;
public function __construct(private string $memberId) { $this->register(); }
public function id(): string { return $this->memberId; }
}
new Book('978-0-00-000001-1');
new Book('978-0-00-000003-5');
new Member('M-17');
echo Book::created(), ' ', Member::created(), ' ', Book::LABEL, "\n"; // 2 1 instances
Source: Abstract Trait Members, Static Trait Members and Constants
Traits composed of traits, and magic constants
A trait can use other traits. Inside trait methods, __CLASS__, self and static refer to the using class,
while __TRAIT__ is the trait’s own name:
<?php
declare(strict_types=1);
trait Named { public function className(): string { return __CLASS__ . ' via ' . __TRAIT__; } }
trait Describable { use Named; }
final class Catalogue { use Describable; }
echo (new Catalogue())->className(), "\n"; // Catalogue via Named
Source: Multiple Traits and Magic constants
When traits are the wrong tool
Traits are copy-and-paste with compiler support, and they share copy-and-paste’s problems:
-
Hidden dependencies: a trait method that uses
$this->repositorysilently requires the class to have that property. Declaring itabstractmakes the requirement explicit, but the trait is still coupled to its host. -
No type: code cannot depend on "something that has timestamps"; pair the trait with an interface (
interface Timestamped+trait HasTimestamps) so consumers type against the interface. -
Testing: a trait cannot be tested alone; you test it through a class (often an anonymous one).
-
State: traits with properties create classes whose state is assembled from several files.
Prefer composition — inject a collaborator object — when the shared code has its own state or dependencies, or
when it represents a separate responsibility. Traits fit small, stateless or nearly stateless helpers (formatting,
__toString() implementations, common JsonSerializable boilerplate) and framework conventions: Laravel’s
HasFactory, SoftDeletes, Notifiable and Dispatchable are traits that wire a model or job into framework
behaviour.
<?php
declare(strict_types=1);
interface Timestamped { public function createdAt(): ?DateTimeImmutable; }
trait HasTimestamps // implementation detail
{
private ?DateTimeImmutable $createdAt = null;
public function createdAt(): ?DateTimeImmutable { return $this->createdAt; }
public function stamp(DateTimeImmutable $at): static { $this->createdAt = $at; return $this; }
}
final class Review implements Timestamped { use HasTimestamps; }
function age(Timestamped $item, DateTimeImmutable $now): int // consumers depend on the interface
{
return $item->createdAt()?->diff($now)->days ?? 0;
}
echo age((new Review())->stamp(new DateTimeImmutable('2026-09-05')), new DateTimeImmutable('2026-10-05')), " days\n"; // 30 days
Source: Traits
Where older books and tutorials are outdated
-
Constants in traits were not allowed before PHP 8.2.
-
Abstract method signatures in traits were not checked against the implementing method before 8.0.
-
Calling static members on a trait directly is deprecated since 8.1.
References
-
PHP Manual — Traits (precedence, multiple traits, conflict resolution, visibility, abstract and static members, properties, constants)