Don't understand why abstract classes are used? Practical example in Laravel/PHP

Video thumbnail

I want to make a slightly interesting video here about abstract classes in PHP. You could say it's a common topic, but in this case, we're working on a Laravel project, and more specifically, using Livewire.

I think this way it's a little more fun to explain the example, since, thanks to all the interactivity Livewire offers, we easily communicate the client with the server through events. So, I think this is easier to understand.

What is an abstract class in PHP?

Basically, an abstract class is a "normal" class—for example, a model, a controller, etc.—but one that can't be instantiated directly. This isn't exclusive to PHP, but to object-oriented programming in general.
So, what's the point of this? Well, that's why I want to give you this example, in case you're a bit of a slob like me (I struggle with it, even though it's part of my complete course on an online store with Laravel and Livewire).

In fact, while I was recording that lecture, I thought,

“Hey, this should be abstract. Why didn't I mark it as abstract?”

So I did, I marked it as abstract, and that's where the idea for this video came from, in case you're as foolish as I was.

The case study: labels in a many-to-many relationship

abstract class TaggableModel extends Model
{
    function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

These methods are defined in that trait, and the important thing is that they receive an entity of type TaggableModel. This model, in turn, inherits from Model and is abstract, which is the fun part here:

<?php
trait TaggableTrait
{
    

    // tags
    public function save(TaggableModel $modelTaggable)
    {
        if ($this->tagSelected) {
            $tag = Tag::find($this->tagSelected);
            $this->tagsSelected[$tag->id] = $tag->title;

            if ($modelTaggable) {
                $modelTaggable->tags()->sync(array_keys($this->tagsSelected));
            }
        }
    }
    public function delete(TaggableModel $modelTaggable, int $id)
    {
        unset($this->tagsSelected[$id]);
        if ($modelTaggable) {
            $modelTaggable->tags()->sync(array_keys($this->tagsSelected));
        }
    }
}

In it, we define common methods like tags(), which returns the list of tags in the many-to-many polymorphic relationship.

Why use an abstract class?

This is where the magic of using an abstract class comes in.

When working with many-to-many polymorphic relationships, as in this case, where a tag can be assigned to both a Post and a Book, we need the common logic to be grouped together, but without allowing direct instantiation of the base class (TaggableModel).

Look at this example: we use TaggableModel as the type for the methods in the trait, and we reuse it for Book and Post, which inherit from it.
Laravel is happy because a model must inherit from Model, and we are happy because we control which types are allowed:

class Post extends TaggableModel

If we don't use abstract, someone could pass it anything: an integer, a string, an unrelated object, even... the girlfriend. And we don't want that. We want the type to be as strict as possible.

What happens if we try to instantiate an abstract class?

Suppose instead of passing a Post to save(), we directly pass it an instance of TaggableModel. Laravel will try to instantiate it. And what happens? Error: cannot instantiate an abstract class.

That's exactly what we want. We want to make it clear to any developer that this class shouldn't be instantiated directly, because it doesn't represent a real entity on its own. It only exists to be inherited.

In fact, if we remove the abstract from TaggableModel, Laravel will try to instantiate it when it's injected into the component, and then we'll get a different error, such as the associated table doesn't exist, because we haven't created it (nor should we).

Conclusion

With this, you can see more clearly what the heck an abstract class is for, and how to use it in a real-life scenario with Laravel and Livewire. It also makes it clearer what happens if you try to instantiate an abstract class, and how this helps create more modular, clear, and maintainable code.

It's like the classic object-oriented examples with Animal, Dog, Cat, etc. In our case, TaggableModel is Animal, and Post and Book are Dog and Cat.

I agree to receive announcements of interest about this Blog.

We talked about what abstract classes are and a practical example of Traditional Classes VS Abstract Classes.

- Andrés Cruz

En español