Agrupación de Rutas en Laravel: 6 técnicas para organizar las rutas


Victor Arana Flores

08 Aug 2022

Laravel Routing es una característica que los desarrolladores aprenden desde el principio. Pero a medida que sus proyectos crecen, se hace más difícil manejar los archivos de rutas cada vez más grandes, desplazándose para encontrar las declaraciones Route::get() correctas. Por suerte, existen técnicas para hacer los archivos de rutas más cortos y legibles, agrupando las rutas y sus configuraciones de diferentes maneras. Echemos un vistazo.

Y no, no voy a hablar sólo de la simple y general Route::group(), esa es de nivel principiante. Vamos a bucear un poco más profundo que eso.

Agrupación 1. Route::resource y Route::apiResource

Empecemos por el elefante en la habitación: esta es probablemente la agrupación más conocida. Si tienes un conjunto típico de acciones CRUD alrededor de un Modelo, vale la pena agruparlas en un controlador de recursos.

Dicho controlador puede constar de hasta 7 métodos (pero puede tener menos):

  • index()
  • create()
  • store()
  • show()
  • edit()
  • update()
  • destroy()

Así que si su conjunto de rutas corresponde a esos métodos, en lugar de:

Route::get('books', [BookController::class, 'index'])->name('books.index');
Route::get('books/create', [BookController::class, 'create'])->name('books.create');
Route::post('books', [BookController::class, 'store'])->name('books.store');
Route::get('books/{book}', [BookController::class, 'show'])->name('books.show');
Route::get('books/{book}/edit', [BookController::class, 'edit'])->name('books.edit');
Route::put('books/{book}', [BookController::class, 'update'])->name('books.update');
Route::delete('books/{book}', [BookController::class, 'destroy'])->name('books.destroy');

... puede tener una sola línea:

Route::resource('books', BookController::class);

Si trabajas con un proyecto de API, no necesitas los formularios visuales para crear/editar, así que puedes tener una sintaxis diferente con apiResource() que cubriría 5 métodos de 7:

Route::apiResource('books', BookController::class);

Además, te aconsejo que consideres los controladores de recursos incluso si tienes 2-4 métodos, y no los 7 completos. Sólo porque mantiene la convención de nomenclatura estándar - para URLs, métodos y nombres de rutas. Por ejemplo, en este caso, usted no necesita proporcionar los nombres manualmente:

Route::get('books/create', [BookController::class, 'create'])->name('books.create');
Route::post('books', [BookController::class, 'store'])->name('books.store');
 
// En su lugar, aquí se asignan automáticamente los nombres "books.create" y "books.store"
Route::resource('books', BookController::class)->only(['create', 'store']);

Agrupación 2. Grupo dentro de un grupo

Por supuesto, todo el mundo conoce la agrupación general de rutas. Pero para proyectos más complejos, un nivel de agrupación puede no ser suficiente.

Ejemplo realista: quieres que las rutas autorizadas se agrupen con auth middleware, pero dentro necesitas separar más subgrupos, como administrador y usuario simple.

Route::middleware('auth')->group(function() {
 
    Route::middleware('is_admin')->prefix('admin')->group(function() {
    	Route::get(...) // rutas del administrador
    });
 
    Route::middleware('is_user')->prefix('user')->group(function() {
    	Route::get(...) // rutas de usuario
    });
});

Agrupación 3. Repetición del middleware en el grupo

¿Y si tienes muchos middlewares, algunos de los cuales se repiten en varios grupos de rutas?

Route::prefix('students')->middleware(['auth', 'check.role', 'check.user.status', 'check.invoice.status', 'locale'])->group(function () {
    // ... rutas para estudiantes
});
 
Route::prefix('managers')->middleware(['auth', 'check.role', 'check.user.status', 'locale'])->group(function () {
    // ... rutas de los gestores
});

Como puedes ver, hay 5 middlewares, 4 de ellos repetidos. Por lo tanto, podemos mover esos 4 en un grupo de middleware separado, en el archivo app/Http/Kernel.php:

