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.
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";
Note the details:
- Declared with
trait Name { ... }โ looks exactly like a class body. - Installed with
use TraitName;as the first line inside a class. - Once installed, its methods and properties behave as if you had typed them directly
into the class โ
$thisworks normally. - A trait can never be instantiated:
new HasTimestamps()is an error. It's an ability-pack, not a thing. - (The
?stringtype means "a string or null" โ null until the timestamp is first set.)
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");
(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();
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
| Tool | Question it answers | Example |
|---|---|---|
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 |
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();
How does a class install a trait?
When is a trait the right choice instead of inheritance?
- A trait is an installable package of methods (and properties) โ the "phone app" of OOP.
- Declare with
trait Name {}, install withuse 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
insteadofandas. - extends = "is a", implements = "promises to", use (trait) = "borrows code from".