<?php OOP: Zero to Hero Chapter 10 of 20

Chapter 10 ยท Going Deeper

Traits

PHP allows a class to extend only one parent. But what if two completely unrelated classes need the same handy methods? Copy-paste? Never. PHP's answer is the trait โ€” a reusable package of methods you can drop into any class.

The problem traits solve

Imagine both Student and Invoice need timestamp tracking โ€” "when was this created? when was it updated?". They are totally unrelated: a student is not an invoice, an invoice is not a student, so inheritance makes no sense. Yet the code they need is identical.

๐Ÿงฉ
Real-world analogy

A trait is like a phone app. WhatsApp isn't part of any phone's "family tree" โ€” Samsung didn't inherit it from Nokia. It's a self-contained bundle of abilities that any phone can install. A trait is an installable ability-pack: write the methods once, install (use) them in as many classes as you like.

Writing and using a trait

<?php
trait HasTimestamps {
    public ?string $createdAt = null;
    public ?string $updatedAt = null;

    public function touchCreated(): void {
        $this->createdAt = date("Y-m-d H:i:s");
    }

    public function touchUpdated(): void {
        $this->updatedAt = date("Y-m-d H:i:s");
    }
}

class Student {
    use HasTimestamps;    // install the ability-pack

    public function __construct(public string $name) {
        $this->touchCreated();
    }
}

class Invoice {
    use HasTimestamps;    // same pack, totally different class

    public function __construct(public float $amount) {
        $this->touchCreated();
    }
}

$s = new Student("Chanda");
echo "Student created at: $s->createdAt <br>";

$inv = new Invoice(5000);
echo "Invoice created at: $inv->createdAt";
Student created at: 2026-07-12 09:41:22 Invoice created at: 2026-07-12 09:41:22

Note the details:

Multiple traits in one class

trait CanLog {
    public function log(string $msg): void {
        echo "[" . date("H:i:s") . "] " . get_class($this) . ": $msg<br>";
    }
}

trait HasSlug {
    public function slug(string $text): string {
        return strtolower(str_replace(" ", "-", trim($text)));
    }
}

class BlogPost {
    use CanLog, HasSlug;      // install two packs at once

    public function __construct(public string $title) {
        $this->log("Post created: " . $this->slug($title));
    }
}

new BlogPost("Learning PHP OOP Is Fun");
[09:45:10] BlogPost: post created: learning-php-oop-is-fun

(get_class($this) is a handy built-in that returns the name of the current object's class โ€” great for log messages.)

Name clashes โ€” when two traits collide

If two installed traits both define a method called hello(), PHP stops and asks you to choose. You resolve it with insteadof (pick one) and optionally as (keep the other under a new name):

trait EnglishGreeting { public function hello(): string { return "Hello!"; } }
trait BembaGreeting   { public function hello(): string { return "Muli shani!"; } }

class Receptionist {
    use EnglishGreeting, BembaGreeting {
        BembaGreeting::hello insteadof EnglishGreeting;  // Bemba wins the name "hello"
        EnglishGreeting::hello as helloEnglish;          // English survives under a new name
    }
}

$r = new Receptionist();
echo $r->hello() . " / " . $r->helloEnglish();
Muli shani! / Hello!

You won't need this often โ€” but when the error "trait method collision" appears, you'll know exactly what it means and how to fix it.

Trait vs inheritance vs interface โ€” the cheat sheet

ToolQuestion it answersExample
Inheritance (extends)What is this thing?A Student is a Person
Interface (implements)What does it promise it can do?An Invoice promises it's Payable
Trait (use)What ready-made code does it borrow?Student and Invoice both use HasTimestamps
โœ๏ธ Try it yourself

Write a trait Describable with one method describe() that returns get_class($this) . " object ready!". Install it in two unrelated classes, Car and Pizza, and call describe() on one object of each.

Show solution
<?php
trait Describable {
    public function describe(): string {
        return get_class($this) . " object ready!";
    }
}

class Car   { use Describable; }
class Pizza { use Describable; }

echo (new Car())->describe() . "<br>";
echo (new Pizza())->describe();
Car object ready! Pizza object ready!
Quick quiz

How does a class install a trait?

Quick quiz

When is a trait the right choice instead of inheritance?

๐Ÿ”‘ Key points
  • A trait is an installable package of methods (and properties) โ€” the "phone app" of OOP.
  • Declare with trait Name {}, install with use Name; inside any class.
  • Traits solve the "one parent only" limit: a class can use many traits.
  • Traits cannot be instantiated โ€” they only exist inside classes.
  • Name clashes are resolved with insteadof and as.
  • extends = "is a", implements = "promises to", use (trait) = "borrows code from".