Skip to content
Accueil » [PHP] Inheritance and abstract classes

[PHP] Inheritance and abstract classes

Today, we are going to see together the notion of inheritance in PHP, and what abstract classes can be used for. Surely you have already heard of this concept, but perhaps never dared to use it in your developments? It’s time to put an end to all that, and delve into abstraction. Let’s go !

What is an abstract class

First of all, a definition is essential, to clearly identify the subject we are going to talk about. A class and its methods are said to be abstract when the parent class needs its child class or classes to execute. In practice, you will have a parent class that defines methods, and one or more child classes that will inherit from them.

If you’ve been following this post about interfaces, you might be wondering what the difference is between the two, as the definition is quite similar. However, the concept is quite different:

  • Interfaces are a skeleton, and child classes must implement everything.
  • Abstract classes can contain common information, and child classes can also add what they lack .
  • All methods of an interface are private, whereas those of abstract classes can be of any type.
  • You can implement multiple interfaces, but only inherit from a single abstract class.

This is why we will talk about inheritance: The children will, in addition to being perfectly classic classes, inherit all the properties and methods of their parent class.

How to implement an abstract class

Now that the “why” is established, we have to define the “how”. A direct example of an abstract class will speak louder than a long speech:

<?php

abstract class ParentClass
{
    private const CONTEXT = 'PARENT';

    public function useMeFromChildClass(): string
    {
        return 'I come from ' . $this->getContext();
    }

    public function getContext(): string
    {
        return self::CONTEXT;
    }

    abstract public function overrideMe(): string;
}

class ChildClass extends ParentClass
{
    private const CONTEXT = 'CHILD';

    public function overrideMe(): string
    {
        return 'I come from ' . self::CONTEXT . ', overriding ' . $this->getContext();
    }
}

$child = new ChildClass();

echo $child->useMeFromChildClass();
echo "<br>";
echo $child->overrideMe();

Let’s break down this piece of code together:

  • On commence par définir notre classe parente :
    • It is always defined with the keyword abstract.
    • Inside, we enter our methods, constants and whatever we want. We can also define abstract methods, of which we provide, as in the interfaces, simply the declaration. In this case, the implementation will be mandatory in the child class.
  • Then, we define our child class:
    • As we want it to extend from our parent, we will specify it with the keyword extends
    • We implement what we want, like a classic class, with the constraint of implementing the abstract methods, if existing (in our case, the overrideMe method must be)
    • We can use all the methods and properties of our parent there, if the context allows it
  • Then, when we create a new instance of our child class, we simply call our methods, as if the two classes were one.

So, the following piece of code will show us:

I come from PARENT
I come from CHILD, overriding PARENT

And there you have it, we’ve written our first abstract class. Easy right?

Use case

So yes, I see you coming, that’s all great, we were able to do an example of inheritance, but what’s the point of doing that in everyday life? Well just about anything, you’ll find that inheritance is a very common notion. To convince you and show you that this concept is useful and powerful, let’s try to apply it to a telling example:

Let’s say you want to create a file reader. You would like all your files to be built in the same way, to know which methods to call to read them, to know their information, etc. We could start from the fact that all these files have a common base, and that each would have its specificities. You recognize a little the definition of the introduction? That’s great, let’s see how all of this would materialize in code. I’ll comment it to you live to make it clearer:

Let’s first create our parent, the PlayableFile class:

<?php

abstract class PlayableFile
{
    public function __construct(private string $name, private string $extension)
    {
        // we instantiate 2 properties, as well as their respective getters
    }

    public function getExtension(): string
    {
        return $this->extension;
    }

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

    // then we declare 2 abstract methods, which the children will have to implement

    abstract public function play(): void;

    abstract public function info(): string;
}

Now that our abstract class is available, let’s build two children for it. The first, which would correspond for example to an mp3 type file:

<?php

class Mp3 extends PlayableFile
{
    private const EXT = 'mp3';

    public function __construct(private string $name, private int $duration)
    {
        // We call our parent constructor with the specific information we have
        parent::__construct($this->name, self::EXT);
    }

    public function getDuration(): int
    {
        return $this->duration;
    }

    public function play(): void
    {
        $mp3 = file_get_contents('/etc/files/' . $this->getName() . '.' . $this->getExtension()); // name and extension from parent
        $this->playMp3File($mp3);
    }

    private function playMp3File(string $mp3): void
    {
        // do whatever is relevant for it
    }

    public function info(): string
    {
        return 'I am ' . $this->getName() . ', my duration is ' . $this->getDuration();
    }
}

Then, more simplistically, a class corresponding to the gif format:

<?php

class Gif extends PlayableFile
{
    public const EXT = 'gif';

    public function play(): void
    {
        $gif = file_get_contents('/etc/files/' . $this->getName() . '.' . $this->getExtension()); // name and extension from parent
    }

    public function info(): string
    {
        return 'I am ' . $this->getName();
    }
}

There you go, now that our entire ecosystem is ready, all you have to do is test it! Let’s take this simple piece of code, which loops over our different children:

<?php

use Mp3;
use Gif;
use PlayableFile;

$mp3 = new Mp3(
    name: 'highway-to-hell.mp3',
    duration: '3343'
);

$gif = new Gif(
    name: 'trololo.gif',
    extension: Gif::EXT
);

foreach ([$mp3, $gif] as $child) {
    assert($child instanceof PlayableFile);
    //
    echo $child->info() . '<br>';
}

Unsurprisingly, the result will then be:

I am highway-to-hell.mp3, my duration is 3343
I am trololo.gif

Conclusion

Finally, we have seen what an abstract class is, falling within the notion of inheritance, as well as its use in a practical case. Do not hesitate to take this concept in hand and apply it wherever its usefulness is felt. You will gain in code quality and lose in technical debt.

It is also very interesting to to look into the interfaces in PHP, which you can perfectly combine with abstraction, to have even more modular and flexible applications.

Leave a Reply

Your email address will not be published. Required fields are marked *