PHP Inheritance

Reuse code by extending classes. Master extends, parent::, method overriding, abstract classes, final, and the principle that should temper your enthusiasm: composition over inheritance.

Intermediate 9 min read 8 examples

Extending a Class

PHP
<?php
class Animal {
    public function __construct(public string $name) {}

    public function speak(): string {
        return "Some sound";
    }
}

class Dog extends Animal {
    public function speak(): string {
        return "Woof!";
    }
}

$d = new Dog("Rex");
echo $d->name;       // Rex   <- inherited
echo $d->speak();    // Woof! <- overridden

var_dump($d instanceof Dog);     // true
var_dump($d instanceof Animal);  // true (also a parent!)

Calling parent::

PHP
<?php
class Logger {
    public function __construct(protected string $prefix) {}

    public function log(string $msg): void {
        echo "[{$this->prefix}] $msg\n";
    }
}

class TimestampLogger extends Logger {
    public function __construct(string $prefix) {
        parent::__construct($prefix);    // CALL PARENT - or fields stay unset
    }

    public function log(string $msg): void {
        $time = date("H:i:s");
        parent::log("$time | $msg");      // call parent's implementation
    }
}

(new TimestampLogger("APP"))->log("Started");
// [APP] 14:33:12 | Started
Always call parent::__construct

If your child class defines a constructor, the parent's constructor will NOT run unless you call it explicitly. Forget this and the parent's properties stay uninitialized - a common source of TypeErrors with typed properties.

Method Overriding

PHP
<?php
class PaymentMethod {
    public function fee(float $amount): float {
        return 0.0;          // default: no fee
    }
}

class CreditCard extends PaymentMethod {
    public function fee(float $amount): float {
        return $amount * 0.029 + 0.30;   // 2.9% + $0.30
    }
}

class BankTransfer extends PaymentMethod {
    public function fee(float $amount): float {
        return min(5.00, $amount * 0.01); // 1% capped at $5
    }
}

function checkout(PaymentMethod $method, float $amount): float {
    return $amount + $method->fee($amount);
}

echo checkout(new CreditCard(),   100);  // 103.20
echo checkout(new BankTransfer(), 100);  // 101.00

Abstract Classes & Methods

An abstract class cannot be instantiated. It exists to be extended:

PHP
<?php
abstract class Shape {
    abstract public function area(): float;
    abstract public function perimeter(): float;

    // Concrete shared method
    public function describe(): string {
        return sprintf(
            "Area: %.2f, Perimeter: %.2f",
            $this->area(),
            $this->perimeter()
        );
    }
}

class Circle extends Shape {
    public function __construct(public float $radius) {}

    public function area(): float {
        return M_PI * $this->radius ** 2;
    }

    public function perimeter(): float {
        return 2 * M_PI * $this->radius;
    }
}

// new Shape();             // FATAL - cannot instantiate abstract
$c = new Circle(5);
echo $c->describe();

final - Prevent Override

PHP
<?php
// final class - cannot be extended
final class Money {
    public function __construct(public readonly int $cents) {}
}

// class Wallet extends Money {}   // FATAL

// final method - cannot be overridden
class Container {
    public final function id(): string {
        return spl_object_hash($this);
    }
}

class Child extends Container {
    // public function id(): string { ... }   // FATAL
}

Polymorphism

Code that operates on the parent type automatically works with any subtype - the key benefit of OOP:

PHP
<?php
function printShapes(array $shapes): void {
    foreach ($shapes as $shape) {
        echo $shape->describe() . "\n";
    }
}

$mix = [
    new Circle(5),
    new Square(4),
    new Triangle(3, 4, 5),
];

printShapes($mix);
// No need to know concrete types - all answer area()/perimeter()

Composition Over Inheritance

A famous design principle. Before reaching for extends, ask "could this be a field instead?":

PHP
<?php
// ANTI-PATTERN - inheritance for code reuse
class Database {
    public function query(string $sql) { /* ... */ }
}
class UserService extends Database {  // UserService IS-A Database? No!
    public function find(int $id) { return $this->query("..."); }
}

// BETTER - composition
class UserService {
    public function __construct(private Database $db) {}

    public function find(int $id) {
        return $this->db->query("...");
    }
}
// Now UserService is easy to test - inject a fake $db
When to inherit

Inherit only when the child is a true subtype of the parent - a Dog IS-A Animal, a Manager IS-A Employee. Otherwise compose. The Liskov Substitution Principle says: anywhere the parent is used, the child should work without surprises.

Next Steps

Frequently Asked Questions

No. PHP supports single inheritance only - one parent class. For multiple sources of behavior use interfaces (multiple allowed) or traits (mixed into the class body).

An abstract class can have implemented methods and state - you inherit from it. An interface is a pure contract with no state - you implement it. Use abstract when subclasses share code, interfaces when they share only a contract.

By default - yes. final class blocks unintended inheritance, making the API smaller and intent clearer. Only remove final if you genuinely designed the class to be extended.

Never automatically. If you write a child __construct(), you must call parent::__construct() yourself - usually as the first line. If the child has no constructor, the parent's runs.

Inheritance locks you into a rigid hierarchy and exposes parent internals. Composition - having a class hold another instead of being it - is more flexible and easier to test. Use inheritance for true is-a relationships, composition for has-a.