6

We're having issues using a global scope with a dynamic query parameter. The global scope is based on the manager ID, but $model is empty, and $this refers to the manager scope not the model so $this->id is an undefined property. Is there a way to do something like this:

public function apply(Builder $builder, Model $model)
{
    return $builder->where('manager', $model->id); // $this->id
}

I'm assuming that $model is supposed to be the manager model, but since it is empty and I can't find any documentation on it I'm not entirely sure (if anyone can tell me in a comment I'd appreciate it). This is our global scope method in the Manager model:

protected static function boot()
{
    parent::boot();

    static::addGlobalScope(new ManagerScope);
}

Since global scopes don't require an explicit method to be applied I thought maybe adding something to the boot might allow for an extra parameter something like:

protected static function boot()
{
    parent::boot();

    static::addGlobalScope(new ManagerScope($this->id);
}

But this isn't allowed in a static method, which makes sense after I saw the error.

2 Answers 2

14

Naturally global scopes are applied automatically and there are no way to pass parameters to them directly.

Therefore you can either stick with a dynamic local scope, which IMO makes more sense,

public function scopeForManager($query, $manager)
{
    return $query->where('manager', $manager->id);
}

Document::forManager($manager)->all();

or if the a manager info is available in some kind of global state (i.e. session) you can create some sort of ManagerResolver class

class ManagerScope
{
    protected $resolver;

    public function __construct(ManagerResolver $resolver)
    {
        $this->resolver = $resolver
    }

    public function apply(Builder $builder, Model $model)
    {
        return $builder->where('manager', $this->resolver->getManagerId());
    }    
}

and pass an instance of it into your scope

protected static function boot()
{
    parent::boot();
    static::addGlobalScope(new ManagerScope(new ManagerResolver());
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, we actually do have a deferred service provider that is used to get the currently authenticated user, in this case the manager, from a token in middleware so this might work. The reason we didn't want to use a local scope was to avoid developers mistakes that forgot to use it since that would be an issue for applicant security, but I guess it could be directly chained to the applicants relationship. Can you explain what $model should be used for? I can't find an example that makes sense.
Built-in SoftDeletingScope would be a good example where $model is used to figure out a fully qualified name of of "deleted_at" column for a table that holds model data because you can override the method that returns the name of that column in your model.
Did it help in any way?
1

Update for Laravel 8.x

You can create a static function inside your model and then use it in a created scope. For example, let's say that you want to create a global search for all tables without creating a search function for each model.

In your scope class do the following:

<?php

namespace App\Scopes;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
 
class SearchScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        if(!empty($keyword = \request()->query('q')))
            $builder->where($model::searchField(), 'LIKE', '%' . $keyword . '%');
    }
}

Then create searchField() function in every model like this:

    <?php
    namespace Modules\Api\Models;
    
    use Illuminate\Database\Eloquent\Model;
    use App\Scopes\SearchScope;
    
    class Product extends Model
    {
        protected static function searchField(){
            return "products.name"; // Name of field to be used in WHERE clause while searching using LIKE operator
        } 
    
        protected static function booted()
        {
            static::addGlobalScope(new SearchScope);
        }
}

Bravo! Now you can use a dynamic variable in Laravel scope!!

3 Comments

Clever use of static function
What does the !empty($keyword = \request()->query('q')) part do? And is there a way to get the keyword dynamically from the model? I'm trying to build something similar to the softDelete but need to have information from the model
That was for example. The global scope is applied only if keyword is passed in $request. The problem here is just how to pass additional variables in apply() method so that they can be used by Builder

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.