PHP Fundamentals

Inheritance & Polymorphism

13 min Lesson 23 of 45

Understanding Inheritance

Inheritance allows you to create a new class based on an existing class. The new class (child) inherits all properties and methods from the existing class (parent), and can add or override functionality.

Key Terms:
  • Parent Class (Base/Super Class): The class being inherited from
  • Child Class (Derived/Sub Class): The class that inherits
  • extends: Keyword used to create inheritance
  • Inheritance promotes code reuse and establishes relationships between classes

Basic Inheritance Example

Let's create a simple inheritance hierarchy:

<?php // Parent class class Animal { protected $name; protected $age; protected $species; public function __construct($name, $age, $species) { $this->name = $name; $this->age = $age; $this->species = $species; } public function eat() { return "{$this->name} is eating."; } public function sleep() { return "{$this->name} is sleeping."; } public function getInfo() { return "{$this->name} is a {$this->age} year old {$this->species}"; } } // Child class inherits from Animal class Dog extends Animal { private $breed; public function __construct($name, $age, $breed) { // Call parent constructor parent::__construct($name, $age, "Dog"); $this->breed = $breed; } // Add new method specific to Dog public function bark() { return "{$this->name} says: Woof! Woof!"; } // Add another method public function fetch() { return "{$this->name} is fetching the ball!"; } } // Another child class class Cat extends Animal { private $color; public function __construct($name, $age, $color) { parent::__construct($name, $age, "Cat"); $this->color = $color; } public function meow() { return "{$this->name} says: Meow!"; } public function scratch() { return "{$this->name} is scratching the furniture!"; } } // Usage $dog = new Dog("Max", 3, "Labrador"); echo $dog->bark(); // Dog-specific method echo $dog->eat(); // Inherited from Animal echo $dog->sleep(); // Inherited from Animal echo $dog->getInfo(); // Inherited from Animal $cat = new Cat("Whiskers", 2, "Gray"); echo $cat->meow(); // Cat-specific method echo $cat->eat(); // Inherited from Animal ?>
parent::__construct(): Always call the parent constructor to properly initialize inherited properties. Use parent:: to access parent class methods.

Method Overriding

Child classes can override parent methods to provide specialized behavior:

