Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 46 of 54

Traits

Reuse methods across unrelated classes with traits, combine several traits, resolve name conflicts, and know when a trait is the right tool.

10 minObject-oriented PHPUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonShare methods between unrelated classes with traits and resolve conflicts between them.

Sharing code without a parent

Inheritance shares code between a parent and its children. But sometimes completely unrelated classes need the same small piece of behaviour: an Order, a BlogPost and a Customer all want "created at" and "updated at" timestamps; several classes want to turn themselves into a URL slug. Making them share a parent class just for that would be wrong, and PHP allows only one parent anyway.

A trait is a reusable bundle of methods (and properties) that PHP copies into any class that asks for it with use:

php
<?php
trait HasSlug
{
    public function slug(): string
    {
        $text = strtolower(trim($this->slugSource()));
        return trim(preg_replace('/[^a-z0-9]+/', '-', $text), '-');
    }

    abstract protected function slugSource(): string;
}

class Product
{
    use HasSlug;

    public function __construct(public string $name) {}

    protected function slugSource(): string
    {
        return $this->name;
    }
}

class BlogPost
{
    use HasSlug;

    public function __construct(public string $title) {}

    protected function slugSource(): string
    {
        return $this->title;
    }
}

echo (new Product('Noise-Cancelling Headphones'))->slug() . "\n";
echo (new BlogPost('PHP & MySQL: Getting Started'))->slug() . "\n";
result
noise-cancelling-headphones
php-mysql-getting-started

The trait declares an abstract method, slugSource(), that each class must provide, so the trait can rely on it. Inside a trait, $this is the object of whichever class is using it.

Properties in traits

Traits can also hold properties. Here is a trait that records timestamps, used by an order:

php
<?php
trait Timestamps
{
    private ?DateTimeImmutable $createdAt = null;
    private ?DateTimeImmutable $updatedAt = null;

    public function touch(DateTimeImmutable $now): void
    {
        $this->createdAt ??= $now;
        $this->updatedAt = $now;
    }

    public function createdAt(): ?string
    {
        return $this->createdAt?->format('Y-m-d H:i');
    }

    public function updatedAt(): ?string
    {
        return $this->updatedAt?->format('Y-m-d H:i');
    }
}

class Order
{
    use Timestamps;

    public function __construct(public int $id) {}
}

$order = new Order(1042);
$order->touch(new DateTimeImmutable('2024-06-03 09:00'));
$order->touch(new DateTimeImmutable('2024-06-05 16:30'));

echo "Created " . $order->createdAt() . ", updated " . $order->updatedAt() . "\n";
result
Created 2024-06-03 09:00, updated 2024-06-05 16:30

Laravel's Eloquent models use exactly this kind of trait for created_at and updated_at, and many of Laravel's features, such as SoftDeletes and HasFactory, are traits you add to a model.

Several traits and conflicts

A class can use several traits at once, separated by commas. If two of them define a method with the same name, PHP refuses to guess:

php
<?php
trait LogsToFile
{
    public function log(string $m): void { echo "file: $m\n"; }
}

trait LogsToScreen
{
    public function log(string $m): void { echo "screen: $m\n"; }
}

class Importer
{
    use LogsToFile, LogsToScreen;
}
result
Fatal error: Trait method LogsToScreen::log has not been applied as Importer::log, because of collision with LogsToFile::log in /path/to/script.php on line 12

Resolve it in a block after use: insteadof picks which method wins, and as gives the other one a new name so it is still available:

php
<?php
trait LogsToFile
{
    public function log(string $m): void { echo "file: $m\n"; }
}

trait LogsToScreen
{
    public function log(string $m): void { echo "screen: $m\n"; }
}

class Importer
{
    use LogsToFile, LogsToScreen {
        LogsToFile::log insteadof LogsToScreen;
        LogsToScreen::log as show;
    }
}

$importer = new Importer();
$importer->log('Imported 42 prices');
$importer->show('Done');
result
file: Imported 42 prices
screen: Done

Traits are not types

A trait is copied into the class; it does not become part of the class's type. $order instanceof Timestamps is always false, and you cannot type-hint a parameter as a trait. When other code needs to check for an ability, pair the trait with an interface: the interface is the contract, and the trait is a ready-made implementation of it.

php
<?php
interface Sluggable
{
    public function slug(): string;
}

trait HasSlug
{
    public function slug(): string
    {
        return strtolower(str_replace(' ', '-', $this->name));
    }
}

class Category implements Sluggable
{
    use HasSlug;

    public function __construct(public string $name) {}
}

function categoryLink(Sluggable $item): string
{
    return "/c/" . $item->slug() . "/";
}

echo categoryLink(new Category('Home Office')) . "\n";
result
/c/home-office/

When to use traits

Traits are handy for small, self-contained helpers that many classes need. They are also easy to overuse: a class built from six traits is hard to understand, because its code is spread across several files and the traits can depend on each other in hidden ways. Prefer, in order:

  1. A plain function or a small helper class you call (composition).
  2. An interface plus a class that implements it.
  3. A trait, when the same methods would otherwise be copied into several unrelated classes.

Common mistakes

  • Writing use inside a method instead of directly in the class body.
  • Expecting instanceof to work with a trait. Use an interface.
  • Hiding a dependency: a trait that reads $this->name without declaring an abstract method or documenting it breaks in classes that have no name.
  • Building classes mostly out of traits, which scatters their logic.

Exercise

Write a trait CountsInstances that keeps a static counter of how many objects of the class have been created, with a static method instances(): int, and increments it from a method register() that the constructor calls. Use it in a Ticket class and create three tickets.

Show one possible answer
php
<?php
trait CountsInstances
{
    private static int $count = 0;

    protected function register(): void
    {
        self::$count++;
    }

    public static function instances(): int
    {
        return self::$count;
    }
}

class Ticket
{
    use CountsInstances;

    public function __construct(public string $code)
    {
        $this->register();
    }
}

new Ticket('A1');
new Ticket('A2');
new Ticket('A3');
echo Ticket::instances() . " tickets issued\n";
result
3 tickets issued

Static properties and methods, which belong to the class rather than to one object, are the topic of the next lesson.

Key points

  • A trait is a set of methods (and properties) that you copy into a class with use TraitName;.
  • A class can use several traits, which works around PHP's single inheritance.
  • When two traits define the same method, choose one with insteadof and rename the other with as.
  • Traits suit small, self-contained behaviour; for a contract, use an interface as well.

Check yourself

1. How do you add a trait to a class?
2. Can you create an object directly from a trait with new?
3. Two traits used by one class both define log(). What happens without any resolution?
4. Does instanceof work with traits?