Aprende eloquent con ejemplos!!! Lección 4  - Model Scopes
Tags: laravel, eloquent, webdev, php
Published: 2021-05-03 01:12:44

Bienvenidos de nuevo a **“Eloquent con ejemplos”**, en la  pasada entrega aprendimos los conceptos básicos de los **Modelos** y una nueva herramienta llamada **Tinker**. Hasta ahora hemos tocado solamente un poco de la superficie de **Eloquent** y su funcionamiento, como habrás notado, a pesar de que este es un curso de Laravel, todavía no hemos construido ninguna ruta o páginas web!

Laravel es un ecosistema muy grande que abarca mucho más que las páginas web estándar. Aprender a mirar una sección aislada de ella, como lo estamos haciendo con **Eloquent**, será muy útil para desarrollar tanto nuestra velocidad y flujo de trabajo, como nuestra comprensión de cada uno de ellas, por lo tanto, seguiremos trabajando con la línea de comandos y Tinker por un tiempo más. Puede sentirse un poco incómodo si no estás acostumbrado a trabajar de esta manera, pero al final tendrá grandes beneficios.

En esta lección vamos a empezar a hacer algunas consultas de base de datos!

Cosas que aprenderemos:

* Model scopes

Uno de los mayores objetivos de esta serie es ayudar a las personas a ser competentes, y no sólo conocedoras, sobre el uso de las herramientas **` artisan `**, es por ello que repasaremos una vez más como construir nuestras tablas y datos. ¡No te preocupes, solo será un repaso y lo haremos rápidamente!

1) Aquí hay un comando que aún no hemos utilizado con **migrate** - **"reset"**. Esto revertirá todas las migraciones para que podamos empezar de nuevo.

```
php artisan migrate:reset
```

2) Eliminemos los archivos creados en entregas pasadas:
 - El factory DogFactory
 - El seeder DogsTableSeeder
 - El modelo Dog
 - La migración _create_dogs_table

Probablemente deberías ejecutar en la terminal **`composer dump-autoload`**, ya que los archivos a veces se registran en el cargador automático y causan problemas más adelante cuando están "desaparecidos".

3) ¿Recuerda cómo hacer la migración y el modelo al mismo tiempo? ¡Espero que si!, igual aquí tienes la respuesta:

```
php artisan make:model Dogs -mfs
```

Esto crea nuestro *modelo*, junto con la *migración*, *factory* y *seeder* respectivamente.

4) En nuestra migración, queremos que los perros tengan un nombre, un género y un campo de edad. Agrega a la función **`up()`** dichos campos, de manera que te quede así:

```php
public function up()
{
    Schema::create('dogs', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('gender')->nullable();
        $table->integer('age')->nullable();
        $table->timestamps();
    });
}
```
5) Para este ejercicio, todos deberíamos tener los mismos datos para que podamos hacer algunas consultas específicas, por lo que usaremos **model factories** para crear introducir datos semillas en lugar de usar Faker. Vayamos a nuestro DogSeeder y coloquemos el siguiente código:

>**Nota:** Aunque usamos *model factories*, no la configuraremos todavía como es debido, si embargo esto no impedirá que la usemos sin problemas. En proximas lecciones nos dedicaremos a establecer nuestra factory como es debido.

```php
public function run()
{
    Dog::factory()->create(['name' => 'Joe', 'age' => 5 , ‘gender’ => ‘male’]);
    Dog::factory()->create(['name' => 'Jock', 'age' => 7, ‘gender’ => ‘male’ ]);
    Dog::factory()->create(['name' => 'Jessie', 'age' => 2, ‘gender’ => ‘female’ ]);
    Dog::factory()->create(['name' => 'Jane', 'age' => 9, ‘gender’ => ‘female’ ]);
}
```
1) Configuremos el archivo seeder central para que ejecute nuestro nuevo DogSeeder. Para esto, debemos ubicarnos en el archivo **`database\seeders\DatabaseSeeder`** y hacer la siguiente invocación:

