SOLID Software Principles

A Couple Of Words About SOLID Software Principles And A Few Violations With Popular PHP Software

A buzzword and not only in the PHP world is SOLID. Mastering SOLID can help you build high quality software and SOLID stands for five principles:

1. Single Responsibility Principle

SRP is one of the most violated principles because many programmers have the tendency to put as much code as possible in a single method or class. I have seen in production in major software companies what some programmers call the GOD class (a class with or over 10k lines of code). Best practices vary so I’m not gonna enforce you a specific number of lines per method or class. I find these kinds of rigid numbers ridiculous. The point is a method must do a single specific task (let’s say serializing an object) while a class should be a lightweight gathering of such methods focused on a larger but still focused goal. For example a class could be a repository of CRUD methods and each method can be create, view, update and delete.

2. Open/Closed Principle

This is the most difficult to grasp principle in my opinion. The idea is that a class should be open for extension and closed for modification. Basically when you want to add features you need to extend classes rather than modify them. If you search the web you can find a lot of techniques for doing this.

I would like to mention another principle which can be used as a technique for assuring the OCP. This is the most powerful principle I ever heard in the programming world and trust me, many programmers in the PHP world have no idea about it. The principle is called composition over inheritance. Composition over inheritance is a style of writing code which basically implies creating classes and injecting them in other classes (the dependencies) rather than creating classes and using inheritance to extend them. So if you want to add features to a class you can inject another class, let’s say a service that will contain the features.

So, let’s summarize: in old school programming OCP implies adding features by extending classes (inheritance) rather than modifying them directly. Of course inheritance is just one of the tools to assure OCP which can be used together with abstraction and polymorphism

But I mentioned a modern approach: composition over inheritance. Composition over inheritance implies creating dependencies (services or specialized classes) to take care of behavior and it assures OCP if the design is loosely coupled (classes don’t depend on each other – please check the last SOLID principle – the D – abstractions over concrete implementations).

3. Liskov Substitution Principle

LSP states that objects of a super class can be replaced with objects of its subclass without breaking the application. If you’re not familiar with this you probably did not understand a thing.

In practice LSP is all about using shared interfaces. For example I can have a superclass called MainCat and one if its subclasses AngoraCat. Let’s say both classes have the behavior of scratching people (what cats usually do you know). If I want to respect LSP I need to create an interface, let’s say Cat that has one method called scratchPeople. And lastly both MainCat (the superclass) and AngoraCat implement the interface which will force me to also implement the method scratchPeople. In this way I will be able to replace an object of type MainCat with an object of type AngoraCat because both objects have the behavior of scratching people. Of course they will scratch people in different ways (that’s why there are two distinct classes) but the point is the type of behavior is assured regardless of the specific implementation.

4. Interface Segregation Principle

ISP is by far the most simple to understand principle, I find it even more simple than SRP. The definition of it says “Clients should not be forced to depend upon interfaces that they do not use.” In practice this implies to use specialized interfaces rather than general ones. For example I would be better off with two interfaces like Human and Cat than one general interface called Animal in the case where my app will deal exclusively with animals now and forever. If I will deal only with Animals there is no need for a super general interface to define this. The problem with such an interface is it would have to be implemented by all classes and thus it will enforce behavior upon all classes. This is kinda like communism (a dictatorship).

5. Dependency Inversion Principle

DIP simply implies that classes should depend on abstractions and abstractions should not depend on details. In the software world abstractions are interfaces. When you play with modern frameworks you see this all the time. IoC containers or Service containers can work with abstractions: you bind an interface (abstraction or contract how it can be called) to a class (concrete implementation) and when you inject it you type hint the interface, not the implementation. Check out this page from the Laravel framework.

At an interview I discovered a practical advantage I never thought about before: unit tests. You know that when you create unit tests you mock dependencies. If you depend on abstractions not implementations you can change the mocked dependencies as you wish without altering the tests themselves. This is indeed a very powerful principle.

Now for the practical part let me show you just two examples of possible violations using popular PHP software, both violations of SRP.

1. Doctrine annotations. If you ever used Doctrine you know its entities support annotations that can add a lot of behavior. For example you can modify the behavior of the entity using events like PrePersist or PostDelete. If you would create code that would benefit from such features you would obviously violate SRP because the entity should be responsible for data mapping persistence only. You can read more in this stack thread. But guess what? In simple applications I violate SRP by using Doctrine annotations to add behavior to entities. The reason is simple: I’m trying to avoid over architecting the application. There is a principle called KISS (keep is simple stupid). And KISS suggests serious architecture in complex applications (that is the most simple path in those cases) while lightweight architecture in simple applications. KISS is basically the simplest path in your application’s specific case.

2. Laravel firstOrNew, firstOrCreate kinds of methods. Getting the first row in the database (active record object) is one thing, creating a new row in the database (a new active record object) is another thing. This is an undeniable violation of SRP. But before shooting me or calling me a purist please read again the previous point – I violate SRP too sometimes!

And let me present to you my favorite way of violating SRP using Doctrine (things aren’t obvious because I’m slick enough to name my method in a general way). Imagine the following:

private function manageSavedSearches()
{
        $savedSearch = $savedSearchesRepo->findOneBy([
            'keyword' => $autoSearch->getKeyword(),
        ]);
        if (!$savedSearch) {
            $savedSearch = new SavedSearch();
        }
        $savedSearch->setKeyword($autoSearch->getKeyword());
        ...

Have you noticed the manageSavedSearches method? It is actually a createOrUpdate style of method. So please don’t call me a hypocrite because I’m not pretending to obey SOLID all the time.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.