PHP Inheritance
What Is Inheritance? In simple terms: Inheritance lets one class build on another. A child class automatically receives the properties and methods of its parent class, and can add new ones or change h
What Is Inheritance?
In simple terms: Inheritance lets one class build on another. A child class automatically receives the properties and methods of its parent class, and can add new ones or change how inherited ones behave. A savings account is a bank account with an extra feature (interest); instead of rewriting everything a bank account does, the savings account class extends the account class.
Technically: A class declared with extends ParentClass inherits all public and protected members of the parent. The child can add members, override inherited methods, and call the parent's versions with parent::. PHP supports single inheritance: a class can extend only one parent, but inheritance chains (A → B → C) are possible. Objects of a child class are also instances of the parent class.
Account
(owner, balance, deposit, withdraw)
▲ ▲
│ │
SavingsAccount CurrentAccount
(+ interest) (+ overdraft limit)
Syntax
class ChildClass extends ParentClass
{
// additional or overridden members
}
A Complete Example: Bank Accounts
<?php
class Account
{
protected float $balance;
public function __construct(protected string $owner, float $openingBalance = 0)
{
$this->balance = $openingBalance;
}
public function deposit(float $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Deposit must be positive.');
}
$this->balance += $amount;
}
public function withdraw(float $amount): void
{
if (!$this->canWithdraw($amount)) {
throw new RuntimeException("Insufficient funds for {$this->owner}.");
}
$this->balance -= $amount;
}
public function describe(): string
{
return sprintf('%s | %s | ₹%s', $this->type(), $this->owner, number_format($this->balance, 2));
}
protected function canWithdraw(float $amount): bool
{
return $amount <= $this->balance;
}
protected function type(): string
{
return 'Account';
}
}
class SavingsAccount extends Account
{
public function __construct(string $owner, float $openingBalance, private float $ratePercent)
{
parent::__construct($owner, $openingBalance);
}
public function addInterest(): void
{
$this->balance += round($this->balance * $this->ratePercent / 100, 2);
}
protected function type(): string
{
return 'Savings';
}
}
class CurrentAccount extends Account
{
public function __construct(string $owner, float $openingBalance, private float $overdraftLimit)
{
parent::__construct($owner, $openingBalance);
}
protected function canWithdraw(float $amount): bool
{
return $amount <= $this->balance + $this->overdraftLimit;
}
protected function type(): string
{
return 'Current';
}
}
$savings = new SavingsAccount('Asha', 10000, 4.0);
$savings->addInterest();
$current = new CurrentAccount('GoCourse Pvt Ltd', 2000, 5000);
$current->withdraw(6000);
echo $savings->describe() . "\n";
echo $current->describe() . "\n";
foreach ([[$savings, 20000], [$current, 2000]] as [$account, $amount]) {
try {
$account->withdraw($amount);
} catch (RuntimeException $e) {
echo "Error: " . $e->getMessage() . "\n";
}
}
Output
Savings | Asha | ₹10,400.00
Current | GoCourse Pvt Ltd | ₹-4,000.00
Error: Insufficient funds for Asha.
Error: Insufficient funds for GoCourse Pvt Ltd.
How It Works
- Inherited behavior:
deposit(),withdraw(), anddescribe()are written once inAccount; both child classes use them without repeating code. - Added behavior:
SavingsAccountaddsaddInterest(), which only savings accounts have. - Overridden behavior:
CurrentAccountoverridescanWithdraw()to allow an overdraft. The inheritedwithdraw()calls$this->canWithdraw(), and because$thisis aCurrentAccount, the child's version runs. This is how a parent method can be customized without being rewritten. - Protected members:
balance,owner,canWithdraw(), andtype()areprotected, so child classes can use and override them, while outside code cannot. - Parent constructor: each child constructor calls
parent::__construct()to letAccountinitialize its own properties.
The current account was allowed to go to −₹4,000 (within its ₹5,000 overdraft), but the next withdrawal of ₹2,000 would exceed the limit.
Calling the Parent Constructor
If a child class defines its own constructor, the parent's constructor is not called automatically. Forgetting parent::__construct() leaves the parent's properties uninitialized:
<?php
class Base
{
protected string $createdBy;
public function __construct(string $user)
{
$this->createdBy = $user;
}
}
class Draft extends Base
{
public function __construct(private string $title)
{
// parent::__construct() is missing
}
public function info(): string
{
return "{$this->title} by {$this->createdBy}";
}
}
try {
echo (new Draft('Intro to PHP'))->info();
} catch (Error $e) {
echo $e->getMessage() . "\n";
}
Output
Typed property Base::$createdBy must not be accessed before initialization
If a child class does not define a constructor at all, the parent's constructor is inherited and used automatically.
Objects of a Child Class Are Also Parent Objects
<?php
class Account {}
class SavingsAccount extends Account {}
$savings = new SavingsAccount();
var_dump($savings instanceof SavingsAccount);
var_dump($savings instanceof Account);
var_dump(get_parent_class($savings));
function printStatement(Account $account): void
{
echo "Statement for " . get_class($account) . "\n";
}
printStatement($savings);
Output
bool(true)
bool(true)
string(7) "Account"
Statement for SavingsAccount
A function that accepts an Account also accepts any subclass. This is the basis of polymorphism: code written for the parent type works with every child type (see Polymorphism).
What Is and Is Not Inherited
| Member | Inherited? | Notes |
|---|---|---|
| Public and protected properties and methods | Yes | Can be used and overridden |
| Private properties and methods | Not accessible | They exist in the object, but only the parent's own code can use them |
| Constants | Yes | Accessible via self::, static::, or ChildClass:: |
| Static members | Yes | See PHP Static Properties |
| Constructor | Yes, if the child has none | Otherwise, call parent::__construct() explicitly |
Inheritance vs Composition
Inheritance models an "is-a" relationship: a savings account is an account. It is often misused for "has-a" or "uses-a" relationships, which are better modeled by composition — one object holding another:
// Inheritance misused: an order is not a logger
class Order extends Logger { /* ... */ }
// Composition: an order service uses a logger
class OrderService
{
public function __construct(private Logger $logger) {}
}
Why Prefer Composition in Many Cases
| Inheritance | Composition |
|---|---|
| Fixed at compile time | Collaborators can be swapped (also in tests) |
Child classes depend on parent internals (protected members) | Objects interact only through public interfaces |
| Only one parent class | Any number of collaborators |
| Changes in the parent can break children | Changes stay local |
A common guideline is "favor composition over inheritance": use inheritance for genuine specializations sharing a common core, and composition for everything else. Interfaces (see PHP Interfaces) and traits (see PHP Traits) cover many cases where inheritance would otherwise be tempting.
Common Mistakes
Forgetting parent::__construct()
Effect: Parent properties remain uninitialized.
Fix: Call the parent constructor from the child constructor.
Using Inheritance to Share Utility Code
Effect: Unrelated classes inherit from a "BaseHelper", creating rigid hierarchies.
Fix: Use composition or traits.
Deep Inheritance Chains
Effect: Behavior is spread over many levels and hard to follow.
Fix: Keep hierarchies shallow (one or two levels is typical).
Accessing Private Parent Members From Children
Effect: The properties appear undefined in the child class.
Fix: Make them protected, or use parent methods.
Overriding Methods With Incompatible Signatures
Effect: Fatal errors (see Method Overriding).
Fix: Keep parameter and return types compatible with the parent method.
Best Practices
- Use inheritance only for true "is-a" relationships with shared behavior.
- Keep shared logic in the parent and specialized logic in child classes.
- Design explicit extension points with
protectedmethods. - Always call
parent::__construct()when overriding the constructor. - Prefer composition and interfaces when classes merely need to use or provide behavior.
Official reference: https://www.php.net/manual/en/language.oop5.inheritance.php