Usando escopos do Eloquent para construir consultas mais limpas no Laravel

É comum precisarmos por diversas vezes fazer consultas que envolvam o mesmo trecho de código. Pensando nisso, o Eloquent possui os escopos, que permitem aplicar modificadores nas consultas. Esses modificadores podem ser aplicados em dois níveis ao model: local e global.

Laravel - Framework PHP (Parte 1/3)
Curso de Laravel - Framework PHP (Parte 1/3)
CONHEÇA O CURSO

Escopo local

O escopo local permite criar métodos com modificadores. Esses métodos podem ser aplicados a uma consulta como se fossem métodos próprios do Eloquent.

Vamos supor que em um model chamado Client exista uma coluna limit que indica o limite de compra do cliente. É possível imaginar que diversas vezes no projeto precisaremos buscar clientes que possuam limite maior que 0.

Poderíamos ter como exemplo as seguintes consultas:

1) Buscar por nome dos clientes que possuem limite:

Client::where('limit', '>', 0)->where('name', 'like', $nome)->get();

2) Buscar por cidade dos clients que possuem limite:

Client::where('limit', '>', 0)->where('city', $cidade)->get();

Note que repetimos o mesmo código where('limit', '>', 0) nas duas consultas. Isso pode não ser muito bom pois, caso a regra de negocio mude, será necessário alterar dentro de cada local do projeto.

A criação de um escopo pode resolver o problema acima facilmente. Para criar um novo escopo basta definir um método publico na classe do model, ele deve ter o nome no padrão scopeNomeEscopo e receber um parâmetro que contém a instancia do Builder.

Veja o exemplo de escopo para definir o limite > 0:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Client extends Model
{
    /*
     * Escopo que busca clientes com limite
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeHasLimit($query)
    {
        return $query->where('limit', '>', 0);
    }
}

Agora podemos usar o escopo facilmente na consulta como se fosse um método do próprio Eloquent:

Busca por nome dos clientes que possuem limite:

Client::hasLimit()->where('name', 'like', $nome)->get();

Busca por cidade dos clients que possuem limite:

Client::hasLimit()->where('city', $cidade)->get();

À primeira vista, parece não ter muita diferença, mas já ganhamos na legibilidade da leitura das consultas, quantidade de código digitado e também na definição única do código.

Vamos supor que agora ao invés de 0 o limite deve ser maior que 100 e que precise também sempre ordenar pelo maior limite. Provavelmente você ficaria muito feliz em precisar alterar em apenas um local ao invés de vários, além de ter que ficar olhando cada consulta para conferir se está tudo certo.

Escopo global

O escopo global é muito útil quando necessário aplicar um modificador a todos os registros que forem buscados no model. Ao invés de definirmos em cada consulta os modificadores, ou até mesmo criar escopos locais e aplicar em cada consulta, podemos declarar o escopo global.

Vamos supor devemos mostrar apenas clientes ativos e que eles sejam ordenados pela data de cadastro. Podemos adicionar dois escopos:

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class Client extends Model
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        //filtra apenas por clientes ativos
        static::addGlobalScope('active', function (Builder $builder) {
            $builder->where('active', true);
        });

        //ordena pela data de cadastro
        static::addGlobalScope('created', function (Builder $builder) {
            $builder->orderBy('created_at');
        });
    }
}

Agora, toda vez que consultar os clientes automaticamente esses dois modificadores são aplicados.

Consulta sem utilizar o escopo global

Caso não queira aplicar em uma consulta especifica basta chamar o método withoutGlobalScopes():

Client::withoutGlobalScopes()->get();

Se quiser deixar de aplicar apenas um escopo global especifico, basta passar o nome dele:

Client::withoutGlobalScope('created')->get();

Escopo global usando classe própria

É possível também passar uma classe ao invés de uma função anônima para o método addGlobalScope():

static::addGlobalScope(new \App\Scopes\ActiveScope);

Nesse caso a classe para aplicar a condição de ativo ao cliente fica:

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class ActiveScope 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)
    {
        $builder->where('active', true);
    }
}

Conclusão

O uso de escopo nos ajuda na legibilidade das nossas buscas e torna o código menos repetitivo. Se aplicado de maneira correta, ele pode ajudar bastante na qualidade do código e facilitar futuras manutenções.

Laravel - Framework PHP (Parte 2/3)
Curso de Laravel - Framework PHP (Parte 2/3)
CONHEÇA O CURSO
Laravel - Framework PHP (Parte 3/3)
Curso de Laravel - Framework PHP (Parte 3/3)
CONHEÇA O CURSO
Deixe seu comentário

Desenvolvedor, autor e instrutor. Apaixonado por desenvolvimento de software e tudo ligado a área de tecnologia. É autor de cursos em diversos temas, como, desenvolvimento back-end, cloud computing e CMSs. Nas horas vagas adora estudar sobre o mercado financeiro, cozinhar e brincar com pequeno Daniel de 1 ano.