Novedades de Laravel 7

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:

  1. Agregar el modelo de Post con php artisan make:model Post -m
  2. Modificar la migración para agregar las columnas title y slug
  3. Configurar la base de datos y ejecutar las migraciones
  4. Agregar 2 posts con Tinker
  5. 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.

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.

Lección siguiente Nuevos métodos id() y foreignId() para las migraciones de Laravel 7