S.O.L.I.D. and OpenCart
Sorry for my English! English is not my native language. One of the reasons to create this blog is to improve my English writing. So I will be highly obliged if you will help me with this. If you find a grammar error on this page, please select it with your mouse and press Ctrl+Enter.
S.O.L.I.D. are programming principles that greatly help to write large applications, making them more flexible, extensible, while reducing the number of errors and conflicts, as well as the time and money to support and maintain this application in the future.
The principles were described back in 2000 by Robert Martin; you've most likely heard of his books, such as Clean Code and Clean Architecture.
These principles are so important that many employers even indicate their knowledge in vacancies as a prerequisite for applying for a job.
I will try to describe these principles in my own words, as simply and clearly as possible.
As you might guess from the name, there are only 5 principles based on the first letters of the name:
S - Single Responsibility Principle
O - Open-closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Single Responsibility Principle
A fairly simple, understandable but at the same time very important, fundamental principle.
He says that any software entity (class, method, function, etc.) should have only one responsibility, that is, solve only one problem. But, Robert Martin initially put a slightly different meaning into this principle.
This principle is very often violated in OpenCart, when everything possible is added to the controller - this includes data validation and the creation of errors, breadcrumbs, pagination, images and in general all the logic, since the model only receives data from the database, and everything else is located in the controller. Because of this, we get a fairly common MVC template error - a very large controller, which is wrong, the controller should be very simple and lightweight, its main task is to receive a request from the user (Request), pass it to someone else for processing and the result return it back to the user (Response), but it should not do all the work at all.
Another very important principle, which is also completely ignored by opencard.
It says that software entities should be open for extension and closed for modification of code
So, these entities should be able to expand their functionality, but not through directly changing their code, but through other methods, for example through inheritance, interfaces, dependency injection, Events or in some systems hooks, modules and plugins for CMS, and so on .
In OpenCart, in fact, the entire system of extensions is built on modifiers (ocmod) or on changing the code of the engine itself, that is a complete violation of this principle.
Liskov Substitution Principle
It says that if some software entity works with an object of the parent’s class, then it should also work with all the descendants of this parent without any code changes. So that instead of the parent’s class we can substitute the class of any of its descendants and our program should work exactly the same as it worked before. This also improves the extensibility of the application and is intertwined with the second principle - open/closed.
How is this achieved? For example, by controlling all data returned by class methods, if a parent method returns an array of a certain format, then all descendants must also return exactly the same array format. Or if some method is implemented in the parent, then this method must be implemented by all children, and so on.
Interface Segregation Principle
It says that if a class implements an interface, then it must implement absolutely all of its methods, and not just some of them.
For example, if we need to create a class and we see that we already have an interface that is very similar to the one we need, for example there are 10 methods of which we need 8, then we can't use this interface for this class and we need to divide this interface into several smaller ones and use exactly what we need (especially since PHP supports class implementation of several interfaces).
Otherwise, we will get a situation when some of our methods working with this interface will assume that in any class that implements it, some method must be implemented, but in some classes it will be implemented, and in others, for example, an exception will be thrown and we will have to write a bunch of conditions in a bunch of places in our program and keep in mind all the logic in which class, which methods are implemeted and which not.. why? if the whole idea of interfaces is to protect us from all these problems.
Dependency Inversion Principle
Another extremely important principle that is perfectly implemented in Laravel through service containers.
In simple terms, it says that all dependencies in an application should be built on the basis of interfaces, and not through specific implementations.
I'll explain better with an example.
We have an application that stores data in a database. For this we have the DB class and the select() methods for getting data from the database and insert() for saving. Everything works great, but at one time we (or the customer) needed to implement saving data in, for example, files. And we even have a ready-made class for this FileStorage with read() and write() methods respectively. But the trouble is that we now need to rewrite a pile of code in dozens of files to replace not only all the names of the methods but also their arguments and return values. Now imagine how much easier it would be if everything was implemented through interfaces and we had the StorageInterface interface, which would have methods such as get() and set(), and everywhere in the code we use these methods. Now, if we need to store data in the database, we create a DB class that implements our StorageInterface interface with certain methods, and for files we create another class that implements the same interface with the same methods. If in the future we need to work with LLM and save data in some kind of vector database, for example. Chroma or Pinecone, no problem, we simply implement our interface with the new VectorStorage class and can easily use this class in our application and nothing will break.
We can even connect different classes for working with data dynamically depending on conditions, for example, depending on environment variables, if we are in development mode then use one data class, if in production then another, and if in testing mode then some kind of FakeStorage and everything will work great without any changes to the code.
I hope I was able to tell you what the S.O.L.I.D. principles are, how they work and why it is so important to use them in your work.
The bad thing is that the main developer of OpenCart seems to have never heard of them at all and that's why we now have such a mess in OpenCart that looks like will never end...
And OpenCart is just a very clear example of how ignorance or ignoring the basic principles of programming when building large systems can over time paralyze the development of the entire project.