In the world of Object-Oriented Programming (OOP), inheritance is a fundamental concept that allows developers to create new classes based on existing ones. This powerful feature promotes code reusability and helps in building hierarchical relationships between classes. In PHP, inheritance is implemented using the extends keyword, enabling developers to create more specialized classes from general ones.

Understanding Inheritance in PHP

Inheritance in PHP allows a class (called the child or derived class) to inherit properties and methods from another class (called the parent or base class). This mechanism facilitates the creation of a new class that is a modified version of an existing class, without altering the original class.

Let's dive into a practical example to illustrate this concept:

class Animal {
    protected $name;
    protected $sound;

    public function __construct($name, $sound) {
        $this->name = $name;
        $this->sound = $sound;
    }

    public function makeSound() {
        return "{$this->name} says {$this->sound}!";
    }
}

class Dog extends Animal {
    public function fetch() {
        return "{$this->name} is fetching the ball!";
    }
}

$dog = new Dog("Buddy", "Woof");
echo $dog->makeSound() . "\n";
echo $dog->fetch();

In this example, we have a base class Animal with properties $name and $sound, and a method makeSound(). The Dog class extends Animal, inheriting its properties and methods while adding a new method fetch().

Output:

Buddy says Woof!
Buddy is fetching the ball!

🔑 Key Point: The Dog class inherits the makeSound() method from Animal, allowing us to call it on a Dog object without redefining it.

Overriding Methods

One of the powerful features of inheritance is the ability to override methods from the parent class in the child class. This allows you to provide a specific implementation for a method in the child class while keeping the same method name.

Let's extend our previous example:

class Cat extends Animal {
    public function makeSound() {
        return parent::makeSound() . " Purr...";
    }

    public function climb() {
        return "{$this->name} is climbing the tree!";
    }
}

$cat = new Cat("Whiskers", "Meow");
echo $cat->makeSound() . "\n";
echo $cat->climb();

Output:

Whiskers says Meow! Purr...
Whiskers is climbing the tree!

In this example, the Cat class overrides the makeSound() method. It calls the parent method using parent::makeSound() and then adds its own behavior.

💡 Pro Tip: Use the parent:: keyword to call the parent class's method within an overridden method in the child class.

The final Keyword

PHP provides the final keyword, which can be used to prevent child classes from overriding a method or to prevent a class from being inherited altogether.

class Vehicle {
    final public function start() {
        return "The vehicle is starting.";
    }
}

class Car extends Vehicle {
    // This will cause a fatal error
    // public function start() {
    //     return "The car is starting.";
    // }
}

final class Bicycle {
    public function pedal() {
        return "Pedaling the bicycle.";
    }
}

// This will cause a fatal error
// class MountainBike extends Bicycle {}

$car = new Car();
echo $car->start();

Output:

The vehicle is starting.

🚫 Important: Using final on a method prevents it from being overridden in child classes, while using final on a class prevents it from being inherited at all.

Multiple Inheritance and Interfaces

PHP does not support multiple inheritance, meaning a class cannot extend more than one class. However, PHP provides interfaces as a workaround to achieve similar functionality.

interface Swimmable {
    public function swim();
}

interface Flyable {
    public function fly();
}

class Bird extends Animal implements Flyable {
    public function fly() {
        return "{$this->name} is soaring through the sky!";
    }
}

class Duck extends Animal implements Swimmable, Flyable {
    public function swim() {
        return "{$this->name} is paddling in the pond!";
    }

    public function fly() {
        return "{$this->name} is flying low over the water!";
    }
}

$bird = new Bird("Tweety", "Tweet");
$duck = new Duck("Donald", "Quack");

echo $bird->makeSound() . "\n";
echo $bird->fly() . "\n";
echo $duck->makeSound() . "\n";
echo $duck->swim() . "\n";
echo $duck->fly();

Output:

Tweety says Tweet!
Tweety is soaring through the sky!
Donald says Quack!
Donald is paddling in the pond!
Donald is flying low over the water!

🔧 Interfaces act as a contract, specifying methods that implementing classes must define.

Abstract Classes

Abstract classes serve as a blueprint for other classes. They may contain abstract methods (methods without a body) that must be implemented by child classes.

abstract class Shape {
    protected $color;

    public function __construct($color) {
        $this->color = $color;
    }

    abstract public function getArea();

