Welcome to my notes.
On this small corner of the internet, I write about the various things that catch my fancy. From software, to organizational process, to poetry and music.
SOLID in Laravel: Too Much or Not Enough?
Tuesday, November 19, 2019
By Etin Obaseki
Inspired by my talk at Laravel Nigeria 2019 on using SOLID with the framework we love.
The SOLID Principles generally espouse the practice of loose coupling when building software projects.
Your application should outlive any components or parts it’s built from.
With frameworks, it can be difficult to adhere to these best practices especially with Laravel which offers so many helpful… well, helpers.
This talk (now an article, of course) explores how the SOLID principles can be used even while enjoying the benefits that Laravel provides.
Our Duty As Software Craftsmen
One of my favorite books about software engineering is The Pragmatic Programmer by Andrew Hunt & David Thomas. In it, they talk about how we should treat our software as the builders and artisans of the famous European cathedrals did. As a craft.
My girlfriend has recently been urging me to exercise more and embrace healthy habits so that I can live longer. I haven’t followed all her advice yet (even though I really should) but it leads in nicely to my point about software.
Our job as software craftsmen is to build applications that can live forever.
We can only achieve this by ensuring that our software is in shape. The SOLID Principles are the exercise that our code needs to stay healthy.
What is SOLID?
Put forward by Robert “Uncle Bob” Martin, the solid design principles are a group of five foundational design principles that when followed help to keep software accurate, testable, refactorable and maintainable.
The SOLID Principles are:
- Single Responsibility: A unit of your application should be responsible to only one player. It should have only one “reason to change”.
- Open Closed: A class should be open to extension, but closed to modification. New functionality should be added by extending existing classes rather than modifying the existing class.
- Liskov Substitution: Objects with a common interface should be interchangeable.
- Interface Segregation: Single Responsibility, but for Interfaces. Clients to an interface should not be forced to implement methods they do not use.
- Dependency Inversion: Modules should depend on abstractions rather than concretions. Dependency should flow inward.
It may not be enough to gain a full understanding of each of the SOLID Principles from these definitions, so I recommend the books in the references below.
Are Frameworks anti-SOLID?
We Live and Die by The Framework
Kristopher Wilson, The Clean Architecture in PHP
Will Laravel live forever?
I certainly hope it does!
I think that these attributes mean Laravel is likely to be around for a very long time. However, as craftsmen, we must do our duty and our duty means that if, for whatever reason, Laravel is no longer a feasible option, our application can go on living.
Kristopher Wilson, in the book quoted above, shared his experience with a large application his team had written in Zend 1 and tried to port to Zend 2 when 1 had reached it’s End-of-Life.
Backwards incompatibility, however, had the last laugh and they had to rewrite the entire application from scratch. In his own words:
If we had paid attention to writing our application better, in a way that did not rely so heavily and fully upon our framework, our transition to Zend Framework 2 would have been much quicker, cheaper, and a lot less stressful.Kristopher Wilson, The Clean Architecture in PHP
If we ever need to port our application to a different framework, how much of the code will need to be rewritten?
The MVC Pattern: How Can We Improve It?
The MVC Pattern is something we’re all familiar with as Laravel Developers but is it limited? How can we improve it?
The problem with MVC becomes apparent in larger applications where everything doesn’t fall neatly into Models, Views and Controllers. Then you start to put things where they don’t belong.
Fat Models? Fat Controllers?
Conflated functionality and tight coupling leads to code that is difficult to debug, test or change.
Applying the SOLID Principles will improve the quality of the applications we build.Me, I think.
Remember what I said about exercise earlier? Yes, you should definitely give your code some solid exercise if you want it to live long. Let’s look at a few examples.
Some SOLID Examples
For my examples, I’m using an e-commerce application developed in this awesome LaraShout tutorial. All credit to the author.
Of course, the principles apply regardless of the specific domain.
SOLID Principles in our Models
The Model is Laravel is actually two things. It is both model, as in an object that holds the state of an entity in our application, and persistence or data layer.
Again, most of the time, this is fine, but for larger applications, it can get unwieldy.
Imagine that we need to switch out Eloquent for something else? Bosun‘s talk mentioned work on an ORM that worked with Async PHP. Imagine we needed to switch to that.
We’d need to rewrite all the parts of our application where we’ve used the model to store or retrieve data.
The Solution? The Repository Pattern.
Applying single responsibility to the Laravel Model shows us that it’s doing too much. It should be split into two classes. One to handle the state of the entity and another for persistence.
Above is a regular Laravel Model for Product. Not much different from what you’re used to.
And here we have the Repository. On closer inspection, it’s just using the Model’s regular Eloquent methods, so what’s the point?
Well, let’s see it in action to find out.
We see the Controller’s index() method is getting a list of Products, but it knows nothing about Eloquent. If we ever needed to switch the data source for Products, we would only need to rewrite the ProductRepository. Everything else would stay the same.
SOLID Principles in our Views
Our Views in the MVC Pattern represent the Presentation Layer of our application.
This layer needs to be as flexible as possible, because it is probably the most volatile layer of any application. Volatile meaning that it changes very easily.
Given this, it would be extremely anti-SOLID to have any business logic inside the views.
In fact, from the Laravel Docs, having “too much” logic in your views is highly discouraged.
In some situations, it’s useful to embed PHP code into your views. You can use the BladeLaravel Docs – https://laravel.com/docs/6.x/blade#php
@phpdirective to execute a block of plain PHP within your template…While Blade provides this feature, using it frequently may be a signal that you have too much logic embedded within your template.
The SOLID way would be to handle whatever logic you needed to in another class (not necessarily your controller, we’ll see why in a bit) before passing it to your views via the controller.
In fact, if you can afford to, it might be best to separate the back end (Laravel) and client (Vue, React, Angular or whatever else) into separate projects. This will give you more maintainability.
SOLID Principles in our Controllers
Our controllers should only be responsible for rendering HTTP Responses to the client. In a sense, Controllers are actually factories who take in a HTTP Request and return a HTTP Response.
Keeping our controllers light is important to ensure that our software is easy to maintain. When we have controllers littered with logic, it makes that logic impossible to reuse since you shouldn’t call controller methods directly.
I personally like the approach of having Service classes who handle the business logic and return final values to the controller to format and present to the Views.
With this pattern, our logic is reusable since the service class can be called where ever and when ever it is needed and our application is easier to debug and test.
Another approach is to use traits. This way, the logic is extracted and can be reused as well, but I personally find these a bit harder to test.
While building today’s software, we must do so with one eye on tomorrow.
The summary of all of this is that we should be conscious of what we are building.
What are the possibilities? What can change? Will the code we’re writing today be able to handle the use cases tomorrow? If not, how can we make sure that it’s easy to upgrade?
Of course, we won’t always be perfect, but we can be intentional and deliberate about it.
I’ll finish with this quote by Andrew Hunt & David Thomas from their book The Pragmatic Programmer:
“One hundred years from now, our engineering may seem as archaic as the techniques used by medieval cathedral builders seem to today’s civil engineers, while our craftsmanship will still be honored.”
Andrew Hunt & David Thomas – The Pragmatic Programmer
Thanks for reading!