Banner Scopes

Construir consultas personalizadas es un tema frecuente que podemos ver en muchos proyectos, aunque en ocasiones podemos caer en código repetitivo al aplicar las mismas condiciones sobre el mismo modelo en distintos métodos. Laravel nos ofrece una solución para esto y se trata de los Query Scopes o ámbitos de una consulta.

Sobre nuestra mini-aplicación

En este tutorial vamos a trabajar con un proyecto donde configuraremos una nueva instalación de Laravel y colocaremos un par de ejemplos de uso para Global Scopes y Local Scopes.

En este ejemplo vamos a necesitar trabajar con usuarios, los cuales tendrán un estado (activos o inactivos) y además necesitaremos mostrar un listado con los usuarios según su género.

Nueva instalación de Laravel

Para comenzar vamos a crear un nuevo proyecto con Laravel, ejecutando el siguiente comando de Composer en nuestra terminal:

composer create-project laravel/laravel styde

Configuración de nuestro proyecto

Vamos a comenzar editando la migración que Laravel trae por defecto, la cual hace referencia a la tabla users.

Podemos ubicar este archivo en:

database/migrations/2014_10_12_000000_create_users_table.php.

El contenido del archivo es el siguiente:

#
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->boolean('active');
    $table->enum('gender', ['male', 'female']);
    $table->rememberToken();
    $table->timestamps();
});

El único cambio que estamos haciendo es agregar un par de columnas llamadas active para determinar el estado del usuario y gender para el género.

Para continuar vamos a editar el Model Factory asociado a nuestro modelo User, ubicado en database/factories/UserFactory.php:

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
        'gender' => $faker->randomElement(['male', 'female']),
        'active' => $faker->boolean,
    ];
});

Para continuar, agregaremos una línea en la clase database/seeds/DatabaseSeeder dentro del método run para crear 100 usuarios con datos aleatorios:

<?php

factory(\App\User::class)->times(100)->create();

Luego usaremos el siguiente comando para ejecutar las migraciones y los seeders:

php artisan migrate --seed

Mejora tus habilidades con Blade, Eloquent ORM y aprende a desarrollar módulos avanzados con Laravel

Ver más

Uso de un Global Scope

En este primer ejemplo de uso vamos a plantear lo que queremos hacer. Para nuestro proyecto sólo queremos trabajar con los usuarios activos, no queremos mostrar los usuarios inactivos en cualquier parte de nuestro sistema. Para lograr esto vamos a utilizar un Global Scope.

Para comenzar vamos a crear una clase llamada ActiveScope y la ubicaremos en app/Scopes, el código que contiene es el siguiente:

<?php

namespace App\Scopes;

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

class ActiveScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('active', true);
    }
}

Finalmente para aplicar este Global Scope debemos dirigirnos a nuestro modelo y sobrescribir el método heredado boot, de esta forma:

#
use App\Scopes\ActiveScope;

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

    static::addGlobalScope(new ActiveScope);
}

O podemos evitar crear una clase si pasamos como argumentos un nombre y una función anónima al método addGlobalScope de la siguiente manera:

#
use App\Scopes\ActiveScope;

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

    static::addGlobalScope('active', function ($query) {
        return $query->where('active', true);
    });
}

Si ahora abrimos la terminal interactiva de Tinker con el comando php artisan tinker y ejecutamos User::count() tendremos como resultado un número que no es 100 ya que estaremos ignorando a los usuarios que no están activos gracias a nuestro Global Scope.

En caso de querer obtener todos los registros debemos desactivar el Global Scope de esta forma:

// Para sólo desactivar un Global Scope en especifico
User::withoutGlobalScope(ActiveScope::class)->count();

// Si hemos declarado el Global Scope con una función anónima indicamos el nombre
User::withoutGlobalScope('active')->count();

// Para desactivar todos los Global Scopes
User::withoutGlobalScopes()->get();

Una de las ventajas de los Global Scopes es que podemos utilizarlos en diferentes modelos. Por ejemplo, podemos imaginar que tenemos un modelo Post en el cual también tenemos una columna active la cual cumple la misma función que en los usuarios.

Uso de un Local Scope

A diferencia del ejemplo pasado, los Local Scope son aplicados a sentencias que podamos usar frecuentemente pero que no deberían aplicarse siempre como lo hace un Global Scope.

Para este ejemplo vamos a crear un Local Scope para que nos devuelva sólo los usuarios de género femenino registrados este mes. Para comenzar vamos a dirigirnos a nuestro modelo User y escribiremos el siguiente método:

public function scopeWomenInThisMonth($query)
{
    return $query->where('gender', 'female')
        ->whereMonth('created_at', now()->month);
}

Hemos declarado un método llamado scopeWomenInThisMonth siguiendo la convención scope[NombreDelScopeAqui].

Con Laravel tenemos la posibilidad de convertir este scope a uno dinámico recibiendo más parámetros como lo podemos ver a continuación:

public function scopeInThisMonthByGender($query, $gender)
{
    return $query->where('gender', $gender)
        ->whereMonth('created_at', now()->month);
}

Podemos hacer uso del scope que acabamos de crear de esta forma:

<?php

User::inThisMonthByGender('female')->get()

En la lección Búsqueda avanzada con Eloquent usando whereHas y Scopes puedes ver en video cómo Duilio aplica un Local Scope para el proyecto del curso Crea un Panel de Control con Laravel.

En este tutorial hemos aprendido cómo podemos aplicar los Query Scopes tanto de forma local como global, cuáles son sus diferencias y en qué momento aplicarlos. Si tienes alguna duda al respecto no olvides dejar un comentario y si te gustó el tutorial recuerda que puedes compartir este contenido en tus redes sociales.

Espero que te haya gustado este material. No olvides seguirnos en Twitter y suscribirte a nuestro boletín:

Suscríbete a nuestro boletín

Te enviaremos publicaciones con consejos útiles y múltiples recursos para que sigas aprendiendo.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.