¿Qué son las macros?
Las macros se utilizan en Laravel para darle nombre a ciertas operaciones. Normalmente se definen en un Service Provider para que estén disponibles en toda nuestra aplicación.
Si has utilizado colecciones conocerás el poder de las macros para hacer tu código más expresivo. Esto es, que sea legible como si fuera una historia dejando “escondidos” los detalles de implementación.
Este poder ha llegado a las relaciones para quedarse. Lo tenemos disponible desde la versión 5.4.8.
Vamos a ver el ejemplo que sirvió como presentación de esta característica. Jordan Pittman nos animaba en este tweet añadiendo a las relaciones uno a muchos la posibilidad de obtener un único registro.
Enséñame el código
Comenzamos creando nuestra aplicación con
1 | laravel new relationship_macro_lesson |
Entramos en app/Providers/AppServiceProvider.php e introducimos el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; ... class AppServiceProvider extends ServiceProvider { public function boot() { ... HasMany::macro('toHasOne', function () { return new HasOne( $this->query, $this->parent, $this->foreignKey, $this->localKey ); }); } |
Abrimos nuestra consola en la raíz del proyecto:
1 | $ cd ~/relationship_macro_lesson |
Creamos un nuevo modelo llamado login con una migración asociada:
1 | $ php artisan make:model -m Login |
Añadimos en la migración el campo del usuario
1 2 3 4 5 6 7 8 9 | ... public function up() { Schema::create('logins', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->timestamps(); }); } |
Para poder verlo en funcionamiento cambiamos nuestro fichero .env para poder trabajar con sqlite:
1 2 3 4 5 6 | APP_URL=http://localhost DB_CONNECTION=sqlite BROADCAST_DRIVER=log ... |
Borramos el resto de valores de DB_ para que no nos pregunte nada más y creamos la base de datos desde consola:
1 | $ touch database/database.sqlite |
Añadimos la relación en el modelo de user app/User.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function logins() { return $this->hasMany(Login::class); } /** * @return mixed */ public function lastLogin() { return $this->logins()->latest()->toHasOne(); } |
Y ahora vamos a probar que todo funciona como pensamos usando tinker, una consola que nos permite interactuar con toda nuestra aplicación:
1 | $ php artisan tinker |
Nota: En versiones de Laravel superiores a 5.4 Tinker es parte de un componente externo, el cual puedes instalar ejecutando composer require laravel/tinker y agregando en el array de providers de config/app.php el provider de Tinker Laravel\Tinker\TinkerServiceProvider::class,
Creamos un usuario, un par de registros de logins y comprobamos que todo funciona como creemos.
1 2 3 4 5 6 7 8 9 10 11 12 | >>> $user = factory(App\User::class)->create(); >>> $user->logins => Illuminate\Database\Eloquent\Collection {#684 all: [], } >>> $user->lastLogin => null >>> $user->logins()->create([]) >>> $user->logins()->create([]) >>> $user->fresh()->lastLogin->id == 2 => true >>> exit |
Podríamos añadir otra macro que incluyera de forma automática el latest que hemos puesto antes para que la consulta que devolviera fuera ordenada de más nuevo a más antiguo. Entramos en app/Providers/AppServiceProvider y añadimos el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class AppServiceProvider extends ServiceProvider { public function boot() { ... HasMany::macro('theLast', function () { return new HasOne( $this->query->latest(), $this->parent, $this->foreignKey, $this->localKey ); }); } |
Y ahora podemos cambiar nuestro lastLogin en app/User.php (nuestro modelo User) por:
1 2 3 4 5 6 7 | /** * @return mixed */ public function lastLogin() { return $this->logins()->theLast(); } |
Y ver que todo funciona como antes usando tinker:
1 | $ php artisan tinker |
Ahora recuperamos nuestro usuario recién creado y vemos que lastLogin devuelve lo mismo que antes:
1 2 3 4 | >>> $user = App\User::find(1); >>> $user->lastLogin->id == 2 => true >>> exit |
Nos encantará leer en los comentarios para que usarías macros en las relaciones. Anímate.