PHP OOP - Inheritance

Last revision:

Introduction

Inheritance in OOP is a mechanism that lets you define classes that are based on other classes.

A class that is based on another class is called:

  • child class
  • subclass
  • derived class

A class upon which another class is based is called:

  • parent class
  • superclass
  • base class

Child classes inherit all the public or protected properties and methods that are present in the parent class, but not the private ones. They can override everything they inherit, or add their own properties and methods.

To define a class that is based on another class, use the extends keyword:

Code
<?php

class MyChildClass extends SomeOtherClass {

 // Class implementation goes here.

}

Example: animals

This example illustrates how child class Dog extends parent class Animal.

Code
<?php

declare(strict_types=1);

class Animal {

 public string $name;

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

 public function walk() {
   print("{$this->name} is walking." . PHP_EOL);
 }

}

class Dog extends Animal {
 
 public function bark() {
   print("Woof." . PHP_EOL);
 }

}
Code
<?php

$mydog = new Dog("Jynx");
$mydog->walk();
$mydog->bark();

$mycat = new Animal("Snowball");
$mycat->walk();

Output:

Jynx is walking.
Woof.
Snowball is walking

The Animal class

This class defines:

  • one property: $name
  • a constructor that takes a name as its parameter and sets the $name property
  • one method: walk()

The Dog class

This class inherits from the Animal class:

  • the $name property
  • the constructor
  • the walk() method

This class defines:

  • the bark() method

This class overrides:

  • nothing

→ $mydog is an instance of Dog; $mycat is an instance of Animal.

→ the Dog class inherits everything from the Animal class, so in the Dog class we do not need to redefine the name, constructor, and methods.

→ The Dog class adds the bark() method, so we can call $mydog->bark();

→ The Animal class does not define a bark() method; we can not call $mycat->bark();

This was a basic example with one parent class and one child class. All of the functionality of the parent class Animal is available in the child class Dog, but the child class added an extra bark() method, only available for objects of the type Dog.

Example: shapes

This example illustrates multiple levels of inheritance. We define a Shape class, which is extended by a Rectangle class, which in turn is extended by a Square class:

  • Shape
    • Rectangle
      • Square
Code
<?php

declare(strict_types=1);

class Shape {

  public int $num_sides;
  public float $surface;

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

class Rectangle extends Shape {
  public float $width;
  public float $height;

  public function __construct(float $width, float $height) {
    $this->num_sides = 4;
    $this->width = $width;
    $this->height = $height;
    $this->calculateSurface();
  }

  public function calculateSurface() {
    $this->surface = $this->width * $this->height;
  }

  public function draw() {
    print("▭") . PHP_EOL;
  }
}

class Square extends Rectangle {
  public function __construct(float $side) {
    $this->num_sides = 4;
    $this->width = $side;
    $this->height = $side;
    $this->calculateSurface();
  }

  public function draw() {
    print("□") . PHP_EOL;
  }  

}
// Usage examples.

// Shape object.
$sh = new Shape(6);

// Rectangle object.
$rect = new Rectangle(4, 7);
$rect->draw();
print "Rectangle surface area: {$rect->surface}";

// Print a dividing line.
print(PHP_EOL . "- - - - -" . PHP_EOL);

// Square object.
$sq = new Square(3);
$sq->draw();
print "Square surface area: {$sq->surface}";

// Print empty line.
print (PHP_EOL);

Let's break down what is happening.

The Shape class

This class defines:

  • 2 public properties: $num_sides and $surface
  • a constructor that takes one parameter to set $num_sides.

→ This class doesn't do much yet, but it's already useful to represent any kind of flat shape.

The Rectangle class (extends Shape)

This class inherits:

  • 2 public properties: $num_sides and $surface

→ These two properties are inherited from Shape, so we don't need to redefine them.

This class defines:

  • two properties: $width and $height
  • a calculateSurface() method
  • a draw() method:
    • draws a little rectangle

→ Rectangles always have a width and a height, so we add those two properties.

→ We add a calculateSurface() method and a draw() method. We didn't add those to the Shape class because there is no single way to calculate the surface of any shape, and no single way to draw any shape.

This class overrides:

  • the constructor: 
    • now takes 2 parameters ($width and $height)
    • sets $num_sides to 4
    • calls calculateSurface to set $surface to the calculated surface area

→ Rectangles always have a width and a height, so we let the constructor take 2 parameters (width and height) and initialize the corresponding properties.

→ Rectangles always have 4 sides, so there's no need to use a variable to initialize the number of sides; instead we can set it to the fixed value 4.

→ We call the newly added CalculateSurface() method to calculate the surface and update the $surface property.

the Square class (extends Rectangle)

This class inherits:

  • the properties $num_sides, $surface, $width, and $height
  • the methods draw() and calculateSurface()

→ The Rectangle class inherited properties $num_sides and $surface from Shape, and added $width and $height. The Square class inherits all of these properties from Rectangle.

This class overrides:

  • the constructor:
    • now takes 1 parameter to set the $side property
    • sets the $num_sides property to 4
    • sets the $width and $height properties to the $side parameter
    • calls calculateSurface() to set $surface to the calculated surface area
  • the draw() method:
    • draws a little square

Notes on the constructor:

→ In a square all sides have the same length, so for squares it doesn't make much sense to construct them using width and height parameters. Instead, we construct them with a single parameter: $side.

→ As with rectangles, we set the number of sides to 4.

→ As all sides are the same length, we set properties $width and $height to $side

→ We call calculateSurface() which we did not need to override because the calculation is the same.

Notes on draw():

→ Draws a little square instead of a rectangle.

Protected properties and methods

Child classes inherit all public and protected properties from their parent class.

Let's look at the modified Animal and Dog classes:

Code
<?php

declare(strict_types=1);

class Animal {
 private string $name;

 public function __construct($name) {
   $this->setName($name);
 }

 protected function setName(string $name) {
   $this->name = $name;
 }

 public function getName() {
   return $this->name;
 }
}

class Dog extends Animal {
 public function bark() {
   print("{$this->getName()} is barking: woof!" . PHP_EOL);
 }
}

$mydog = new Dog("Dirk");
$mydog->bark();

print("My dog is named {$mydog->getName()}." . PHP_EOL);

Output:

Dirk is barking. Woof!
My dog is named Dirk.

Let's break this down.

The $name property on Animal is private, so only methods inside the Animal class can directly access the name (via $this->name).

From outside the Animal class, direct access to $name is not possible.

Because we still want people to be able to retrieve the name of any Animal object, we added the public method getName() to Animal. Because that method is public, we can call getName() on Animal objects and on Dog objects (because public methods are inherited).

Animal also provides the setName() method, set to protected:

  • Not set to public because we don't want to allow setting the name outside the class ($myanimal->setName() should not be allowed)
  • We only want to call setName() in the constructor. Making setName() private or protected would allow this.
  • We want to be able to call setName() in child classes like Dog. Making setName() protected allows this; making it private would not.

As you can see, the choice of making your methods and properties public, private, or protected depends on whether you want to make them directly accessible inside and/or outside the class, and in subclasses.

A commonly used approach is to make everything private by default, and then change things to public or protected as needed.

Summary

  • Inheritance allows you to define classes that are based on other classes and "inherit" all their public and protected properties and methods.
  • Private properties and methods from a parent class are not directly accessible in the child class.
  • Use the extends keyword to create a class that is based on another class.
  • Parent classes are also called base classes or superclasses.
  • Child classes are also called derived classes or subclasses.
  • Child classes can override (replace) any method they inherit from their parent class.
  • Child classes can define additional properties and methods