l5-repository icon indicating copy to clipboard operation
l5-repository copied to clipboard

How to use Eloquent Scopes with l5-repository

Open mallardduck opened this issue 6 years ago • 8 comments

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.

mallardduck avatar May 03 '18 18:05 mallardduck

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.

mallardduck avatar May 03 '18 20:05 mallardduck

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.

mallardduck avatar May 03 '18 20:05 mallardduck

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

mallardduck avatar May 04 '18 16:05 mallardduck

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 avatar Jun 08 '18 07:06 johannesschobel

@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.

mallardduck avatar Jun 20 '18 13:06 mallardduck

So, Criteria is for controller and scope is for model?

skys215 avatar Jul 23 '18 10:07 skys215

@skys215 , i would rather say: Scopes are provided by Laravel itself (work directly on the model builder!), but Criterias are provided by this package (and work on the Repository!)

johannesschobel avatar Jul 23 '18 15:07 johannesschobel

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.

eness avatar Jan 15 '24 23:01 eness