PHP Traits
What Is a Trait?
In simple terms: A trait is a reusable block of methods (and properties) that can be "copied into" any class. If several unrelated classes need the same small feature — timestamps, a slug generator, logging helpers — you write it once as a trait and use it in each class, without forcing those classes into a common parent.
Technically: A trait is declared with the trait keyword and included in a class with use TraitName; inside the class body. PHP copies the trait's methods, properties, and (since PHP 8.2) constants into the class at compile time, as if they had been written there. Traits solve a limitation of single inheritance: a class can extend only one parent, but it can use any number of traits.
Why Traits Exist
| Problem | Without Traits | With Traits |
|---|---|---|
Post and Product both need timestamps, but have different parents | Duplicate the code in both classes | One HasTimestamps trait used by both |
| A class needs features from two "parents" | Impossible with single inheritance | Use several traits |
| Shared helper code across a hierarchy | A bloated base class | Small, focused traits added where needed |
Declaring and Using a Trait
Syntax
trait TraitName
{
// properties, methods, abstract methods, constants (PHP 8.2+)
}
class ClassName
{
use TraitName;
}
Example
<?php
trait HasTimestamps
{
private ?DateTimeImmutable $createdAt = null;
private ?DateTimeImmutable $updatedAt = null;
public function touch(DateTimeImmutable $now): void
{
$this->createdAt ??= $now;
$this->updatedAt = $now;
}
public function timestamps(): string
{
return sprintf(
'created %s, updated %s',
$this->createdAt?->format('Y-m-d H:i') ?? '-',
$this->updatedAt?->format('Y-m-d H:i') ?? '-'
);
}
}
trait HasSlug
{
abstract public function title(): string;
public function slug(): string
{
return strtolower(trim(preg_replace('/[^A-Za-z0-9]+/', '-', $this->title()), '-'));
}
}
final class Post
{
use HasTimestamps, HasSlug;
public function __construct(private string $title) {}
public function title(): string
{
return $this->title;
}
}
final class Product
{
use HasTimestamps;
public function __construct(public readonly string $sku) {}
}
$post = new Post('PHP Traits & Reuse: A Guide');
$post->touch(new DateTimeImmutable('2026-10-15 10:00'));
$post->touch(new DateTimeImmutable('2026-10-15 12:30'));
$product = new Product('PEN-01');
echo $post->slug() . "\n";
echo "Post: " . $post->timestamps() . "\n";
echo "Product: " . $product->timestamps() . "\n";
print_r(class_uses($post));
Output
php-traits-reuse-a-guide
Post: created 2026-10-15 10:00, updated 2026-10-15 12:30
Product: created -, updated -
Array
(
[HasTimestamps] => HasTimestamps
[HasSlug] => HasSlug
)
How It Works
use HasTimestamps, HasSlug;copies the members of both traits intoPost.Productuses onlyHasTimestamps.- The trait's properties become properties of each class; every object has its own
createdAtandupdatedAt. - Inside trait methods,
$thisrefers to the object of the class using the trait. HasSlugdeclares an abstract methodtitle(): the trait needs a title, and every class using the trait must provide it. This is how a trait states its requirements.class_uses()lists the traits a class uses directly.
Post and Product are unrelated classes with no common parent, yet they share the timestamp code without duplication.
Resolving Conflicts
If two traits provide a method with the same name, PHP cannot decide which one to use:
<?php
trait Loggable
{
public function log(string $message): string
{
return "[LOG] $message";
}
}
trait Auditable
{
public function log(string $message): string
{
return "[AUDIT] $message";
}
}
final class Order
{
use Loggable, Auditable {
Auditable::log insteadof Loggable; // use Auditable's log()
Loggable::log as debugLog; // keep Loggable's version under another name
}
}
$order = new Order();
echo $order->log('Order 501 paid') . "\n";
echo $order->debugLog('Payment payload received') . "\n";
Output
[AUDIT] Order 501 paid
[LOG] Payment payload received
insteadofchooses which trait's method wins.ascreates an alias — an additional name for a method.- Without the conflict resolution block, PHP stops with a fatal error:
Trait method Auditable::log has not been applied as Order::log, because of collision with Loggable::log.
Changing Visibility
as can also change a method's visibility in the using class:
class Report
{
use Loggable {
log as protected; // log() is protected in Report
log as private writeLog; // plus a private alias
}
}
Precedence Rules
When the same method exists in several places, PHP uses this order:
1. Method defined in the class itself (highest priority)
2. Method from a trait used by the class
3. Method inherited from the parent class (lowest priority)
<?php
class BaseController
{
public function render(): string
{
return 'base';
}
}
trait JsonRendering
{
public function render(): string
{
return 'json';
}
}
class ApiController extends BaseController
{
use JsonRendering;
}
class CustomController extends BaseController
{
use JsonRendering;
public function render(): string
{
return 'custom';
}
}
echo (new ApiController())->render() . "\n";
echo (new CustomController())->render() . "\n";
Output
json
custom
The trait's method overrides the inherited one, and the class's own method overrides the trait's.
Other Trait Features
| Feature | Example | Notes |
|---|---|---|
| Abstract methods | abstract public function title(): string; | Requirements the using class must fulfill |
| Static methods and properties | public static function create(): static | Each using class gets its own static properties |
| Constants (PHP 8.2+) | public const VERSION = 1; | Accessed through the using class (Post::VERSION), not the trait |
| Traits using traits | trait A { use B; } | Compose small traits into larger ones |
__CLASS__ inside a trait | — | Refers to the class using the trait |
self:: / static:: | — | Refer to the using class |
Traits vs Inheritance vs Composition vs Interfaces
| Tool | Shares | Defines a Type? | Best For |
|---|---|---|---|
| Inheritance | Code and type | Yes | True "is-a" relationships |
| Interface | Contract only | Yes | Interchangeable implementations |
| Trait | Code only | No | Small, reusable pieces of behavior across unrelated classes |
| Composition | Behavior via another object | — | Most reuse of services and logic |
A trait is not a type: you cannot type-hint HasSlug or check instanceof HasSlug. A common pattern combines a trait with an interface: the interface (Sluggable) defines the type, and the trait (HasSlug) provides a ready-made implementation.
interface Sluggable
{
public function slug(): string;
}
final class Post implements Sluggable
{
use HasSlug;
// ...
}
Common Mistakes
Using Traits as a Replacement for Services
Effect: Large traits with database access or HTTP calls create hidden dependencies and are hard to test.
Fix: Inject a service object (composition) for complex behavior.
Traits Relying on Undeclared Properties
Effect: The trait silently depends on $this->title existing in every class.
Fix: Declare requirements as abstract methods.
Unresolved Method Conflicts
Effect: Fatal "collision" errors when two traits share a method name.
Fix: Resolve with insteadof and as.
Expecting instanceof to Work With Traits
Effect: Traits are not types.
Fix: Pair the trait with an interface.
Too Many Traits per Class
Effect: Behavior is scattered across many files and hard to follow.
Fix: Keep traits few and focused.
Best Practices
- Use traits for small, cohesive, stateless or lightly stateful behavior shared by unrelated classes.
- Declare a trait's requirements with abstract methods.
- Combine traits with interfaces when a type is needed.
- Resolve conflicts explicitly and keep trait method names specific.
- Prefer composition for services and complex logic.
Official reference: https://www.php.net/manual/en/language.oop5.traits.php