Inheritance
Inheritance lets a class build on another one. The child class (subclass) automatically gets the parent's public and protected properties and methods, can add new ones, and can override existing ones. It models "is a" relationships — a savings account is an account, an admin is a user — and lets shared code live in one place.
extends
<?php class Vehicle { public function __construct(public string $brand) {} public function start(): string { return "{$this->brand} engine started"; } } class Car extends Vehicle { public int $seats = 5; public function honk(): string { return "Beep beep!"; } } $car = new Car("Volvo"); echo $car->start(), "\n"; echo $car->honk(), "\n"; echo "{$car->brand}, {$car->seats} seats\n"; var_dump($car instanceof Car, $car instanceof Vehicle);
Volvo engine started Beep beep! Volvo, 5 seats bool(true) bool(true)
Car inherited the constructor, $brand and start(). A car is also a Vehicle, so it can be passed anywhere a Vehicle is expected. A class can extend only one parent.
Overriding methods
A child class can replace a parent method by declaring one with the same name. parent::method() calls the original, so you can extend behaviour rather than rewrite it. The #[\Override] attribute (PHP 8.3) asks PHP to check that a parent method really exists — catching typos and renamed methods:
<?php class Employee { public function __construct(public string $name, public float $salary) {} public function describe(): string { return "{$this->name} earns " . number_format($this->salary); } } class Manager extends Employee { public function __construct(string $name, float $salary, public float $bonus) { parent::__construct($name, $salary); } #[\Override] public function describe(): string { return parent::describe() . " plus a " . number_format($this->bonus) . " bonus"; } } $staff = [new Employee("Ana", 48000), new Manager("Iris", 62000, 8000)]; foreach ($staff as $person) { echo $person->describe(), "\n"; }
Ana earns 48,000 Iris earns 62,000 plus a 8,000 bonus
The loop calls describe() without knowing which kind of employee it has — each object uses its own version. This is polymorphism.
Note how Manager's constructor calls parent::__construct(): when a child defines its own constructor, the parent's isn't called automatically.
<?php class Base { public function greet(): string { return "hi"; } } class Child extends Base { #[\Override] public function greeet(): string { return "hello"; } // typo! }
Fatal error: Child::greeet() has #[\Override] attribute, but no matching parent method exists in main.php on line 11
Stack trace:
#0 {main}protected
protected members are hidden from the outside world but available to subclasses — useful for shared internals:
<?php class Account { protected float $balance = 0; public function deposit(float $amount): void { $this->balance += $amount; } public function balance(): float { return $this->balance; } } class SavingsAccount extends Account { public function __construct(private float $rate) {} public function addInterest(): void { $this->balance += $this->balance * $this->rate; // allowed: protected } } $s = new SavingsAccount(0.03); $s->deposit(1000); $s->addInterest(); echo $s->balance(), "\n";
1030
Method signatures must be compatible
An overriding method must accept at least what the parent accepts and return something the parent's return type allows (so subclasses can be used anywhere the parent is). Breaking that is a fatal error:
<?php class Shape { public function scale(float $factor): static { return $this; } } class Square extends Shape { public function scale(int $factor): static { return $this; } }
Fatal error: Declaration of Square::scale(int $factor): static must be compatible with Shape::scale(float $factor): static in main.php on line 10
Stack trace:
#0 {main}final
final on a method stops subclasses from overriding it; on a class, it stops the class from being extended at all. Many teams mark classes final by default and only open them up deliberately:
<?php final class Invoice {} class SpecialInvoice extends Invoice {}
Fatal error: Class SpecialInvoice cannot extend final class Invoice in main.php on line 5
Stack trace:
#0 {main}Inheritance or composition?
Inheritance couples classes tightly: changes to the parent ripple into every child, and deep hierarchies become hard to follow. Use it when the "is a" relationship is real and stable. When one class merely uses another ("an order has a customer"), give it a property of that type instead — composition. Interfaces (next lesson) and traits (lesson 39) are other ways to share contracts and code without deep hierarchies.
Try it yourself
Add a Truck class that extends Vehicle, adds a cargo capacity and overrides describe().
Bike with 2 wheels Lorry with 6 wheels, carries 18.5 t