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
<?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);
Output
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
<?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";
}
Output
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
<?php

class Base
{
    public function greet(): string { return "hi"; }
}

class Child extends Base
{
    #[\Override]
    public function greeet(): string { return "hello"; }   // typo!
}
Output
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
<?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";
Output
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
<?php

class Shape
{
    public function scale(float $factor): static { return $this; }
}

class Square extends Shape
{
    public function scale(int $factor): static { return $this; }
}
Output
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
<?php

final class Invoice {}

class SpecialInvoice extends Invoice {}
Output
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().

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
Bike with 2 wheels
Lorry with 6 wheels, carries 18.5 t