SOLID DESIGN PRINCIPLES IN PHP
Faizan Khan
Senior PHP Developer | Symfony Expert | Microservices | NestJS | OpenAI | AWS | Docker | GIT
This post is to provide an easy to understand idea about the implementation of one of the most “talked about” principles of Object Oriented Programming - SOLID principles. As a programmer, there might come a time when we are so consumed in finishing the task at hand that we forget the world around us. And in the very same world, somewhere lost are the programming principles that we read about and planned to use next time. So let’s try to look at the SOLID principles in one of the simplest way possible so that we can practice them while coding.
So let’s dig in to the SOLID principles at a glance.
- Single Responsibility: A Class should be responsible for a single task.
- Open-Close Principle: A Class should be open to extension and close to modification.
- Liskov Substitution: A derived Class can be substituted at places where base Class is used.
- Interface Segregation: Don’t make FAT Interfaces. i.e. Classes don’t have to override extra agreements that are not needed for that Class simply because it is there in interface.
- Dependency Inversion: Depend on abstractions, not on concretions. Not only high level Classes but low level Classes also depend on the abstractions in order to decouple the code.
Single Responsibility
The easiest one to follow and put into practice. It’s very straight forward. e.g. I need to make an API end point in back-end which receives a request, does authentication, validates the received data, does query on database, makes a formatted response and finally, send back the response. All these tasks look trivial. So, the Single Responsibility Principle (SRP) just asks us to do these tasks in separate Classes, instead of, making functions in a single Class and doing everything at one place. If we keep all the things in one file, it will be like a kid who’s playing and messing up things and enjoying his life. Lets not be lazy to create the Classes. They take a little extra effort but it will make life easier for you and the coders who will read your code later.
Open-Close Principle
It ensures that the code is open for extension but closed for modification. It reduces the chances of code rot i.e. editing the same piece of code again and again. It is very helpful in cases when the requirements may keep getting added in the future.
For example, an authentication module which requires users with just username and password on your website to login. You code a login module and perform login for that user.
However, you may now want the users to be able to login using Google, Facebook or other platforms, where they have already logged in and they bring with them a custom access token with which they can login. Now you have to put checks on what kind of user is that and based on that, you proceed to write the login logic.
Liskov Substitution
It states that any implementation of an abstraction (interface) should be substitutable in any place that the abstraction is accepted. Basically it takes care that while coding using interfaces in our code, we not only have a contract of input that the interface receives but also the output returned by different Classes implementing that interface; they should be of same type.
e.g. Two different Classes implementing an interface return different type of values(array, collection, list). Now we will be forced to put conditional statements to check what kind of value is returned from the method we called. If its an array do this, else, if its a collection do that and so on. This kind of checks won’t be necessary if we follow Liskov substitution principle and make sure that returned exception types(if any) and the values have uniform return types.
Interface Segregation Principle
It states that a client must not be forced to implement an interface that it doesn’t use. It will make sure that Classes are sharing only the knowledge that is required and not just bloating themselves, just because we committed to code to interface.
At this point, I would like to name one more jargon, Fat Interface. So what is Fat Interface, basically, and interface becomes fat when it’s handling too many contracts. A fat interface violates Single Responsibility Principle too as it’s handling more than one responsibilities at a time.
Dependency Inversion Principle
It states that High level modules should never depend on Low level modules, instead the High level module can depend upon an abstraction and the Low level module depends on that same abstraction. It’s not the simplest statement that we have come across. In very simple words… nope, a statement this complex can’t be simplified.
So let’s take a simple example, suppose there is a PasswordReminder Class which can be a high level module(a module that doesn’t focus on detailed implementation) requires access to dbConnection property. Now how about we just inject the MySQLConnection Class to the constructor of PasswordReminder Class(dependency injection) and give it access to dbConnection property. But this will make the High level module(PasswordReminder) depend on low level module(MySQLConnection). But does the PasswordReminder need the knowledge on connection type? Nope. It doesn’t.