Laravel nos brinda una manera muy conveniente y fácil de enlazar el parámetro de una ruta a un modelo como aprendimos en Enlace de modelos a rutas en Laravel. Lamentablemente esta característica se vuelve un poco engorrosa cuando los parámetros de rutas se relacionan a diferentes columnas de nuestros modelos, por ejemplo, cuando usamos slug
en el frontend de la aplicación, pero id
en el admin. En esta lección veremos cómo Laravel 7 soluciona este problema de forma muy elegante.
Si creamos un nuevo proyecto de Laravel 6 con el instalador de Laravel o a través de Composer:
# composer create-project laravel/laravel laravel6demo "6.*"
Creamos un sencillo modelo de Post con su migración:
# php artisan make:model Post -m
Dentro de la migración ..._create_posts_table.php
vamos a agregar estas columnas a la tabla de posts
:
<?php //... class CreatePostsTable extends Migration { /**/ public function up() { Schema::create('posts', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title'); $table->string('slug')->unique(); $table->timestamps(); }); } //... }
Luego configuremos la base de datos en el archivo .env
y ejecutemos las migraciones con php artisan migrate
.
Ahora a través de Tinker (php artisan tinker
) agreguemos un par de posts:
<?php App\Post::forceCreate(['title' => 'Laravel 6', 'slug' => 'laravel-6']); App\Post::forceCreate(['title' => 'Laravel 7', 'slug' => 'laravel-7']);
También puedes crear un seeder si así lo prefieres. Aprende más en Usando Eloquent ORM de forma interactiva con Tinker o Inserción de datos con los seeders de Laravel.
Ahora vámonos a routes/web.php
y agreguemos la siguiente ruta:
<?php Route::get('posts/{post}', function ($post) { dd($post); });
Si abrimos posts/laravel-6
en el navegador deberíamos ver el texto «laravel-6»
Puedes ejecutar tu nuevo proyecto con php artisan serve
, creando un virtual host, con Laravel Valet o de muchas otras formas, esto es indiferente para la lección.
Podríamos usar Enlace de modelos a rutas en Laravel para recibir el post directamente sin usar App\Post::where('slug', $post)->firstOrFail()
:
<?php Route::get('posts/{post}', function (App\Post $post) { dd($post); });
Pero esto nos da error 404 porque por defecto el enlace se hace a través del campo id
. Debemos ir a App\Post
y agregar el método getRouteKeyName
:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function getRouteKeyName() { return 'slug'; } }
Si recargas la página ahora sí deberías ver el Post con todas sus propiedades.
Pero imagina que ahora queremos declarar otra ruta para el admin y queremos enlazar con id
y no con slug
:
<?php // routes/web.php Route::get('admin/posts/{post}', function (App\Post $post) { dd($post); });
Lamentablemente http://127.0.0.1:8000/admin/posts/1
no funciona, tenemos que usar http://127.0.0.1:8000/admin/posts/laravel-6
.
Mi solución en Laravel 6 y versiones anteriores era regresar al modelo y hacer algo como lo siguiente:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function getRouteKeyName() { if (request()->segment(1) == 'admin') { return 'id'; } return 'slug'; } }
Si el primer segmento de la URL es ‘admin’ entonces enlazaremos con id
sino con slug
.
Ahora tanto http://127.0.0.1:8000/admin/posts/1
como http://127.0.0.1:8000/posts/laravel-6
funcionan. Genial. Pero esto es una especie de hack.
Enlace de modelos a rutas en Laravel 7
Si creamos un nuevo proyecto de Laravel 7 con laravel new laravel7demo --dev
o usando Composer:
# composer create-project laravel/laravel laravel7demo "dev-develop"
Repetimos los pasos para:
- Agregar el modelo de Post con
php artisan make:model Post -m
- Modificar la migración para agregar las columnas title y slug
- Configurar la base de datos y ejecutar las migraciones
- Agregar 2 posts con Tinker
- Agregar las mismas rutas en
routes/web.php
Podemos obtener el comportamiento anterior sin sobrescribir el método getRouteKeyName
en el modelo. Simplemente vamos a definir la ruta donde queremos enlazar con slug
y no con id
de esta forma:
<?php Route::get('posts/{post:slug}', function (App\Post $post) { dd($post); });
Nota la sintaxis de dos puntos seguido del nombre de la columna con la cual queremos crear el enlace.
Puesto que id
es el valor por defecto la ruta de admin quedará igual que antes a menos que estemos sobrescribiendo getRouteKeyName
en el modelo.
Mi consejo es que a partir de Laravel 7 elimines el uso de getRouteKeyName
(dejando el valor por defecto id
) y seas explícito cuando quieras usar Route Model Binding con otra columna.
Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.
Lección siguiente Nuevos métodos id() y foreignId() para las migraciones de Laravel 7