Eloquent is great, but in big projects it's common for extended functionality to dig into models. I'm not sure how to properly separate them when this need arises.
For instance, a ShopStock model needs to initiate a SQL transaction and acquire a row lock on its quantity column, to process a customer purchase in order to avoid race conditions; this implies a Query Builder-based solution. But I still want to make use of Eloquent for everything else.
Clearly, I need to write code that uses Query Builder and put it somewhere along with the rest of my model. But where do I put it? This kind of mixes up the separation of concerns between Controllers and Models, so there's no clear-cut solution. There are a few ways out:
Please spare me this torture.
Benefit: Fewer classes to deal with, straightforward controller vs model organization. Everything related to the model is in it, simplifying any future changes.
Drawback: We're polluting an Eloquent class. It's possible that some of this custom code may disrupt Eloquent's magic (or could do so in future versions). For instance, non-custom functionality like establishing relationships would get mixed together with alien code. We could end up with raw SQL or Query Builder-based operations sitting in an Eloquent model.
Benefit: Still relatively separate controllers and models. It's somewhat intuitive where code goes where; Eloquent models only have Eloquent magic and will only accept documented Eloquent interaction, everything else goes to the other model.
Drawback: Double models. It sounds neat, but now functionality over a model is split up in two files. It feels generally un-Laravel-like; some controllers will be referring to $user = new EloquentUser; $user->name = "Bob"
while other controllers will be going for $user = new User; $user->password = $unhashed
. In order to make the "custom" model use a pattern like Eloquent, developers will have to make their own getter/setting functionality. Either that, or use a different pattern, e.g. $user->setPassword($unhashed)
.
Benefit: Some of this functionality makes sense to be placed on a controller. Most important code can be separated into the application's domain. Best fits the single-responsibility concept.
Drawback: It can be awkward when dealing with code that looks like it should act upon models. In one controller we might find $user->name = "Name"
and in another we discover UserManager::setPassword($user->id, $password)
. In an app with sprawling requirements, one model might have dozens of classes managing various aspects. Responsibility over SQL is broken up over many classes; in the case of a schema change, all hell breaks loose.
I'm still looking at these options, though I'm trending towards the last one. It sounds like the least sane. The first 2 sound very dirty and could potentially waste a lot of time. I understand that part of this question pertains to how we separate responsibilities, and that's true; but an important part here is that the Query Builder segment isn't just a "responsibility", but low level operations that should be on the same level as Eloquent. I just don't like SQL being splattered around my project.
What's your take on the matter, and why did you go there?
See also: Managing relationships with repositories, over on StackOverflow
Repositories
Model
https://github.com/zenry/contract/blob/master/app/Scrfix/Contract.php
Repository
https://github.com/zenry/contract/blob/master/app/Scrfix/Repositories/ContractRepository.php
RepositoryInterface
https://github.com/zenry/contract/blob/master/app/Scrfix/Repositories/Contracts/ContractRepositoryInterface.php
ServiceProvider
https://github.com/zenry/contract/blob/master/app/Scrfix/Providers/ContractServiceProvider.php
Controller
https://github.com/zenry/contract/blob/master/app/controllers/ContractController.php
zenry said:
Repositories
Doing it via repositories work (it's closest to the last option above), but it adds an extensive middleman every time we want to deal with the model. As in your example, the repository is straightforward when it's just find, store, update or other simple methods, but what if other controllers/repositories want to interact with the model too? As other parts of code want to keep manipulating the model, instead of just feeding them Eloquent code, we'd have to continue padding in more functionality and learn a whole new interface too (since we're no longer working with eloquent's pattern).
(This, of course, assumes other bits of code can't just stretch into Eloquent when they want to do things directly, or use the repository when it's special functionality. But then that splits the responsibility over the model.)
To be fair, this is what I'm doing right now (and yes, I just keep throwing in more functionality into the repository every time I need something new). I just feel really dirty for separating fundamentally model-related code from where the model class lives. Seeing SQL in the repositories just give me the heeby-jeebies.
Thanks for the reply!
Edit: To be clear, I completely agree that a repository is the way to go if there's general model-related stuff going on, like how to fetch common groups of them. I'm just in doubt when it comes to there being specific model code, like SQL, query building, or operations that are designed to execute on single model instances. It's as if the repository isn't just a container for model-related code, but it becomes the model.
Okay, after going through further research and reading, I think I'm going to settle on this solution (very similar to Kyle Noland's answer on StackOverflow and building on repositories):
It's a more complex solution, but I like the separation of responsibilities. I tend to work with sprawling software (usually for businesses) so I prefer manageable complexity over confusing simplicity, so for anyone else reading this, I think going with a more succinct solution might be more practical. This high level of separation makes unit testing and rapid decoupling really convenient.
It feels awkward to answer my own question, but zenry got the ball rolling and rubber duck problem solving has always treated me well!
Have you seen this. Works perfectly, much like the stackoverflow url you mentioned. http://www.slashnode.com/reusable-repository-design-in-laravel/
Check this out, it may help: http://howdoi.pl/how-do-i-create-helper-class-with-global-scope-in-laravel-4/
Sign in to participate in this thread!
The Laravel portal for problem solving, knowledge sharing and community building.
The community