```php
public function run()
{
    $this->call(DogSeeder::class);
}
```

7) Finalmente ejecutemos nuestras migraciones junto con los archivos seeders:

```php
php artisan migrate --seed
```

## Model scopes:

Muy bien, ejecutemos Tinker y empecemos a consultar!

Tenemos perros de cuatro edades distintas (2, 5, 7 y 9 años), dos de ellos son hembras y dos son machos. Nos gustaría ver a todos los perros mayores de 6 años. La forma sencilla de hacer esto es simplemente poner una cláusula **`where()`** en nuestro modelo **Dog,** así:

```php
php artisan tinker
Psy Shell v0.10.8 (PHP 8.0.5 — cli) by Justin Hileman

App\Models\Dog::where('age', '>', 6)->get();
```

Deberías ver a los encantadores Jock y Jane en pantalla.

Algo que confunde a muchas personas cuando comienzan con Eloquent es el hecho de que este se basa en **QueryBuilder**, pero los resultados vuelven como un objeto **Collection**. Esto significa que vas a encontrarte con muchas funciones con nombre similar que parecen ser diferentes dependiendo de si estás creando la consulta o manipulando los registros devueltos. En este caso estamos utilizando **`where()`** para crear una cláusula *where* en nuestra consulta, no para filtrar el conjunto de resultados. Para este curso usaremos las funciones **querybuilder** para crear instrucciones **sql**, te recomiendo ampliar mas sobre el tema en la documentación oficial de Laravel sobre las [Query Builder](https://laravel.com/docs/8.x/queries).

El código que acabamos de ejecutar funciona, pero tiene algunos problemas desde el punto de vista de diseño y mantenimiento. Con este código en nuestro controlador es como si estuviéramos codificando instrucciones **sql** en toda nuestra aplicación. Imagina que comenzamos a usar dicha instrucción en varios lugares de nuestra aplicación, podría darse el caso que esa edad cambie por alguna razón, obligándonos a tener que a cambiarla en todos y cada uno de esos lugares, tarea bastante tediosa y poco práctica, sin mencionar que podríamos saltarnos uno o varios de ellos, generando posibles errores e inconsistencias. Por otra parte, tampoco dicha instrucción no explica el por qué usamos esa edad en específico o cuales son sus casos de uso y/o de aplicación.

Usemos algo llamado **"scopes"** para hacer una restricción más limpia.

```php
class Dog extends Model
{
    function scopeAgeGreaterThan($query, $age) {
        return $query->where('age', '>', $age);
    }

    function scopeGender($query, $gender) {
        return $query->where('gender',$gender);
    }
}
```

Como puedes ver, tenemos dos métodos arriba que siguen a una convención de nomenclatura muy específica, un subproceso común en Laravel. La función debe comenzar con **"scope"** y el primer parámetro será la instancia de *QueryBuilder* inyectada, dicho argumento puede tener cualquier nombre que quieras darle, pero normalmente **`$q`** o **`$query`** se utiliza para mayor claridad, depende de ti el nombre y la cantidad de parámetros subsecuentes que quieras inyectar, recomiendo el uso de nombre descriptivos como **`$age`** o **`$gender`** que dan una idea clara de la razón de su existencia dentro de cada método. Ahora puedes utilizar esta función de esta forma (reinicia la sesión de Tinker para que actualice los último cambios):

```php
App\Models\Dog::ageGreaterThan(6)→get();
```

Aquí hemos usado el primero de nuestros **scopes**, observa la respuesta en pantalla y verás que es exactamente la misma que hemos obtenido anteriormente, ¿la diferencia?, podremos usarla en cualquier parte de nuestra aplicación, con el plus de solo tener que modificarla en un solo lugar. Por otro lado es bueno mencionar que los **scopes** toman una instancia de **QueryBuilder** y la retornan, lo que permite encadenar cualquier cantidad de funciones como varios **`where()`, `orderBy()`**, entre otros. Veamos un ejemplo claro.

Vamos a nuestro modelo, y agreguemos:

```php
App\Models\Dog::ageGreaterThan(6)->gender('female')->get();
```

Fácilmente hemos filtrado nuestro primer resultado y ahora solo nos aparece la hermosa Jane en pantalla, sin clausulas `where` visibles ni largas líneas de código, todo muy limpio, compacto y totalmente reusable. ¡Genial!

> En la documentación oficial de laravel encontrarás un apartado dedicado al uso de clausulas *where* y lo que ellos llaman [logical-grouping](https://laravel.com/docs/8.x/queries#logical-grouping). Recomiendo encarecidamente su lectura, te será de mucha ayuda.

Esto es agradable, sin embargo todavía no está muy claro cuál es el propósito de dichos **scopes**, así que, vamos a darles un sentido: Digamos que a cierta edad, nuestro amigos caninos requieren de un tratamiento especial que les ayudará en su calidad de vida cuando lleguen a una edad avanza, ademas, este tratamiento es diferente según su género. Bajo este supuesto, vamos a hacer algunos cambios en nuestro modelo:

```php
class Dog extends Model
{
    function scopeAgeGreaterThan($query, $age) {
        return $query->where('age', '>', $age);
    }

    function scopeGender($query, $gender) {
        return $query->where('gender',$gender);
    }

   function dogsForSpecialTreatment($gender) {
        return $this->AgeGreaterThan(6)->gender($gender);
    }
}
```

Reiniciemos tinker una vez mas:

```php
(new App\Models\Dog)->dogsForSpecialTreatment('female')->get();
```

Resaltemos un par de cosas en el código de arriba. Tenemos un método común **`dogsForSpecialTreatment($gender)`** en nuestro modelo, el cual hace uso de **scopes** dentro de sus instrucciones, creando la consulta necesaria para cumplir con los requerimientos de nuestro tratamiento especial, puedes jugar cambiando el género y verás como se alterna el resultado entre Jock y Jane.  Es importante destacar que ahora nuestro modelo tiene un método más descriptivo,  cuyo nombre denota un propósito bien definido. Por otro lado, observa que al hacer uso de este método, ya no lo hacemos de la misma forma que con nuestros **scopes**, pues a este último accedemos mediante la forma `static::`, mientras que a nuestro nuevo método accedemos como a cualquier método de clase común, debiendo para ello crear una instancia de nuestro modelo usando **new**.

El ejemplo anterior se mantiene intencionalmente simple, y por lo tanto, si te preguntas **¿Por qué no hacer un solo **scope** 'dogsForSpecialTreatment()' con toda la lógica dentro de él?** , pues dejame decirte que estas en lo cierto y tiene sentido. Sin embargo, las aplicaciones del mundo real suelen tener un conjunto de criterios que definen un cierto estado o grupo (por ejemplo, límites de edad o el género), los cuales en algún punto dado, como algún tipo de reporte, se necesita trabajar con algún tipo de subconjunto derivado (todos los perros mayores de 6 años y que sean hembras). Si piensas en diseñar y crear tus modelos a partir de piezas más granulares, en lugar de una gran función completa y monolítica, facilitará el mantenimiento de tus aplicaciones, te mantendrá alejado de la duplicación de código e incluso te ayudará a darle un significado explícito que exprese que es lo que hace, algo de bastante ayuda para cuando vuelvas a hacer cambios unos meses más tarde.

La siguiente lección introducirá alcances globales *(global scopes)*, así como los ámbitos Laravel incorporados, como *soft-deletes*. No seguiremos repitiendo todos los pasos de reconstrucción cada día, así que vuelve a esta lección si necesitas ayuda para recordar cómo restablecer la base de datos para lecciones posteriores.

Quédate atento a la próxima entrega, si tienes alguna duda puedes contactarme en mi cuenta de Twitter @johantovar  o déjala en los comentarios. Hasta entonces y que tengas un feliz y exitoso día.