<?php class Animal { protected $name; public function __construct($name) { $this->name = $name; } public function makeSound() { return "{$this->name} makes a sound"; } public function move() { return "{$this->name} is moving"; } } class Dog extends Animal { // Override makeSound method public function makeSound() { return "{$this->name} barks: Woof! Woof!"; } // Override move method public function move() { return "{$this->name} runs on four legs"; } } class Bird extends Animal { // Override makeSound method public function makeSound() { return "{$this->name} chirps: Tweet! Tweet!"; } // Override move method public function move() { return "{$this->name} flies in the sky"; } } class Fish extends Animal { // Override makeSound method public function makeSound() { return "{$this->name} makes bubbles"; } // Override move method public function move() { return "{$this->name} swims underwater"; } } // Each animal behaves differently $dog = new Dog("Max"); echo $dog->makeSound(); // Max barks: Woof! Woof! echo $dog->move(); // Max runs on four legs $bird = new Bird("Tweety"); echo $bird->makeSound(); // Tweety chirps: Tweet! Tweet! echo $bird->move(); // Tweety flies in the sky $fish = new Fish("Nemo"); echo $fish->makeSound(); // Nemo makes bubbles echo $fish->move(); // Nemo swims underwater ?>

Understanding Polymorphism

Polymorphism means "many forms." It allows objects of different classes to be treated as objects of a common parent class, while still maintaining their unique behaviors.

<?php // Polymorphism in action function describeAnimal(Animal $animal) { echo $animal->makeSound() . "\n"; echo $animal->move() . "\n"; } $dog = new Dog("Buddy"); $bird = new Bird("Chirpy"); $fish = new Fish("Goldie"); // Same function handles different animal types describeAnimal($dog); // Dog behavior describeAnimal($bird); // Bird behavior describeAnimal($fish); // Fish behavior ?>
Polymorphism Benefits:
  • Code Flexibility: Write functions that work with any subclass
  • Extensibility: Add new classes without changing existing code
  • Maintainability: Easier to manage and update code

Real-World Example: Employee System

Let's build a practical employee management system:

<?php class Employee { protected $name; protected $id; protected $baseSalary; protected $department; public function __construct($name, $id, $baseSalary, $department) { $this->name = $name; $this->id = $id; $this->baseSalary = $baseSalary; $this->department = $department; } public function getName() { return $this->name; } public function getId() { return $this->id; } // Can be overridden by child classes public function calculateSalary() { return $this->baseSalary; } public function getDetails() { return "ID: {$this->id}, Name: {$this->name}, Department: {$this->department}"; } // Can be overridden for different employee types public function getRole() { return "Employee"; } } class Manager extends Employee { private $bonus; private $teamSize; public function __construct($name, $id, $baseSalary, $department, $teamSize) { parent::__construct($name, $id, $baseSalary, $department); $this->teamSize = $teamSize; $this->bonus = 0; } public function setBonus($amount) { $this->bonus = $amount; } // Override salary calculation public function calculateSalary() { return $this->baseSalary + $this->bonus; } // Override role public function getRole() { return "Manager (Team of {$this->teamSize})"; } public function conductMeeting() { return "{$this->name} is conducting a team meeting"; } } class Developer extends Employee { private $programmingLanguages; private $projectBonus; public function __construct($name, $id, $baseSalary, $department, $languages) { parent::__construct($name, $id, $baseSalary, $department); $this->programmingLanguages = $languages; $this->projectBonus = 0; } public function addProjectBonus($amount) { $this->projectBonus += $amount; } // Override salary calculation public function calculateSalary() { return $this->baseSalary + $this->projectBonus; } // Override role public function getRole() { return "Developer (" . implode(", ", $this->programmingLanguages) . ")"; } public function writeCode() { return "{$this->name} is writing code in " . implode(", ", $this->programmingLanguages); } } class Intern extends Employee { private $mentor; private $duration; // months public function __construct($name, $id, $baseSalary, $department, $mentor, $duration) { parent::__construct($name, $id, $baseSalary, $department); $this->mentor = $mentor; $this->duration = $duration; } // Override salary calculation (interns might get reduced salary) public function calculateSalary() { return $this->baseSalary * 0.6; // 60% of base salary } // Override role public function getRole() { return "Intern ({$this->duration} months program)"; } public function learn() { return "{$this->name} is learning from mentor: {$this->mentor}"; } } // Function using polymorphism function printEmployeeReport(Employee $employee) { echo "=== Employee Report ===\n"; echo $employee->getDetails() . "\n"; echo "Role: " . $employee->getRole() . "\n"; echo "Salary: $" . number_format($employee->calculateSalary(), 2) . "\n"; echo "===================\n\n"; } // Create different employees $manager = new Manager("Ahmed Ali", "M001", 8000, "Engineering", 10); $manager->setBonus(2000); $dev = new Developer("Sara Hassan", "D001", 6000, "Engineering", ["PHP", "JavaScript", "Python"]); $dev->addProjectBonus(1000); $intern = new Intern("Omar Khalil", "I001", 2000, "Engineering", "Sara Hassan", 6); // Use polymorphism - same function for all employee types printEmployeeReport($manager); printEmployeeReport($dev); printEmployeeReport($intern); // Type-specific methods echo $manager->conductMeeting() . "\n"; echo $dev->writeCode() . "\n"; echo $intern->learn() . "\n"; ?>

The final Keyword

Use final to prevent classes from being inherited or methods from being overridden:

<?php // Cannot be extended final class Configuration { private $settings; public function get($key) { return $this->settings[$key] ?? null; } } // This would cause an error: // class CustomConfig extends Configuration {} // Error! class User { protected $name; // Cannot be overridden in child classes final public function getId() { return $this->id; } // Can be overridden public function getRole() { return "User"; } } class Admin extends User { // This is fine public function getRole() { return "Administrator"; } // This would cause an error: // public function getId() {} // Error: Cannot override final method } ?>
When to use final: Use it for critical methods that must not be changed (security, licensing, core functionality) or for classes that should not be extended.

Abstract Classes

Abstract classes serve as templates and cannot be instantiated directly:

<?php abstract class Shape { protected $color; public function __construct($color) { $this->color = $color; } public function getColor() { return $this->color; } // Abstract method - must be implemented by child classes abstract public function calculateArea(); abstract public function calculatePerimeter(); // Concrete method - can be used as-is public function describe() { return "A {$this->color} shape with area " . $this->calculateArea(); } } class Rectangle extends Shape { private $width; private $height; public function __construct($color, $width, $height) { parent::__construct($color); $this->width = $width; $this->height = $height; } // Must implement abstract methods public function calculateArea() { return $this->width * $this->height; } public function calculatePerimeter() { return 2 * ($this->width + $this->height); } } class Circle extends Shape { private $radius; public function __construct($color, $radius) { parent::__construct($color); $this->radius = $radius; } public function calculateArea() { return pi() * $this->radius ** 2; } public function calculatePerimeter() { return 2 * pi() * $this->radius; } } // Cannot do this: // $shape = new Shape("red"); // Error: Cannot instantiate abstract class // Can do this: $rect = new Rectangle("blue", 10, 5); echo $rect->describe(); // A blue shape with area 50 echo $rect->calculatePerimeter(); // 30 $circle = new Circle("red", 7); echo $circle->describe(); // A red shape with area 153.94 echo $circle->calculatePerimeter(); // 43.98 ?>
Abstract Classes vs Regular Classes:
  • Abstract: Cannot be instantiated, can have abstract methods
  • Regular: Can be instantiated, all methods must be implemented
  • Use abstract: When you want to define a common interface but force child classes to implement specific methods

Multi-Level Inheritance

Classes can inherit from classes that inherit from other classes:

<?php class Vehicle { protected $brand; public function __construct($brand) { $this->brand = $brand; } public function start() { return "Starting {$this->brand} vehicle..."; } } class Car extends Vehicle { protected $doors; public function __construct($brand, $doors) { parent::__construct($brand); $this->doors = $doors; } public function honk() { return "Beep beep!"; } } class ElectricCar extends Car { private $batteryCapacity; public function __construct($brand, $doors, $batteryCapacity) { parent::__construct($brand, $doors); $this->batteryCapacity = $batteryCapacity; } public function charge() { return "Charging {$this->brand} with {$this->batteryCapacity}kWh battery"; } // Can access methods from all ancestors public function displayInfo() { return $this->start() . " " . $this->honk() . " " . $this->charge(); } } $tesla = new ElectricCar("Tesla", 4, 100); echo $tesla->start(); // From Vehicle echo $tesla->honk(); // From Car echo $tesla->charge(); // From ElectricCar echo $tesla->displayInfo(); // Uses all three ?>
Exercise:

Create a payment processing system with inheritance:

  • Abstract class Payment with properties: amount, currency, status
  • Abstract methods: processPayment(), validatePayment()
  • Concrete method: getDetails()
  • Class CreditCardPayment extends Payment with: cardNumber, expiryDate, cvv
  • Class PayPalPayment extends Payment with: email, transactionId
  • Class BankTransferPayment extends Payment with: accountNumber, bankName
  • Implement all abstract methods with appropriate logic
  • Create a function processPayments(array $payments) that uses polymorphism

Best Practices

Important Guidelines:
  • Favor composition over inheritance: Sometimes it's better to have objects contain other objects rather than inherit
  • Keep inheritance shallow: Avoid deep inheritance chains (3+ levels)
  • Use protected for shared data: Allow child classes to access what they need
  • Call parent constructors: Always call parent::__construct() when overriding
  • Document inheritance: Make it clear what methods should/shouldn't be overridden

Summary

In this lesson, you learned:

  • How inheritance creates parent-child relationships between classes
  • How to use the extends keyword
  • Method overriding to specialize behavior
  • Polymorphism and its benefits
  • The final keyword to prevent inheritance/overriding
  • Abstract classes as templates for child classes
  • Multi-level inheritance hierarchies
  • Best practices for effective inheritance

Next, we'll learn about static members and constants for class-level data and functionality!