    public function getColor() {
        return $this->color;
    }
}

class Circle extends Shape {
    private $radius;

    public function __construct($color, $radius) {
        parent::__construct($color);
        $this->radius = $radius;
    }

    public function getArea() {
        return pi() * $this->radius * $this->radius;
    }
}

class Rectangle extends Shape {
    private $width;
    private $height;

    public function __construct($color, $width, $height) {
        parent::__construct($color);
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }
}

$circle = new Circle("Red", 5);
$rectangle = new Rectangle("Blue", 4, 6);

echo "Circle: Color - " . $circle->getColor() . ", Area - " . $circle->getArea() . "\n";
echo "Rectangle: Color - " . $rectangle->getColor() . ", Area - " . $rectangle->getArea();

Output:

Circle: Color - Red, Area - 78.539816339745
Rectangle: Color - Blue, Area - 24

🎨 Abstract classes can contain both abstract and concrete methods, providing a mix of defined functionality and required implementations.

Inheritance and Constructors

When a child class is instantiated, PHP will automatically call the parent class's constructor if the child class doesn't define its own constructor. If the child class defines its own constructor, you need to explicitly call the parent constructor using parent::__construct() if you want to run the parent's initialization code.

class Product {
    protected $name;
    protected $price;

    public function __construct($name, $price) {
        $this->name = $name;
        $this->price = $price;
    }

    public function getInfo() {
        return "Product: {$this->name}, Price: ${$this->price}";
    }
}

class DiscountedProduct extends Product {
    private $discountPercentage;

    public function __construct($name, $price, $discountPercentage) {
        parent::__construct($name, $price);
        $this->discountPercentage = $discountPercentage;
    }

    public function getDiscountedPrice() {
        $discount = $this->price * ($this->discountPercentage / 100);
        return $this->price - $discount;
    }

    public function getInfo() {
        $parentInfo = parent::getInfo();
        return $parentInfo . ", Discounted Price: $" . $this->getDiscountedPrice();
    }
}

$regularProduct = new Product("Laptop", 1000);
$discountedProduct = new DiscountedProduct("Smartphone", 500, 20);

echo $regularProduct->getInfo() . "\n";
echo $discountedProduct->getInfo();

Output:

Product: Laptop, Price: $1000
Product: Smartphone, Price: $500, Discounted Price: $400

💼 Best Practice: Always call the parent constructor in the child class constructor when you're extending a class, unless you have a specific reason not to.

Inheritance and Visibility

PHP uses three visibility keywords: public, protected, and private. These keywords affect how properties and methods are inherited:

  • public: Accessible from anywhere
  • protected: Accessible within the class and its child classes
  • private: Accessible only within the defining class

Let's see how this works in practice:

class BaseClass {
    public $publicVar = "Public";
    protected $protectedVar = "Protected";
    private $privateVar = "Private";

    public function publicMethod() {
        return "Public method";
    }

    protected function protectedMethod() {
        return "Protected method";
    }

    private function privateMethod() {
        return "Private method";
    }
}

class ChildClass extends BaseClass {
    public function testAccess() {
        echo $this->publicVar . "\n";
        echo $this->protectedVar . "\n";
        // This will cause an error
        // echo $this->privateVar . "\n";

        echo $this->publicMethod() . "\n";
        echo $this->protectedMethod() . "\n";
        // This will cause an error
        // echo $this->privateMethod() . "\n";
    }
}

$child = new ChildClass();
$child->testAccess();

Output:

Public
Protected
Public method
Protected method

🔒 Remember: Private members are not inherited by child classes and are only accessible within the class they are defined in.

Conclusion

Inheritance is a powerful feature in PHP OOP that allows for code reuse and the creation of hierarchical relationships between classes. By understanding and effectively using inheritance, you can write more efficient, organized, and maintainable code.

Key takeaways:

  • Use the extends keyword to create child classes
  • Override methods in child classes to provide specific implementations
  • Use final to prevent method overriding or class inheritance
  • Implement interfaces for a form of multiple inheritance
  • Use abstract classes as blueprints for other classes
  • Be mindful of constructor behavior in inheritance
  • Understand how visibility affects inheritance

By mastering these concepts, you'll be well on your way to becoming a proficient PHP developer, capable of creating complex and efficient object-oriented systems.

Happy coding! 🚀💻