Los controladores permiten estructurar mejor el código de nuestra aplicación. Su principal utilidad radica en liberar a los archivos de rutas de tener que ocuparse también de gestionar cierta lógica común de las peticiones, como el acceso a los datos, validación de formularios, etc. Además, a medida que la aplicación crezca, el archivo de rutas puede ser demasiado grande si tiene que almacenar también la lógica de cada ruta, y el tiempo de procesamiento del archivo también crecerá. Lo mejor es dividir esa lógica en controladores.
Para definir un controlador en nuestra aplicación, tenemos que echar mano de nuevo del comando php artisan
visto previamente. En concreto, utilizaremos la opción make:controller
seguida del nombre que le queramos dar al controlador. Típicamente, los nombres de controladores terminan con el sufijo Controller, por lo que podemos crear uno de prueba así:
php artisan make:controller PruebaController
Esto generará una clase vacía con el nombre del controlador. Por defecto, los controladores se guardan en la subcarpeta app/Http/Controllers
de nuestro proyecto Laravel.
El comando anterior admite algunos parámetros adicionales más. Uno muy útil es el parámetro -i
, que crea el controlador con un método llamado __invoke
, que se auto ejecuta cuando es llamado desde algún proceso de enrutamiento. Por ejemplo, si creamos el controlador así:
php artisan make:controller PruebaController -i
Se creará la clase PruebaController
en la carpeta app/Http/Controllers
, con un contenido como éste:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PruebaController extends Controller
{
...
public function __invoke(Request $request)
{
...
}
}
Dentro del método __invoke
podemos definir la lógica de generar u obtener los datos que necesita una vista, y renderizarla. Por ejemplo:
public function __invoke(Request $request)
{
$datos = array(...);
return view('miVista', compact('datos'));
}
Así, en el archivo de rutas, basta con definir la ruta que queramos, y como segundo parámetro del método get
, indicar el nombre del controlador que se va a disparar para procesar esa ruta. Adicionalmente, también le podemos asignar un nombre a la ruta, como ya hemos hecho en ejemplos anteriores.
Route::get('prueba', 'PruebaController')->name('prueba');
Alternativamente (y de manera especial desde Laravel 8, donde la anterior forma de definir rutas asociadas a controlador ya no funciona directamente), podemos especificar la clase del controlador, incluyendo con use
dicha clase en el archivo de rutas:
use App\Http\Controllers\PruebaController;
...
Route::get('prueba', PruebaController::class)->name('prueba');
Los controladores de tipo invoke son útiles para definir controladores simples, donde sólo necesitamos un método para procesar rutas asociadas a una entidad del programa. Esto no es lo habitual, ya que, por ejemplo, para una misma entidad (como puedan ser los libros de nuestra biblioteca), necesitaremos métodos para listarlos, crearlos, borrarlos, etc. En este caso, podemos crear otros tipos de controladores que ofrecen más métodos disponibles.
Si creamos un controlador con la opción -r
en lugar de la opción -i
utilizada en el ejemplo anterior, creará un controlador de recursos (resources
), y predefinirá en él una serie de métodos de utilidad para las operaciones principales que se pueden realizar sobre una entidad de nuestra aplicación:
index
: muestra un listado de los elementos de esa entidad o recursocreate
: muestra el formulario para dar de alta nuevos elementosstore
: almacena en la base de datos el recurso creado con el formulario anteriorshow
: muestra los datos de un recurso específico (a partir de su clave o id).edit
: muestra el formulario para editar un recurso existenteupdate
: actualiza en la base de datos el recurso editado con el formulario anteriordestroy
: elimina un recurso por su identificador.Obviamente, el código de todos estos métodos aparecerá vacío al principio, y los deberemos rellenar con las operaciones correspondientes más adelante.
Si queremos utilizar un controlador de este tipo, y llamar a alguno de sus métodos desde alguna ruta, ya no basta con poner el nombre del controlador, como hacíamos antes con los de tipo invoke, puesto que ahora hay más de un método que elegir. Lo que haremos será poner el nombre del controlador, seguido de una arroba @
y el nombre del método a invocar. Por ejemplo:
Route::get('prueba', 'PruebaController@index')->name('listado_prueba');
También de forma alternativa podemos emplear la propia clase del controlador en la definición de la ruta (recomendado desde Laravel 8). En este caso, definimos un array con la clase del controlador y el método a llamar (index
en nuestro caso):
use App\Http\Controllers\PruebaController;
...
Route::get('prueba', [PruebaController::class, 'index'])
->name('listado_prueba');
Vamos a probar esta opción en nuestro proyecto de biblioteca. Crearemos un controlador para gestionar los libros, con este comando:
php artisan make:controller -r LibroController
De momento varios de los métodos generados en el controlador no los vamos a utilizar. Podemos modificar los dos que sí vamos a usar de momento (index
y show
) y poner en ellos lo que antes teníamos en el archivo de rutas. Así nos quedarían, respectivamente:
public function index()
{
$libros = array(
array("titulo" => "El juego de Ender",
"autor" => "Orson Scott Card"),
array("titulo" => "La tabla de Flandes",
"autor" => "Arturo Pérez Reverte"),
array("titulo" => "La historia interminable",
"autor" => "Michael Ende"),
array("titulo" => "El Señor de los Anillos",
"autor" => "J.R.R. Tolkien")
);
return view('listado', compact('libros'));
}
public function show($id)
{
return "Mostrando ficha de libro $id";
}
NOTA: el método
show
no lo habíamos implementado en la sesión anterior, pero básicamente lo vamos a utilizar para mostrar la ficha de un libro. De momento mostramos sólo el id del libro recibido, como texto plano.
Estas dos rutas quedarían ahora así en el archivo routes/web.php
(eliminaríamos la vieja ruta de listado de posts):
Route::get('libros', 'LibroController@index');
Route::get('libros/{id}', 'LibroController@show');
O de forma alternativa (desde Laravel 8):
use App\Http\Controllers\LibroController;
...
Route::get('libros', [LibroController::class, 'index']);
Route::get('libros/{id}', [LibroController::class, 'show']);
Como alternativa a los controladores de recursos vistos antes, podemos crear los controladores con la opción --api
. Creará un controlador con los mismos métodos que el de recursos, salvo los métodos create
y edit
, encargados de mostrar los formularios de creación y edición de recursos, ya que en las APIs estos formularios no son necesarios, como veremos en sesiones posteriores.
A medida que el proyecto crece, generaremos un buen número de vistas asociadas a controladores, y es necesario estructurar estas vistas de una forma adecuada para poderlas identificar rápidamente. Una convención que podemos seguir es nombrar las vistas a partir del controlador o modelo al que hacen referencia, y a la operación que realizan. Por ejemplo, si tenemos un controlador llamado PruebaController
, se supone que actuará sobre una tabla llamada pruebas
(lo veremos más adelante, en la sesión de acceso a datos). En nuestro caso de la biblioteca, podemos almacenar las vistas de los libros de la biblioteca en la subcarpeta resources/views/libros
, y definir dentro las vistas asociadas a cada operación del controlador que tengamos definida. Por ejemplo:
index.blade.php
show.blade.php
Paralelamente, cada vez que vayamos a cargar una vista desde algún controlador o ruta, haremos referencia a este nombre. Así, si queremos renderizar la vista show
para los libros desde el método show
del controlador de libros, haríamos algo así (pasándole como parámetro el id del libro a buscar, para que lo saque en la vista por ahora):
public function show($id)
{
return view('libros.show', compact('id'));
}
Del mismo modo, los nombres que asociemos a las rutas deberían seguir este mismo patrón.
Al final de todo el proceso de implementación de un controlador (de recursos o de API) tendremos en el archivo de rutas una dedicada a cada método del controlador (una para index
, otra para show
, etc.). Estas rutas pueden agruparse en una sola usando el método resource
de la clase Route
, en lugar de get
, indicándole como parámetros el nombre base de la ruta, y el controlador que se va a encargar de ella:
use App\Http\Controllers\LibroController;
...
Route::resource('libros', LibroController::class);
La ruta anterior definirá una ruta GET hacia /libros
, atendida por el método index
del controlador, otra ruta GET hacia /libros/{id}
atendida por el método show
del controlador… etc.
IMPORTANTE: a través de la ruta
Route::resource
sólo se incluyen los métodos estándar de un controlador de recursos (index
,show
,create
,edit
, etc), pero NO los que añadamos a mano después en dicho controlador con otros nombres.
También podemos utilizar el método only
para indicar para qué métodos queremos rutas:
use App\Http\Controllers\LibroController;
...
Route::resource('libros', LibroController::class)
->only(['index', 'show']);
Desde el lado opuesto, tenemos disponible el método except
para indicar que se generen todas las rutas excepto aquellas para los métodos indicados:
use App\Http\Controllers\LibroController;
...
Route::resource('libros', LibroController::class)
->except(['update', 'edit']);
Con los controladores de tipo API también podemos generar automáticamente todas las rutas para sus métodos, utilizando el método apiResource
de la clase Route
, en lugar del método resource
empleado antes:
use App\Http\Controllers\PruebaController;
...
Route::apiResource('prueba', PruebaController::class);
Si generamos rutas automáticas para los métodos de un controlador, veremos que para los formularios de crear y editar se define una ruta terminada en /create
o en /edit
, respectivamente. Esto puede chocar si pretendemos una web hecha en otro idioma. Pero podemos cambiar el nombre que se genera automáticamente para estas rutas, editando el proveedor de servicios AppServiceProvider
, ubicado en la carpeta app/Providers
. En el método boot
, podemos llamar al método resourceVerbs
de la clase Route
y renombrar los verbos utilizados para acceder a las rutas del recurso. Por ejemplo:
public function boot()
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar'
])
}
NOTA: deberemos incluir con
use
el espacio de nombresIlluminate\Support\Facades\Route
para poder emplear la claseRoute
en el proveedor de servicios.
Para nuestro ejemplo de la biblioteca, podemos devolver un texto plano en los métodos create
y edit
que indiquen que ahí va un formulario:
public function create()
{
return "Formulario de inserción de libros";
}
public function edit()
{
return "Formulario de edición de libros";
}
Nuestro archivo de rutas se puede quedar con esta única instrucción para todas las rutas de libros, indicando que por ahora sólo vamos a gestionar el listado, la ficha y los dos formularios:
Route::resource('libros', LibroController::class)
->only(['index', 'show', 'create', 'edit']);
Ahora ya podemos acceder a estas 4 URLs y ver la respuesta correspondiente:
index
)show
para el libro 3)create
)edit
para el libro 3)Notar que la URL aparece en castellano gracias a los cambios en AppServiceProvider
, pero los nombres de funciones en los controladores siguen estando en inglés, así como en los elementos de la llamada al método only
.
Podemos consultar el conjunto de rutas al que está respondiendo nuestra aplicación en todo momento con este comando:
php artisan route:list