Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 44 of 54

Inheritance and method overriding

Reuse code with class inheritance in PHP, extend a parent class, override methods, call parent methods, use protected members, and stop inheritance with final.

12 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 lessonExtend a class, override and extend its methods, and decide when inheritance is the right tool.

Sharing behaviour between classes

A shop sells physical products, which need shipping, and digital ones, such as an e-book, which do not. Both have a name and a price, and both can calculate tax. Writing two unrelated classes would duplicate all of that. Inheritance lets one class build on another: the child class extends the parent, receives everything the parent has, and adds or changes only what is different.

php
<?php
class Product
{
    public function __construct(
        public string $name,
        protected float $price,
    ) {}

    public function priceWithTax(): float
    {
        return round($this->price * 1.18, 2);
    }

    public function shippingCost(): float
    {
        return $this->price >= 500 ? 0 : 49;
    }

    public function describe(): string
    {
        return "$this->name: " . $this->priceWithTax() . " + shipping " . $this->shippingCost();
    }
}

class DigitalProduct extends Product
{
    public function shippingCost(): float
    {
        return 0;
    }
}

$mat = new Product('Yoga Mat', 24.99);
$ebook = new DigitalProduct('SQL Basics e-book', 9.99);

echo $mat->describe() . "\n";
echo $ebook->describe() . "\n";
result
Yoga Mat: 29.49 + shipping 49
SQL Basics e-book: 11.79 + shipping 0

DigitalProduct has no constructor, no priceWithTax() and no describe() of its own: it inherited them. It only overrides shippingCost(). Notice that describe(), written in the parent, called $this->shippingCost() and got the child's version for the e-book. The method that runs depends on the object's actual class, which is called polymorphism.

A child object is also an instance of the parent: $ebook instanceof Product is true, so code that expects a Product accepts a DigitalProduct too.

protected

price above is protected: outside code cannot touch it, but the child class can. That is the middle level of visibility:

Visibility Inside the class In child classes Outside
public yes yes yes
protected yes yes no
private yes no no

Use private by default and loosen it to protected only when a child genuinely needs it.

Calling the parent with parent::

An overriding method often wants to extend the parent's behaviour rather than replace it. parent::method() calls the parent's version. The most common case is a constructor that needs extra arguments:

php
<?php
class Product
{
    public function __construct(public string $name, protected float $price) {}

    public function label(): string
    {
        return "$this->name (" . number_format($this->price, 2) . ")";
    }
}

class SaleProduct extends Product
{
    public function __construct(string $name, float $price, private int $percentOff)
    {
        parent::__construct($name, $price * (100 - $percentOff) / 100);
    }

    public function label(): string
    {
        return parent::label() . " - {$this->percentOff}% off!";
    }
}

$sale = new SaleProduct('Noise-Cancelling Headphones', 199.99, 25);
echo $sale->label() . "\n";
result
Noise-Cancelling Headphones (149.99) - 25% off!

If a child defines its own constructor, PHP does not call the parent's automatically; call parent::__construct() yourself, usually first.

Method signatures must be compatible

An overriding method must accept at least what the parent accepted and return something compatible. PHP checks this when it loads the class:

php
<?php
class Product
{
    public function shippingCost(): float
    {
        return 49;
    }
}

class Furniture extends Product
{
    public function shippingCost(): string
    {
        return "Quote on request";
    }
}
result
Fatal error: Declaration of Furniture::shippingCost(): string must be compatible with Product::shippingCost(): float in /path/to/script.php on line 12

That rule (the Liskov substitution principle) is what makes polymorphism safe: any code written for a Product can trust that shippingCost() returns a float, whatever kind of product it gets.

final

final before a class stops it from being extended; before a method, it stops that method from being overridden. Use it when a class or method is not designed to be changed by subclasses, as with the Money class in Constructors, typed and readonly properties:

php
<?php
class Order
{
    final public function total(): float
    {
        return 100.0;
    }
}

class DiscountedOrder extends Order
{
    public function total(): float
    {
        return 90.0;
    }
}
result
Fatal error: Cannot override final method Order::total() in /path/to/script.php on line 12

When not to inherit

Inheritance is powerful but easy to overuse. Deep hierarchies (Product > PhysicalProduct > ElectronicProduct > Laptop > ...) become hard to change, because a change at the top ripples down. Some guidelines:

  • Inherit only when the child really is a kind of the parent (an e-book is a product).
  • Keep hierarchies shallow, one or two levels.
  • When classes merely share some behaviour, consider interfaces (next lesson), traits (Traits) or giving one object another object to use (composition), for example a Product that has a ShippingRule object.

A PHP class can extend only one parent, which also pushes you towards those alternatives.

Common mistakes

  • Forgetting parent::__construct() in a child constructor, so the parent's properties are never set.
  • Using private where the child needs access, then getting "Undefined property" errors in the child.
  • Changing a method's return type in the child to something incompatible.
  • Building deep hierarchies for code reuse alone.

Exercise

Create a class Employee with a name and a monthly salary, and a method annualPay() (12 times the salary). Create Manager extends Employee that adds a yearly bonus through its constructor and overrides annualPay() using parent::annualPay(). Print the annual pay of an employee earning 50,000 a month and a manager earning 90,000 with a 200,000 bonus.

Show one possible answer
php
<?php
class Employee
{
    public function __construct(public string $name, protected float $monthlySalary) {}

    public function annualPay(): float
    {
        return $this->monthlySalary * 12;
    }
}

class Manager extends Employee
{
    public function __construct(string $name, float $monthlySalary, private float $bonus)
    {
        parent::__construct($name, $monthlySalary);
    }

    public function annualPay(): float
    {
        return parent::annualPay() + $this->bonus;
    }
}

foreach ([new Employee('Kavya', 50000), new Manager('Farid', 90000, 200000)] as $person) {
    echo $person->name . ": " . number_format($person->annualPay()) . "\n";
}
result
Kavya: 600,000
Farid: 1,280,000

Key points

  • class Child extends ParentClass gives the child every public and protected property and method of the parent.
  • A child can override a method by defining one with the same name; parent::method() calls the original.
  • protected members are visible to the class and its children, not to outside code.
  • final stops a class from being extended or a method from being overridden. Prefer small hierarchies.

Check yourself

1. What does class DigitalProduct extends Product mean?
2. How does an overriding method call the parent's version?
3. Which visibility lets a child class use a property that outside code cannot?
4. How many classes can one PHP class extend directly?