Laravel mixins

fernandoorm

FernandoOrM

Posted on July 26, 2022

Laravel mixins

Cuando trabajamos con Laravel, muchas veces necesitamos funciones muy especificas, pero que desgraciadamente no se encuentran dentro del Laravel.

La buena noticia es que Laravel nos permite agregar nuestras propias funciones gracias a un trait que incluye Laravel llamado Macroable

Pero basta de tanta palabrería y vamos a lo que nos interesa, "el código".

Para este ejemplo partiremos de un problema sencillo.
Tenemos una aplicación que contiene una base de datos de usuarios y nos estan solicitando un buscador de usuarios.
Así que tendremos una ruta llamada /users, la cuál recibira parametros por medio de la url de la siguiente forma /users?search[name]=Luis

Lo primero será ir a nuestro archivo App\Providers\AppServiceProvider y dentro de esta clase nos encontraremos la función register, en donde colocaremos nuestro código.

public function register()
    {
        Builder::macro('search', function () {
            $search = \request()->get('search');
            if (is_null($search)) {
                return $this;
            }

            foreach ($search as $key => $value) {
                $this->orWhere($key,'LIKE',"%{$value}%");
            }

            return $this;
        });
    }
Enter fullscreen mode Exit fullscreen mode

¿Pero que estamos haciendo aquí?
Bueno, pues la verdad es que es bastante sencillo de entender.

Lo primero que estamos haciendo es acceder al la función macro de la clase Builder, la cual se encarga de construir las consultas en Laravel.
La función macro recibe dos parametros, el primero es el nombre con el cual llamaremos a nuestra función, y el segundo parametro es la implementación de nuestra función.

            $search = \request()->get('search');
            if (is_null($search)) {
                /**  var Illuminate\Database\Query\Builder */
                return $this;
            }
Enter fullscreen mode Exit fullscreen mode

La función que declaramos es bastante sencilla, en ella, simplemente obtenemos la llave "search", desde el request, para después validar mediante una condición si el "search" contiene algo, y si no es así entonces devuelve $this. Aquí es importante aclarar que $this dentro de este contexto se refiere a la clase Builder, por lo tanto estamos devolciendo una instancia de la clase Builder y no de AppServiceProvider que es donde estamos declarando nuestro mixin.

            foreach ($search as $key => $value) {
                $this->orWhere($key,'LIKE',"%{$value}%");
            }
            /** var Illuminate\Database\Query\Builder */
            return $this;
Enter fullscreen mode Exit fullscreen mode

Por último solo hacemos un foreach para recorrer los filtros de busqueda, porque recordemos que podemos envíar n cantidad de filtros a nuestra API. Y por ultimo simplemente devolvemos la instancía de Builder despues de agregar todos los where.

Ahora probemos si todo funciona correctamente

Para esta prueba creamos 1000 usuarios gracias a los seeders que nos ofrece laravel.

\App\Models\User::factory(1000)->create();

lista_usuarios

Ahora simplemente llamaremos a la nueva función que creamos, dentro de nuestra ruta /users

Route::get('/users', static function (Request $request) {
    return \App\Models\User::search()->get();
});
Enter fullscreen mode Exit fullscreen mode

Al acceder a la ruta users pero sin envíar ningún parametro dentro de la misma podemos ver que obtendremos una lista de todos los usuarios almacenados.

test_without_filters

Pero la magía pasa cuando envíamos parametros dentro de nuestra ruta (/api/users?search[name]=fred&search[lastname]=Stark)

test_filters

Como vemos ahora obtenemos un resultado totalmente diferente. Ahora el número de resultados disminuyo considerablemente y los resultados que obtenemos coinciden con los parametros de busqueda que envíamos en la url.

En este punto podemos extender la funcionalidad de tanto como querrámos, pero también tendremos un problema, que luego de declarar un par de funciones nuestro ServiceProvider, nuestro archivo crecera demasiado, lo que hará que nuestro código se vuelva poco legible, y realizar cambios en un futuro puede volverse un dolor de cabeza.

Así que vamos a resolver este problema de una forma sencilla.

Para esto usaremos otro ejemplo.
Imaginemos que ahora ademas de poder buscar usuarios tambien queremos paginarlos de la siguiente forma /api/users?page[size]=1&page[number]=1, porque como vimos, las respuestas pueden volverse muy grandes y afectar el rendimiento de nuestra aplicación.

Bueno, pues en vez de declarar las funciones en un ServiceProvider, crearemos una nueva clase llamada QueryBuilder dentro de nuestro proyecto.
Y dentro de esta clase definiremos nuestras dos funciones.

<?php

namespace App\Mixins;

class QueryBuilder
{
    public function search()
    {
        return function (){
            $search = \request()->get('search');
            if (is_null($search)) {
                /**  var Illuminate\Database\Query\Builder */
                return $this;
            }

            foreach ($search as $key => $value) {
                $this->orWhere($key,'LIKE',"%{$value}%");
            }
            /** var Illuminate\Database\Query\Builder */
            return $this;
        };
    }

    public function queryPaginate()
    {
        return function (){
            $paginate = \request()->get('page');
            if (is_null($paginate)) {
                /**  var Illuminate\Database\Query\Builder */
                return $this;
            }
            return $this->paginate(
                $perPage = $paginate['size'],
                $columns = ['*'],
                $pageName = 'page',
                $page = $paginate['number']
            );

        };
    }

}
Enter fullscreen mode Exit fullscreen mode

Como podemos ver todas las funciones deben retornar una función que contenga la declaración de nuestra función.

Y por último declararemos nuestro mixin en AppServiceProvider.

    public function register(): void
    {
        Builder::mixin(new QueryBuilder());
    }
Enter fullscreen mode Exit fullscreen mode

Como vemos, ahora nuestro código se ve más organizado, y tambien hay que darnos cuenta que la declaración es distinta , ya que ahora no accedemos a la función macro sino a la función mixin, la cúal solo recibe como parametro una nueva instancia de la clase que contiene nuestras funciones.

Y si probamos todo esto en conjunto tendremos el siguiente resultado.

test_pagination

Ahora que enviamos los parametros de paginado en conjutno con los filtros de busqueda, podemos observar que obtenemos los mismos resultados de la busqueda pero ahora solo obtenemos tres resultados puesto que lo definimos en el url /api/users?search[name]=fred&search[lastname]=Stark&page[size]=3&page[number]=1

Conclusión
Solo queda decir que usar mixin en nuestras aplicaciones es un buen recurso que nos puede ayudar mucho para reutilizar código. Y aunque existen muchas formas de hacer esto sin la necesidad de utilizar mixin e incluso hacerlo de mejor forma, es bueno conocer este tipo de alternativas que nos ofrece laravel.

💖 💪 🙅 🚩
fernandoorm
FernandoOrM

Posted on July 26, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Working with Laravel events
laravel Working with Laravel events

February 19, 2023

Laravel mixins
laravel Laravel mixins

July 26, 2022