Why you can’t have a solid application using laravel
we are comparing how the ORM is being handled in laravel vs symfony.
THE PROBLEM : The fact that laravel models are tightly coupled with database and that violates the Dependency Inversion principle.
keep in mind I’m not talking about projects where the speed of the first development is what matters the most (small projects , MVPs etc). in those project laravel might even be a better choice than symfony.
here’s an example , lets assume in a laravel project we have a model called Post that represents the posts table. now imagine a PostController which just has a show method :
<?php
namespace App\Http\Controllers;use App\Models\Post;class PostController extends Controller
{
public function show(Post $post)
{
return view('post.show', ['post' => $post]);
}
}
looks good right? but wait what could possibly be wrong with this? let’s just ignore the fact that laravel is querying database in the routing faze in order to provide us with the $post (SubstituteBindings middleware which can be disabled) and just assume that we are controlling the connection to the database. now if we want to apply the Dependency inversion principle in SOLID we can use the repository pattern(the repository itself with the dependency injection using the service container is pretty much the standard stuff and I’m not gonna talk about them . also laravel used to have a tutorial in it’s documentation witch used the repository pattern ):
<?php
namespace App\Http\Controllers;use App\Models\Post;class PostController extends Controller
{
public function show($id,PostRepositoryInterface $postRepository)
{
$post = $postRepository->find($id);
return view('post.show', ['post' => $post]);
}
}
now it seems like we’ve fixed the DI violation but we have the $post variable with the type of App\Models\Post which inherits Eloquent model , this means that we still have all kinds of database related methods available on our entity! let me elaborate , let’s assume this is our view:
<div>{{$post->delete()}}</div>
There goes our post , this example may look like a odd one and some would say why the hell would somebody do that but that is not the point here , we are talking about dependency inversion , why should this method be available on the data model in the first place? this is definition of our data model being tightly coupled with the database layer which violates the DI principle.
Now lets look at how this would look like in symfony:
<?php
namespace App\Controller;
use App\Service\PostRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class PostController extends AbstractController
{
#[Route('/post/{id}')]
public function show(int $id, PostRepositoryInterface $postRepository): Response
{
$post = $postRepository->find($id);
//$post->delete() would throw undefined exception return $this->render('post/show.html.twig', [
'title' => $post->getTitle(),
]);
}
}
here in symfony the $post object is a pure data model object and it doesn’t have any connection to database. (there is no method like save or delete etc on the data model itself )
basically even if we don’t use the repository pattern in symfony , it would attach our model on the doctrine to work with database , lets look at our model here:
<?php
namespace App\Entity;// note that the orm and the repository is only being used as annotations
use App\Repository\PostRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: PostRepository::class)]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
}
as you can see our model doesn’t inherit any other class and database info is being specified as Annotations which acts as metadata and wouldn’t affect the Model itself.
Some would say we can ignore just this one principle in SOLID principles , that is not gonna be so dramatic right ? problem is that this code smell doesn’t just violate the DI principle , if we consider the clean architecture here:
one of the main rules in clean architecture is that the inner layers shouldn’t access the outer layer and the only communication should be from outer layers into inner layers. in our example the violation that is happening is the most inner layer(Entities) is accessing the most outer layer(DB) , the worst violation possible!
What if I don’t believe in SOLID and or Clean architecture (POV: you really hate uncle bob)
ok now imagine that you have a project that stores posts on database and you decide to use some other data-store that is not supported by default by laravel. for example you store the posts on cassandra , now imagine a scenario which you show a post from cassandra , you load a single post and create a model representing that post now you can’t really use the eloquent model because you’ve used the database related methods throughout you project which connects to the database , then you have to create a new data model and refactor most of code that is related to that data model. but if you’ve used the symfony style model, the parts that are working with the model itself won’t need refactoring cause you can use the same data model(not a data model that is tightly coupled with database).
I’m not saying that by using symfony you will definitely need no refactoring when switching to cassandra as it depends to a lot of factors , but in a normal circumstance it definitely need less refactoring or might even be possible with no refactoring and just by adding the repository.
now you can still use laravel without using the eloquent but doesn’t seem like a good solution when symfony is based on the better solution.
I would really like your comments on this cause I couldn’t find much about this. also don’t hesitate to attack me if you don’t agree with this.
P.S. Thanks to readers, we had some really good discussions in the comments section I suggest to check them out.