Controladores
Introducción
En lugar de definir toda la lógica de manejo de solicitudes como closures en tus archivos de rutas, puedes organizar este comportamiento utilizando clases de controller
. Los controllers
pueden agrupar la lógica de manejo de solicitudes relacionadas en una sola clase. Por ejemplo, una clase UserController
podría manejar todas las solicitudes entrantes relacionadas con usuarios, incluyendo mostrar, crear, actualizar y eliminar usuarios. Por defecto, los controllers
se almacenan en el directorio app/Http/Controllers
.
Escribiendo Controllers
Controllers Básicos
Para generar rápidamente un nuevo controller
, puedes ejecutar el comando Artisan make:controller
. Por defecto, todos los controllers
de tu aplicación se almacenan en el directorio app/Http/Controllers
:
php artisan make:controller UserController
Veamos un ejemplo de un controller
básico. Un controller
puede tener cualquier número de métodos públicos que responderán a solicitudes HTTP entrantes:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Muestra el perfil de un usuario dado.
*/
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
Una vez que hayas escrito una clase y un método de controller
, puedes definir una ruta al método del controller
de la siguiente manera:
use App\Http\Controllers\UserController;
Route::get('/user/{id}', [UserController::class, 'show']);
Cuando una solicitud entrante coincide con el URI de ruta especificado, se invocará el método show
en la clase App\Http\Controllers\UserController
y los parámetros de la ruta se pasarán al método.
Los controllers
no están obligados a extender una clase base. Sin embargo, a veces es conveniente extender una clase base de controller
que contenga métodos que deberían compartirse entre todos tus controllers
.
Controllers de Acción Única
Si una acción de controller
es particularmente compleja, podrías encontrar conveniente dedicar una clase de controller
completa a esa única acción. Para lograr esto, puedes definir un único método __invoke
dentro del controller
:
<?php
namespace App\Http\Controllers;
class ProvisionServer extends Controller
{
/**
* Provisión de un nuevo servidor web.
*/
public function __invoke()
{
// ...
}
}
Al registrar rutas para controllers
de acción única, no necesitas especificar un método de controller
. En su lugar, puedes simplemente pasar el nombre del controller
al enrutador:
use App\Http\Controllers\ProvisionServer;
Route::post('/server', ProvisionServer::class);
Puedes generar un controller
invocable utilizando la opción --invokable
del comando Artisan make:controller
:
php artisan make:controller ProvisionServer --invokable
Los stubs de controller
pueden personalizarse utilizando la publicación de stubs.
Middleware de Controller
El middleware
puede asignarse a las rutas del controller
en tus archivos de rutas:
Route::get('/profile', [UserController::class, 'show'])->middleware('auth');
O, podrías encontrar conveniente especificar middleware
dentro de tu clase de controller
. Para hacerlo, tu controller
debe implementar la interfaz HasMiddleware
, que dicta que el controller
debe tener un método estático middleware
. Desde este método, puedes devolver un array de middleware
que debería aplicarse a las acciones del controller
:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class UserController extends Controller implements HasMiddleware
{
/**
* Obtén el middleware que debería asignarse al controller.
*/
public static function middleware(): array
{
return [
'auth',
new Middleware('log', only: ['index']),
new Middleware('subscribed', except: ['store']),
];
}
// ...
}
También puedes definir middleware
de controller
como closures, lo que proporciona una forma conveniente de definir un middleware
en línea sin escribir una clase de middleware
completa:
use Closure;
use Illuminate\Http\Request;
/**
* Obtén el middleware que debería asignarse al controller.
*/
public static function middleware(): array
{
return [
function (Request $request, Closure $next) {
return $next($request);
},
];
}
Controllers de Recursos
Si piensas en cada modelo Eloquent en tu aplicación como un «recurso», es típico realizar los mismos conjuntos de acciones contra cada recurso en tu aplicación. Por ejemplo, imagina que tu aplicación contiene un modelo Photo
y un modelo Movie
. Es probable que los usuarios puedan crear, leer, actualizar o eliminar estos recursos.
Debido a este caso de uso común, el enrutamiento de recursos de Laravel asigna las rutas típicas de crear, leer, actualizar y eliminar («CRUD») a un controller
con una sola línea de código. Para comenzar, podemos usar la opción --resource
del comando Artisan make:controller
para crear rápidamente un controller
que maneje estas acciones:
php artisan make:controller PhotoController --resource
Este comando generará un controller
en app/Http/Controllers/PhotoController.php
. El controller
contendrá un método para cada una de las operaciones de recursos disponibles. A continuación, puedes registrar una ruta de recurso que apunte al controller
:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);
Esta única declaración de ruta crea múltiples rutas para manejar una variedad de acciones en el recurso. El controller
generado ya tendrá métodos creados para cada una de estas acciones. Recuerda, siempre puedes obtener una visión general rápida de las rutas de tu aplicación ejecutando el comando Artisan route:list
.
Incluso puedes registrar muchos controllers
de recursos a la vez pasando un array al método resources
:
Route::resources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
Acciones Manejadas por Controllers de Recursos
Verbo | URI | Acción | Nombre de Ruta |
---|---|---|---|
GET | /photos | index | photos.index |
GET | /photos/create | create | photos.create |
POST | /photos | store | photos.store |
GET | /photos/{photo} | show | photos.show |
GET | /photos/{photo}/edit | edit | photos.edit |
PUT/PATCH | /photos/{photo} | update | photos.update |
DELETE | /photos/{photo} | destroy | photos.destroy |
Personalizando el Comportamiento de Modelos Faltantes
Normalmente, se generará una respuesta HTTP 404 si no se encuentra un modelo de recurso vinculado implícitamente. Sin embargo, puedes personalizar este comportamiento llamando al método missing
al definir tu ruta de recurso. El método missing
acepta un closure que se invocará si no se puede encontrar un modelo vinculado implícitamente para cualquiera de las rutas del recurso:
use App\Http\Controllers\PhotoController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
Route::resource('photos', PhotoController::class)
->missing(function (Request $request) {
return Redirect::route('photos.index');
});
Modelos Eliminados Suavemente
Normalmente, la vinculación implícita de modelos no recuperará modelos que han sido eliminados suavemente, y en su lugar devolverá una respuesta HTTP 404. Sin embargo, puedes instruir al framework para que permita modelos eliminados suavemente invocando el método withTrashed
al definir tu ruta de recurso:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->withTrashed();
Llamar a withTrashed
sin argumentos permitirá modelos eliminados suavemente para las rutas de recurso show
, edit
y update
. Puedes especificar un subconjunto de estas rutas pasando un array al método withTrashed
:
Route::resource('photos', PhotoController::class)->withTrashed(['show']);
Especificando el Modelo de Recurso
Si estás utilizando la vinculación de modelos de ruta y deseas que los métodos del controller
de recursos hagan type-hint a una instancia de modelo, puedes usar la opción --model
al generar el controller
:
php artisan make:controller PhotoController --model=Photo --resource
Generando Solicitudes de Formulario
Puedes proporcionar la opción --requests
al generar un controller
de recursos para instruir a Artisan que genere clases de solicitud de formulario para los métodos de almacenamiento y actualización del controller
:
php artisan make:controller PhotoController --model=Photo --resource --requests
Rutas de Recursos Parciales
Al declarar una ruta de recurso, puedes especificar un subconjunto de acciones que el controller
debería manejar en lugar del conjunto completo de acciones predeterminadas:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->only([
'index', 'show'
]);
Route::resource('photos', PhotoController::class)->except([
'create', 'store', 'update', 'destroy'
]);
Rutas de Recursos de API
Al declarar rutas de recursos que serán consumidas por APIs, comúnmente querrás excluir rutas que presenten plantillas HTML como create
y edit
. Para mayor comodidad, puedes usar el método apiResource
para excluir automáticamente estas dos rutas:
use App\Http\Controllers\PhotoController;
Route::apiResource('photos', PhotoController::class);
Puedes registrar muchos controllers
de recursos de API a la vez pasando un array al método apiResources
:
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;
Route::apiResources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
Para generar rápidamente un controller
de recursos de API que no incluya los métodos create
o edit
, usa el switch --api
al ejecutar el comando make:controller
:
php artisan make:controller PhotoController --api
Recursos Anidados
A veces puedes necesitar definir rutas para un recurso anidado. Por ejemplo, un recurso photo
puede tener múltiples comentarios que pueden estar adjuntos a la foto. Para anidar los controllers
de recursos, puedes usar la notación de «punto» en tu declaración de ruta:
use App\Http\Controllers\PhotoCommentController;
Route::resource('photos.comments', PhotoCommentController::class);
Esta ruta registrará un recurso anidado que puede ser accedido con URIs como los siguientes:
/photos/{photo}/comments/{comment}
Alcance de Recursos Anidados
La característica de vinculación implícita de modelos de Laravel puede automáticamente delimitar las vinculaciones anidadas de manera que el modelo hijo resuelto se confirme que pertenece al modelo padre. Al usar el método scoped
al definir tu recurso anidado, puedes habilitar el alcance automático así como instruir a Laravel qué campo debería ser utilizado para recuperar el recurso hijo. Para más información sobre cómo lograr esto, por favor consulta la documentación sobre el alcance de rutas de recursos.
Anidamiento Superficial
A menudo, no es completamente necesario tener tanto el ID del padre como el del hijo dentro de un URI ya que el ID del hijo es ya un identificador único. Al usar identificadores únicos como claves primarias auto-incrementales para identificar tus modelos en segmentos de URI, puedes optar por usar «anidamiento superficial»:
use App\Http\Controllers\CommentController;
Route::resource('photos.comments', CommentController::class)->shallow();
Esta definición de ruta definirá las siguientes rutas:
Verbo | URI | Acción | Nombre de Ruta |
---|---|---|---|
GET | /photos/{photo}/comments | index | photos.comments.index |
GET | /photos/{photo}/comments/create | create | photos.comments.create |
POST | /photos/{photo}/comments | store | photos.comments.store |
GET | /comments/{comment} | show | comments.show |
GET | /comments/{comment}/edit | edit | comments.edit |
PUT/PATCH | /comments/{comment} | update | comments.update |
DELETE | /comments/{comment} | destroy | comments.destroy |
Nombrando Rutas de Recursos
Por defecto, todas las acciones del controller
de recursos tienen un nombre de ruta; sin embargo, puedes sobrescribir estos nombres pasando un array names
con los nombres de ruta deseados:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->names([
'create' => 'photos.build'
]);
Nombrando Parámetros de Rutas de Recursos
Por defecto, Route::resource
creará los parámetros de ruta para tus rutas de recursos basándose en la versión «singularizada» del nombre del recurso. Puedes sobrescribir esto fácilmente por recurso utilizando el método parameters
. El array pasado al método parameters
debe ser un array asociativo de nombres de recursos y nombres de parámetros:
use App\Http\Controllers\AdminUserController;
Route::resource('users', AdminUserController::class)->parameters([
'users' => 'admin_user'
]);
El ejemplo anterior genera el siguiente URI para la ruta show
del recurso:
/users/{admin_user}
Alcance de Rutas de Recursos
La característica de vinculación implícita de modelos con alcance de Laravel puede automáticamente delimitar las vinculaciones anidadas de manera que el modelo hijo resuelto se confirme que pertenece al modelo padre. Al usar el método scoped
al definir tu recurso anidado, puedes habilitar el alcance automático así como instruir a Laravel qué campo debería ser utilizado para recuperar el recurso hijo:
use App\Http\Controllers\PhotoCommentController;
Route::resource('photos.comments', PhotoCommentController::class)->scoped([
'comment' => 'slug',
]);
Esta ruta registrará un recurso anidado con alcance que puede ser accedido con URIs como los siguientes:
/photos/{photo}/comments/{comment:slug}
Al usar una vinculación implícita con clave personalizada como un parámetro de ruta anidado, Laravel automáticamente delimitará la consulta para recuperar el modelo anidado por su padre utilizando convenciones para adivinar el nombre de la relación en el padre. En este caso, se asumirá que el modelo Photo
tiene una relación llamada comments
(el plural del nombre del parámetro de ruta) que puede ser utilizada para recuperar el modelo Comment
.
Localizando URIs de Recursos
Por defecto, Route::resource
creará URIs de recursos utilizando verbos y reglas de pluralización en inglés. Si necesitas localizar los verbos de las acciones create
y edit
, puedes usar el método Route::resourceVerbs
. Esto puede hacerse al inicio del método boot
dentro del App\Providers\AppServiceProvider
de tu aplicación:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
}
El pluralizador de Laravel soporta varios idiomas diferentes que puedes configurar según tus necesidades. Una vez que los verbos y el idioma de pluralización han sido personalizados, un registro de ruta de recurso como Route::resource('publicacion', PublicacionController::class)
producirá los siguientes URIs:
/publicacion/crear
/publicacion/{publicaciones}/editar
Complementando Controllers de Recursos
Si necesitas agregar rutas adicionales a un controller
de recursos más allá del conjunto predeterminado de rutas de recursos, debes definir esas rutas antes de tu llamada al método Route::resource
; de lo contrario, las rutas definidas por el método resource
pueden tomar precedencia sobre tus rutas suplementarias:
use App\Http\Controllers\PhotoController;
Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);
Recuerda mantener tus controllers
enfocados. Si te encuentras necesitando rutinariamente métodos fuera del conjunto típico de acciones de recursos, considera dividir tu controller
en dos controllers
más pequeños.
Controllers de Recursos Singleton
A veces, tu aplicación tendrá recursos que solo pueden tener una única instancia. Por ejemplo, el «perfil» de un usuario puede ser editado o actualizado, pero un usuario no puede tener más de un «perfil». Del mismo modo, una imagen puede tener una única «miniatura». Estos recursos se llaman «recursos singleton», lo que significa que solo puede existir una instancia del recurso. En estos escenarios, puedes registrar un controller
de recurso «singleton»:
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::singleton('profile', ProfileController::class);
La definición de recurso singleton anterior registrará las siguientes rutas. Como puedes ver, no se registran rutas de «creación» para recursos singleton, y las rutas registradas no aceptan un identificador ya que solo puede existir una instancia del recurso:
Verbo | URI | Acción | Nombre de Ruta |
---|---|---|---|
GET | /profile | show | profile.show |
GET | /profile/edit | edit | profile.edit |
PUT/PATCH | /profile | update | profile.update |
Los recursos singleton también pueden ser anidados dentro de un recurso estándar:
Route::singleton('photos.thumbnail', ThumbnailController::class);
En este ejemplo, el recurso photos
recibiría todas las rutas de recursos estándar; sin embargo, el recurso thumbnail
sería un recurso singleton con las siguientes rutas:
Verbo | URI | Acción | Nombre de Ruta |
---|---|---|---|
GET | /photos/{photo}/thumbnail | show | photos.thumbnail.show |
GET | /photos/{photo}/thumbnail/edit | edit | photos.thumbnail.edit |
PUT/PATCH | /photos/{photo}/thumbnail | update | photos.thumbnail.update |
Recursos Singleton Creables
Ocasionalmente, puedes querer definir rutas de creación y almacenamiento para un recurso singleton. Para lograr esto, puedes invocar el método creatable
al registrar la ruta del recurso singleton:
Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable();
En este ejemplo, se registrarán las siguientes rutas. Como puedes ver, también se registrará una ruta DELETE
para recursos singleton creables:
Verbo | URI | Acción | Nombre de Ruta |
---|---|---|---|
GET | /photos/{photo}/thumbnail/create | create | photos.thumbnail.create |
POST | /photos/{photo}/thumbnail | store | photos.thumbnail.store |
GET | /photos/{photo}/thumbnail | show | photos.thumbnail.show |
GET | /photos/{photo}/thumbnail/edit | edit | photos.thumbnail.edit |
PUT/PATCH | /photos/{photo}/thumbnail | update | photos.thumbnail.update |
DELETE | /photos/{photo}/thumbnail | destroy | photos.thumbnail.destroy |
Si deseas que Laravel registre la ruta DELETE
para un recurso singleton pero no registre las rutas de creación o almacenamiento, puedes utilizar el método destroyable
:
Route::singleton(...)->destroyable();
Recursos Singleton de API
El método apiSingleton
puede ser utilizado para registrar un recurso singleton que será manipulado a través de una API, eliminando así la necesidad de las rutas create
y edit
:
Route::apiSingleton('profile', ProfileController::class);
Por supuesto, los recursos singleton de API también pueden ser creables, lo que registrará las rutas store
y destroy
para el recurso:
Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();
Inyección de Dependencias y Controllers
Inyección en el Constructor
El contenedor de servicios de Laravel se utiliza para resolver todos los controllers
de Laravel. Como resultado, puedes hacer type-hint de cualquier dependencia que tu controller
pueda necesitar en su constructor. Las dependencias declaradas se resolverán e inyectarán automáticamente en la instancia del controller
:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* Crea una nueva instancia del controller.
*/
public function __construct(
protected UserRepository $users,
) {}
}
Inyección en Métodos
Además de la inyección en el constructor, también puedes hacer type-hint de dependencias en los métodos de tu controller
. Un caso de uso común para la inyección en métodos es inyectar la instancia de Illuminate\Http\Request
en los métodos de tu controller
:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Almacena un nuevo usuario.
*/
public function store(Request $request): RedirectResponse
{
$name = $request->name;
// Almacena el usuario...
return redirect('/users');
}
}
Si el método de tu controller
también espera entrada de un parámetro de ruta, lista tus argumentos de ruta después de tus otras dependencias. Por ejemplo, si tu ruta está definida de la siguiente manera:
use App\Http\Controllers\UserController;
Route::put('/user/{id}', [UserController::class, 'update']);
Aún puedes hacer type-hint de Illuminate\Http\Request
y acceder a tu parámetro id
definiendo tu método de controller
de la siguiente manera:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Actualiza el usuario dado.
*/
public function update(Request $request, string $id): RedirectResponse
{
// Actualiza el usuario...
return redirect('/users');
}
}