l5-repository
l5-repository copied to clipboard
How to use Eloquent Scopes with l5-repository
Hello,
As the title says I'm looking to learn how to do this and if it is possible? After some digging I'm starting to believe that it's not possible to do as things are setup currently. Am I just missing something or is that correct?
Overall I'd like to be able to utilize Laravel's global and local Eloquent scopes as needed when using a repository. For example if I've defined a scopeOurs
on a model I'd like to be able to apply the ours()
scope to the repository when needed.
It seems like this functionality is something others are looking for too - even if said in other terms. For instance #149 seems like a case that would benefit from being able to use Eloquent scopes.
Expanding on this issue further - as mentioned #149 seems related and has a few suggested solutions - the issue with these solutions is that they aren't necessarily ideal. For example @pjmartorell suggests doing:
class PostRepository extends BaseRepository {
...
def withTrashed() {
$this->model = $this->model->withTrashed();
return $this;
}
}
Overall I thought this seemed legit enough, however it didn't work as expected in practice. By applying the scope in this manner we're actually trading out the Model class for a Builder class - since Eloquent scopes return a Builder instance instead of your model class.
So with this method you can only use a single scope and things kinda fall apart.
I think that there are a few approaches that could help resolve this - just off the top of my head.
One option is to copy the same concept used by Laravels Model and Builder class and the other is to build out logic in the repo to apply scopes.
1. Copy the same idea used in Laravel core
So in this method we'd literally just use the __call
method like laravel does. The challenge here would be accessing the scopes
method on the Builder from within the Repo. Likely not impossible - but not a easy or quickly executed task either.
2. Add a new (set of) method(s) to allow applying scopes
This one could result in more lines of code added, but is likely easier to reason about building out. For instance we could add a useScopes
or useEloquentScopes
method. Using it could look something like:
$places = $this->repository->useEloquentScopes([
'ours',
['testsOurs' => false]
])->paginate();
When it sees a set that's a single string it'll just assume the scope parameters are null - when it sees an array it'll use the key as the scope name and the value as the parameters.
So in an interesting turn - an article was just published yesterday that goes into detail on this issue. Put more eloquently by the author - the root issue here is about method chaining and how Eloquent poxyies methods to the underlying Builder.
The article is here: https://medium.com/@simonhamp/breaking-the-chain-e8a55106493f
I think the key to finding an elegant solution to this issue will be keeping their technique in mind and adapting it for this use case. In the end, I think the result may be an amalgamation of all the techniques I've discussed
Hey there.. I think, i would go with the Criteria
approach and just create lots of small Criteria
that, for example, add where
statements. These criteria, in turn, can than be added to the Repository
before retrieving the data..
Maybe this can solve your problem?
@johannesschobel thanks for the suggestion and sorry for the lack of reply. This is still on my radar, but I haven't had time to work on the project I'm using it in lately. Will report back once I have that time.
So, Criteria
is for controller
and scope
is for model
?
@skys215 , i would rather say: Scope
s are provided by Laravel itself (work directly on the model builder!), but Criteria
s are provided by this package (and work on the Repository!)
For others in need, I've found a solution using Criterias. But it requires some boilerplate code.
Define your scoped function inside your repository as follows. Use pushCriteria() with an anonymous class, this way you don't have to use a separate file for criteria and it will still return repository object instead of eloquent's Builder.
public function ours() {
$this->pushCriteria(new class implements \Prettus\Repository\Contracts\CriteriaInterface {
public function apply($model, \Prettus\Repository\Contracts\RepositoryInterface $repository) {
$model = $model->where('ours', 1);
return $model;
}
});
return $this;
}
public function phone($number) {
$this->pushCriteria(new class ($number) implements \Prettus\Repository\Contracts\CriteriaInterface {
public function __construct(protected $number) {}
public function apply($model, \Prettus\Repository\Contracts\RepositoryInterface $repository) {
$model = $model->where('phone', $this->number);
return $model;
}
});
return $this;
}
And use these scopes as follows :
$orders = $this->orderRepository
->ours()
->phone('449999888777')
->get();
Although they are not Eloquent scopes, they work without any problems.