protected $middlewareGroups = [
    // Este grupo viene por defecto de Laravel
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
 
    // This group comes from default Laravel
    'api' => [
        // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
 
    // ESTE ES NUESTRO NUEVO GRUPO DE MIDDLEWARE
    'check_user' => [
        'auth',
        'check.role',
        'check.user.status',
        'locale'
    ],
];

Así que llamamos a nuestro grupo check_user, y ahora podemos acortar las rutas:

Route::prefix('students')->middleware(['check_user', 'check.invoice.status'])->group(function () {
    // ... rutas para estudiantes
});
 
Route::prefix('managers')->middleware(['check_user'])->group(function () {
    // ... rutas de los gestores
});

Agrupación 4. Controladores con el mismo nombre, espacios de nombres diferentes

Una situación bastante común es tener, por ejemplo, HomeController para diferentes roles de usuario, como Admin/HomeController y User/HomeController. Y si usas la ruta completa en tus rutas, se ve algo así:

Route::prefix('admin')->middleware('is_admin')->group(function () {
    Route::get('home', [\App\Http\Controllers\Admin\HomeController::class, 'index']);
});
 
Route::prefix('user')->middleware('is_user')->group(function () {
    Route::get('home', [\App\Http\Controllers\User\HomeController::class, 'index']);
});

Bastante código para escribir con esas rutas completas, ¿verdad? Por eso muchos desarrolladores prefieren tener sólo HomeController::class en la lista de rutas y añadir algo como esto en la parte superior:

use App\Http\Controllers\Admin\HomeController;

¡Pero el problema aquí es que tenemos el mismo nombre de clase de controlador! Por lo tanto, esto no funcionaría:

use App\Http\Controllers\Admin\HomeController;
use App\Http\Controllers\User\HomeController;

¿Cuál sería el "oficial"? Bueno, una forma es cambiar el nombre y asignar el alias de uno de ellos:

use App\Http\Controllers\Admin\HomeController as AdminHomeController;
use App\Http\Controllers\User\HomeController;
 
Route::prefix('admin')->middleware('is_admin')->group(function () {
    Route::get('home', [AdminHomeController::class, 'index']);
});
 
Route::prefix('user')->middleware('is_user')->group(function () {
    Route::get('home', [HomeController::class, 'index']);
});

Pero, personalmente, cambiar el nombre de la clase en la parte superior es bastante confuso para mí, me gusta otro enfoque: añadir un namespace() para las subcarpetas de los Controladores:

Route::prefix('admin')->namespace('App\Http\Controllers\Admin')->middleware('is_admin')->group(function () {
    Route::get('home', [HomeController::class, 'index']);
    // ... otros controladores del espacio de nombres Admin
});
 
Route::prefix('user')->namespace('App\Http\Controllers\User')->middleware('is_user')->group(function () {
    Route::get('home', [HomeController::class, 'index']);
    // ... otros controladores del espacio de nombres Usuario
});

Agrupación 5. Archivos de ruta separados

Si sientes que tu archivo principal routes/web.php o routes/api.php se está haciendo demasiado grande, puedes tomar algunas de las rutas y ponerlas en un archivo separado, llámalo como quieras, como routes/admin.php.

Luego, para permitir que ese archivo sea incluido, tienes dos maneras: Yo llamo la "manera Laravel" y la "manera PHP".

Si quieres seguir la estructura de cómo Laravel estructura sus archivos de rutas por defecto, esto sucede en el app/Providers/RouteServiceProvider.php:

public function boot()
{
    $this->configureRateLimiting();
 
    $this->routes(function () {
        Route::middleware('api')
            ->prefix('api')
            ->group(base_path('routes/api.php'));
 
        Route::middleware('web')
            ->group(base_path('routes/web.php'));
    });
}

Como puedes ver, tanto routes/api.php como routes/web.php están aquí, con una configuración un poco diferente. Por lo tanto, todo lo que necesitas hacer es añadir tu archivo de administración aquí:

$this->routes(function () {
    Route::middleware('api')
        ->prefix('api')
        ->group(base_path('routes/api.php'));
 
    Route::middleware('web')
        ->group(base_path('routes/web.php'));
 
    Route::middleware('is_admin')
        ->group(base_path('routes/admin.php'));
});

Pero si no quieres sumergirte en los proveedores de servicios, hay una manera más corta - simplemente incluye/requiere tu archivo de rutas en otro archivo como lo harías en cualquier archivo PHP, fuera del framework Laravel.

De hecho, lo hace el propio Taylor Otwell, requiriendo el archivo routes/auth.php directamente en las rutas de Laravel Breeze:

routes/web.php:

Route::get('/', function () {
    return view('welcome');
});
 
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth'])->name('dashboard');
 
require __DIR__.'/auth.php';

Agrupación 6. Nuevo en Laravel 9: Route::controller()

Si tienes unos cuantos métodos en el Controlador pero no siguen la estructura estándar de los Recursos, puedes seguir agrupándolos, sin repetir el nombre del Controlador para cada método.

En su lugar:

Route::get('profile', [ProfileController::class, 'getProfile']);
Route::put('profile', [ProfileController::class, 'updateProfile']);
Route::delete('profile', [ProfileController::class, 'deleteProfile']);

Puedes hacerlo:

Route::controller(ProfileController::class)->group(function() {
    Route::get('profile', 'getProfile');
    Route::put('profile', 'updateProfile');
    Route::delete('profile', 'deleteProfile');
});

Esta funcionalidad está disponible en Laravel 9, y en las últimas versiones menores de Laravel 8.


Eso es todo, estas son las técnicas de agrupación que, con suerte, te ayudarán a organizar y mantener tus rutas, por muy grande que sea tu proyecto.

Artículo traducido del blog de Laravel New


0 comentarios

Inicia sesión para comentar