Laravel is probably the closest thing that the PHP community has to Marmite, it seems you either love it or hate it. It’s proponents love how easy it makes developing software; whereas it’s detractors accuse the framework of promoting bad practices. They can’t both be right or can they?
As a software architecture consultant, I don’t usually have the luxury of choosing the framework I am working with; by the time I join a team several years of development has often been invested into the current choice. This gives me a somewhat rare insight as I have worked with all three of the big framework names: Zend, Symfony and Laravel.
On twitter, I am usually on the negative side of the Laravel debate but it is not very easy to cover why in 140 chars. This post is my professional analysis of Laravel as a framework and a recommended read for any greenfield project considering Laravel as a framework. This is general purpose advice and your own use case should be considered as well. If you want tailored advice to your circumstances, get in touch and we can work something out.
Laravel as a product
The first point that has to be made when talking about Laravel is that it is an excellent piece of engineering. Whaaaaaatttt? I hear you exclaim. Let me explain. Laravel’s stated goals are to enable rapid development of new applications and to make development an enjoyable experience. I contend that it meets both goals quite well. That is not to say Laravel is the best thing since sliced bread or that you should use it unconditionally for everything. Consider a goal to move people from A to B and make it exciting; if you delivered a trebuchet you would meet those goals and could have engineered a great product but it wouldn’t make it the best mode of transport.
If initial development speed is something which is very important to you, perhaps for a side project which you don’t know if it will be successful and want to test the market before you commit to a larger investment of time or money, it would be hard to argue that there is a better option than Laravel. However for a company who plans a long term investment in a system, initial development speed isn’t the only thing they must consider: total cost of ownership also plays a factor and the later life of a Laravel system will likely prove more costly than other choices.
Laravel also claims to support industry best practices such as unit testing and dependency injection - this is true and it also hides a lot of the complexity of these things from the developer making it much easier for someone inexperienced in these things to start using them. A downside to the magic code used to do this is that it hide the implementation from the developer which means if something goes wrong or you need to go a bit off book you end up fighting the framework to do so.
Laravel as a project
Considering Laravel as a project, it has many up sides. It is open source, having many contributors; not just to the framework but to it’s many optional components and third party modules. Googling for Laravel + thing you wish to integrate with will usually bring back a module to help you, this isn’t unique to Laravel, but it demonstrates that it has a high level of adoption.
The documentation is detailed and up to date including both written and video tutorials. Additionally there is alot of third part blogs and articles on using Laravel. Laravel has a vibrant and dedicated community
- there are several Laravel specific conferences across the world further proving the level of support available should you choose to use Laravel.
In my view the only downside from the project/community side can only really come from the benevolent dictator project management model. This is something which works well for other high profile projects such as the Linux kernel, but there is always a risk that the current community could fracture into several competing forks should Taylor Otwell ever decide to step away from the project.
Laravel as a choice
So now to the key part of this post, should you choose to use Laravel for a project? Until now, I’ve had mostly good things to say about the framework so you may be thinking it’s a fairly safe bet; but as I hinted at the beginning the engineering choices made to enable speed of development can come back to bite you later on.
It is often argued that all of the features which get the most negative feedback are “optional” and you are free to develop in what ever way you choose but this is a misnomer - if Laravel has been developed specifically to enable development speed and enjoyment but you then choose not to use the features that enable this then Laravel has just lost it’s unique selling point over equally mature frameworks such as Symfony and Zend.
So what exactly is the issues you will face with Laravel, used as documented? What is it that gets some of the biggest names in PHP development so riled up on twitter? I’ve picked a trio of Laravels features which make it quick to develop with to elaborate on how they endanger the long term health of your project.
Facades are always the first thing that is pointed to by anyone arguing against Laravel. For anyone who doesn’t know a facade looks like this:
$item = Cache::get('item');
The first thing you’ll scream is: “static access is completly untestable why would you do such a thing!” Well Laravel has an interesting solution for that:
That’s right in your unit test you can call shouldReceive on a facade object and mock it out making it testable again. Yay! Of course calling shouldReceive on a facade at any point in your code will turn it into a mock object but your code reviews can catch that sort of thing right?
So if facades are testable what makes them so bad? There are two main gripes I have with people using facades:
Static access means you can use them /anywhere/ in your code base. Want to cache something from within your models? Sure you can do that. Need to charge a credit card in your view layer? Go for it. The fact that developers can and will call infrastructure layer code from wherever happens to be convenient breaks normal conventions of having separate layers of code for separate concerns and stores up a maintenance nightmare for future you to deal with.
Facades produce hidden dependencies. I spend a lot of my time reviewing code; it is one of the best ways to catch bugs and other issues having dependencies of a class neatly defined in the constructor helps understanding the code quickly. Hunting for any calls to facades is much slower. It is also impossible to easily tell the difference between a call to a facade and a call to a regular static class method.
Dependency auto wiring
The talk around hidden dependencies invariably leads onto the defence that Laravel does dependency injection as well, so you could just use that instead of facades and have it auto wire your classes with their dependencies - no facades needed.
This seems like it’s great, the only issue is that it does it based on type hints using reflection, Laravel will look up inside it’s service container a class which matches your type hint. This works great 90% of the time but you run into problems when you need to inject a dependency that isn’t a class or for which there are multiple possible dependencies (eg two different database classes)
Laravel gives you the tools to resolve these issues but in my experience developer tend not to use them, instead falling back to facades or “over injecting” eg passing in the entire config object when all that was needed was a single API key. It should also be pointed out that Laravel has no support for custom factory classes - the only way you can gain control over service creation is to inject a closure into the service manager.
This isn’t the only weirdness with the container if you deal with any “newable” objects such as commands; (which are infact both commands and command handlers at the same time) you have to pass your data in the constructor and once dispatched Laravel will pass dependencies into the handle method. A saner implementation would separate the data object representing a command from the class which handles that command removing the need for this inconsistency.
The final stop on our tour of features which make Laravel a poor choice for long lived projects is Eloquent; Laravel’s database abstraction layer. There are several patterns which are used for ORM’s, one is the data mapper pattern, where a translator class maps fields in your object to database fields. Another is the active record pattern where by your object becomes a representation of a specific database row. Eloquent follows the active record pattern. (For contrast, Doctrine 2 is a data mapper)
Eloquent is one of those features which again, enables rapid development but in doing so sacrifices maintainability: each of the models support static methods to query and update them which has the same issues as the facades in cost of long term maintenance but this is arguably worse as it sits this right in the heart of your software.
The active record pattern is bad here for two main reasons.
One it couples your entire codebase (via the model) to your database structure. This often means that a change to a database column can end up with changes throughout your model, controller, forms and views - a data mapper ORM could handle this via a change in the mapping information, without necessitating a change in your domain model code.
Two it couples your domain model to Laravel itself; people argue as to how bad this is since “they are never going to change framework” but consider this. Coupling to a framework is not bad because it prevents you moving from Laravel to Zend; it’s bad because it causes you extra work to go from Laravel 5 to Laravel 6. This may mean you are stuck for a while on an older (perhaps insecure) version until you can convince the business that it is worth the time investment to upgrade - a looser coupling would reduce the changes required.
Every pattern for creating maintainable, testable software puts infrastructure code eg database access at the highest level, meaning nothing below it is aware of the infrastructure and how it operates. This allows for clear separation of concerns and trivial mocking for testing purposes. Eloquent puts database access concerns right in the middle of everything.
Every tool has a purpose, a task at which it excels over others but whenever you optimise for one task it comes at the expense of other tasks (No free lunch theorem). Laravel is no exception to this, it definitely has a place in the PHP eco system as it fulfills a role that no other framework does as well: it enables rapid application development.
Does this mean you should use it for every project? No.
My recommendation when it comes to Laravel is simple: if you are building a prototype application to test the market - use Laravel (but make sure the business understands it needs to be thrown away and rebuild if it’s a success). If you are building coca cola’s marketing site for the world cup (a site which will have a shelf life of ~ 6 months) use Laravel. If you are building a business critical system which will evolve with the business over the next five to ten years do NOT use Laravel.