Controladores
-
class Cake\Controller\Controller
Los controladores son la “C” en MVC. Después de aplicar el enrutamiento y
que el controlador
ha sido encontrado, la acción de tu controlador es llamado. Tu controlador
debe manejar la interpretación de los datos de la solicitud,
asegurándose de que se llamen
a los modelos correctos y se muestre la respuesta o vista correcta.
Los controladores se pueden
considerar como una capa intermedia entre el Modelo y la Vista. Quieres mantener
tus controladores delgados, y tus modelos gruesos.
Esto te ayudará a reutilizar tu código y lo hará más fácil de probar.
Comúnmente, un controlador se usa para administrar la lógica en torno
a un solo modelo. Por
ejemplo, si estuvieras construyendo un sitio online para una panadería,
podrías tener un
RecipesController que gestiona tus recetas y un IngredientsController
que gestiona tus
ingredientes. Sin embargo, es posible hacer que los controladores trabajen
con más de
un modelo. En CakePHP, un controlador es nombrado a raíz del modelo que maneja.
Los controladores de tu aplicación extienden de la clase AppController
,
que a su vez extiende de la clase principal Controller
.
La clase AppController
puede ser definida en src/Controller/AppController.php
y debería contener los métodos que se comparten entre todos los controladores
de tu aplicación.
Los controladores proveen una serie de métodos que manejan las peticiones. Estos
son llamadas acciones. Por defecto, cada método público en un controlador es
una acción, y es accesible mediante una URL. Una acción es responsable de
interpretar la petición y crear la respuesta. Por lo general, las respuestas
son de la forma de una vista renderizada, pero también, hay otras maneras de crear
respuestas.
El App Controller
Como se indicó en la introducción, la clase AppController
es clase padre de
todos los controladores de tu aplicación. AppController
extiende de la clase
Cake\Controller\Controller
que está incluida en CakePHP.
AppController
se define en src/Controller/AppController.php como se
muestra a continuación:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
}
Los atributos y métodos del controlador creados en tu AppController
van a
estar disponibles en todos los controladores que extiendan de este. Los
componentes (que aprenderás más adelante) son mejor usados para código que se
encuentra en muchos (pero no necesariamente en todos) los componentes.
Puedes usar tu AppController
para cargar componentes que van a ser utilizados
en cada controlador de tu aplicación. CakePHP proporciona un método initialize()
que es llamado al final del constructor de un controlador para este tipo de uso:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize(): void
{
// Always enable the FormProtection component.
$this->loadComponent('FormProtection');
}
}
Flujo de solicitud
Cuando se realiza una solicitud a una aplicación CakePHP, las clases CakePHP
Cake\Routing\Router
y Cake\Routing\Dispatcher
usan Conectando Rutas para encontrar y crear la instancia correcta
del controlador. Los datos de la solicitud son encapsulados en un objeto de
solicitud. CakePHP pone toda la información importante de la solicitud en la
propiedad $this->request
. Consulta la sección sobre Solicitud (Request)
para obtener más información sobre el objeto de solicitud de CakePHP.
Acciones del controlador
Las acciones del controlador son las responsables de convertir los parámetros
de la solicitud en una respuesta para el navegador/usuario que realiza la
petición. CakePHP usa convenciones para automatizar este proceso y eliminar
algunos códigos repetitivos que de otro modo se necesitaría escribir.
Por convención, CakePHP renderiza una vista con una versión en infinitivo del nombre
de la acción. Volviendo a nuestro ejemplo de la panadería online, nuestro
RecipesController podría contener las acciones view()
, share()
, y
search()
. El controlador sería encontrado en
src/Controller/RecipesController.php y contiene:
// src/Controller/RecipesController.php
class RecipesController extends AppController
{
public function view($id)
{
// La lógica de la acción va aquí.
}
public function share($customerId, $recipeId)
{
// La lógica de la acción va aquí.
}
public function search($query)
{
// La lógica de la acción va aquí.
}
}
Las plantillas para estas acciones serían templates/Recipes/view.php,
templates/Recipes/share.php, y templates/Recipes/search.php. El nombre
convencional para un archivo de vista es con minúsculas y con el nombre de la
acción entre guiones bajos.
Las acciones de los controladores por lo general usan Controller::set()
para
crear un contexto que View
usa para renderizar la capa de vista. Debido
a las convenciones que CakePHP usa, no necesitas crear y renderizar la vista
manualmente. En su lugar, una vez que se ha completado la acción del controlador,
CakePHP se encargará de renderizar y entregar la vista.
Si por algún motivo deseas omitir el comportamiento predeterminado, puedes retornar
un objeto Cake\Http\Response
de la acción con la respuesta creada.
Para que puedas usar un controlador de manera efectiva en tu aplicación,
cubriremos algunos de los atributos y métodos principales proporcionados por los
controladores de CakePHP.
Interactuando con vistas
Los controladores interactúan con las vistas de muchas maneras. Primero, los
controladores son capaces de pasar información a las vistas, usando Controller::set()
.
También puedes decidir qué clase de vista usar, y qué archivo de vista debería
ser renderizado desde el controlador.
Configuración de variables de vista
-
Cake\Controller\Controller::set(string $var, mixed $value)
El método Controller::set()
es la manera principal de mandar información
desde el controlador a la vista. Una vez que hayas utilizado Controller::set()
,
la variable puede ser accedida en tu vista:
// Primero pasas las información desde el controlador:
$this->set('color', 'rosa');
// Después, en la vista, puede utilizar la información:
?>
Has seleccionado cubierta <?= h($color) ?> para la tarta.
El método Controller::set()
también toma un array asociativo como su primer
parámetro. A menudo, esto puede ser una forma rápida de asignar un conjunto de
información a la vista:
$data = [
'color' => 'pink',
'type' => 'sugar',
'base_price' => 23.95,
];
// Hace $color, $type, y $base_price
// disponible para la vista:
$this->set($data);
Ten en cuenta que las variables de la vista se comparten entre todas las partes
renderizadas por tu vista. Estarán disponibles en todas las partes de la vista:
la plantilla y todos los elementos dentro de estas dos.
Configuración de las opciones de la vista
Si deseas personalizar la clase vista, las rutas de diseño/plantillas, ayudantes
o el tema que se usarán para renderizar la vista, puede usar el método viewBuilder()
para obtener un constructor. Este constructor se puede utilizar para definir
propiedades de la vista antes de crearlas:
$this->viewBuilder()
->addHelper('MyCustom')
->setTheme('Modern')
->setClassName('Modern.Admin');
Lo anterior muestra cómo puedes cargar ayudantes personalizados, configurar el tema
y usar una clase vista personalizada.
Renderizando una vista
-
Cake\Controller\Controller::render(string $view, string $layout)
El método Controller::render()
es llamado automáticamente al final de cada
solicitud de la acción del controlador. Este método realiza toda la lógica
de la vista (usando la información que has enviado usando el método Controller::set()
),
coloca la vista dentro de su View::$layout
, y lo devuelve al usuario final.
El archivo de vista por defecto utilizado para el renderizado es definido por
convención.
Si la acción search()
de RecipesController es solicitada, el archivo vista en
templates/Recipes/search.php será renderizado:
namespace App\Controller;
class RecipesController extends AppController
{
// ...
public function search()
{
// Renderiza la vista en templates/Recipes/search.php
return $this->render();
}
// ...
}
Aunque CakePHP va a llamarlo automáticamente después de cada acción de lógica
(a menos que llames a $this->disableAutoRender()
), puedes usarlo para
especificar un archivo de vista alternativo especificando el nombre de este como
primer argumento del método Controller::render()
.
Si $view
empieza con “/”, se asume que es una vista o un archivo relacionado
con la carpeta templates. Esto permite el renderizado directo de elementos,
muy útil en llamadas AJAX:
// Renderiza el elemento en templates/element/ajaxreturn.php
$this->render('/element/ajaxreturn');
El segundo parámetro $layout
de Controller::render()
te permita especificar
la estructura con la que la vista es renderizada.
Renderizando una plantilla específica
En tu controlador, puede que quieras renderizar una vista diferente a la que es
convencional. Puedes hacer esto llamando a Controller::render()
directamente.
Una vez que hayas llamado a Controller::render()
, CakePHP no tratará de
re-renderizar la vista:
namespace App\Controller;
class PostsController extends AppController
{
public function my_action()
{
$this->render('custom_file');
}
}
Esto renderizará templates/Posts/custom_file.php en vez de
templates/Posts/my_action.php.
También puedes renderizar vistas dentro de plugins usando la siguiente sintaxis:
$this->render('PluginName.PluginController/custom_file')
. Por ejemplo:
namespace App\Controller;
class PostsController extends AppController
{
public function myAction()
{
$this->render('Users.UserDetails/custom_file');
}
}
Esto renderizará plugins/Users/templates/UserDetails/custom_file.php
Negociación del tipo de contenido
-
Cake\Controller\Controller::viewClasses()
Los controladores pueden definir una lista de clases de vistas que soportan.
Después de que la acción del controlador este completa, CakePHP usará la lista de
vista para realizar negociación del tipo de contenido. Esto permite a tu aplicación
rehusar la misma acción del controlador para renderizar una vista HTML o
renderizar una respuesta JSON o XML. Para definir la lista de clases de vista que
soporta un controlador se utiliza el método viewClasses()
:
namespace App\Controller;
use Cake\View\JsonView;
use Cake\View\XmlView;
class PostsController extends AppController
{
public function viewClasses(): array
{
return [JsonView::class, XmlView::class];
}
}
La clase View
de la aplicación se usa automáticamente como respaldo cuando
no se puede seleccionar otra vista en función del encabezado de la petición Accept
o de la extensión del enrutamiento. Si tu aplicación sólo soporta tipos de contenido
para una acción específica, puedes definir esa lógica dentro de viewClasses()
:
public function viewClasses(): array
{
if ($this->request->getParam('action') === 'export') {
// Usa una vista CSV personalizada para exportación de datos
return [CsvView::class];
}
return [JsonView::class];
}
Si dentro de las acciones de tu controlador necesitas procesar la petición o cargar datos
de forma diferente dependiendo del tipo de contenido puedes usar
Comprobación de las condiciones de la solicitud:
// En la acción de un controlador
// Carga información adicional cuando se preparan respuestas JSON
if ($this->request->is('json')) {
$query->contain('Authors');
}
También puedes definir las clases View soportadas por tu controlador usando
el método addViewClasses()
que unirá la vista proporcionada con
aquellas que están actualmente en la propiedad viewClasses
.
Nota
Las clases de vista deben implementar el método estático contentType()
para participar en las negociaciones del tipo de contenido.
Negociación de tipo de contenido alternativos
Si ninguna vista puede coincidir con las preferencias del tipo de contenido de la
petición, CakePHP usará la clase base View
. Si deseas solicitar una negociación
del tipo de contenido, puedes usar NegotiationRequiredView
que setea un código
de estatus 406:
public function viewClasses(): array
{
// Requiere aceptar la negociación del encabezado o devuelve una respuesta 406.
return [JsonView::class, NegotiationRequiredView::class];
}
Puede usar el valor del tipo de contenido TYPE_MATCH_ALL
para crear tu lógica
de vista alternativa:
namespace App\View;
use Cake\View\View;
class CustomFallbackView extends View
{
public static function contentType(): string
{
return static::TYPE_MATCH_ALL;
}
}
Es importante recordar que las vistas coincidentes se aplican sólo después de
intentar la negociación del tipo de contenido.
Redirigiendo a otras páginas
-
Cake\Controller\Controller::redirect(string|array $url, integer $status)
El método redirect()
agrega un encabezado Location
y establece un código
de estado de una respuesta y la devuelve. Deberías devolver la respuesta creada
por redirect()
para que CakePHP envíe la redirección en vez de completar
la acción del controlador y renderizar la vista.
Puedes redigir usando los valores de un array ordenado:
return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
$order->id,
'?' => [
'product' => 'pizza',
'quantity' => 5
],
'#' => 'top'
]);
O usando una URL relativa o absoluta:
return $this->redirect('/orders/confirm');
return $this->redirect('http://www.example.com');
O la referencia de la página:
return $this->redirect($this->referer());
Usando el segundo parámetro puede definir un código de estatus para tu redirección:
// Haz un 301 (movido permanentemente)
return $this->redirect('/order/confirm', 301);
// Haz un 303 (Ver otro)
return $this->redirect('/order/confirm', 303);
Reenviando a un acción en el mismo controlador
-
Cake\Controller\Controller::setAction($action, $args...)
Si necesitas reenviar la acción actual a una acción diferente en el mismo controlador,
puedes usar Controller::setAction()
para actualizar el objeto de la solicitud,
modifica la plantilla de vista que será renderizada y reenvía la ejecución a la
nombrada acción:
// Desde una acción de eliminación, puedes renderizar a lista de página
// actualizada.
$this->setAction('index');
Cargando modelos adicionales
-
Cake\Controller\Controller::fetchModel(string $alias, array $config = [])
La función fetchModel()
es útil cuando se necesita cargar un modelo o tabla del ORM que
no es la predeterminada por el controlador. Modelos obtenidos de ésta manera no serán seteados
como propiedades en el controlador:
// Obtiene un modelo de ElasticSearch
$articles = $this->fetchModel('Articles', 'Elastic');
// Obtiene un modelo de webservice
$github = $this->fetchModel('GitHub', 'Webservice');
-
Cake\Controller\Controller::fetchTable(string $alias, array $config = [])
La función fetchTable()
es útil cuando se necesita usar una tabla del ORM que no es
la predeterminada por el controlador:
// En un método del controlador.
$recentArticles = $this->fetchTable('Articles')->find('all',
limit: 5,
order: 'Articles.created DESC'
)
->all();
Added in version 4.3.0: Controller::fetchTable()
fue añadido. Antes de 4.3 necesitas usar Controller::loadModel()
.
Paginación de un modelo
-
Cake\Controller\Controller::paginate()
Este método se utiliza para paginar los resultados obtenidos por tus modelos.
Puedes especificar tamaño de páginas, condiciones de búsqueda del modelo y más.
Ve a la sección pagination para más detalles
sobre como usar paginate()
.
El atributo $paginate
te da una manera de personalizar cómo paginate()
se comporta:
class ArticlesController extends AppController
{
protected array $paginate = [
'Articles' => [
'conditions' => ['published' => 1],
],
];
}
Configuración de componentes para cargar
-
Cake\Controller\Controller::loadComponent($name, $config = [])
En el método initialize()
de tu controlador, puedes definir cualquier componente
que deseas cargar, y cualquier dato de configuración para ellos:
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Flash');
$this->loadComponent('Comments', Configure::read('Comments'));
}
Callbacks del ciclo de vida de la petición
Los controladores de CakePHP activan varios eventos/callbacks que puedes usar
para insertar lógica alrededor del ciclo de vida de la solicitud.
Métodos de callback del controlador
Por defecto, los siguientes métodos de callback están conectados a
eventos relacionados si los métodos son implementados por tus controladores.
-
Cake\Controller\Controller::beforeFilter(EventInterface $event)
Llamado durante el evento Controller.initialize
que ocurre antes de cada
acción en el controlador. Es un lugar útil para comprobar si hay una sesión
activa o inspeccionar los permisos del usuario.
Nota
El método beforeFilter() será llamado por acciones faltantes.
Devolver una respuesta del método beforeFilter
no evitará que otros oyentes
del mismo evento sean llamados. Debes explícitamente parar el evento.
-
Cake\Controller\Controller::beforeRender(EventInterface $event)
Llamado durante el evento Controller.beforeRender
que ocurre después
de la lógica de acción del controlador, pero antes de que la vista sea renderizada.
Este callback no se usa con frecuencia, pero puede ser necesaria
si estas llamando Cake\Controller\Controller::render()
de forma
manual antes del final de una acción dada.
-
Cake\Controller\Controller::afterFilter(EventInterface $event)
Llamado durante el evento Controller.shutdown
que se desencadena después
de cada acción del controlador, y después de que se complete el renderizado.
Este es el último método del controlador para ejecutar.
Además de las devoluciones de llamada del ciclo de vida del controlador,
Componentes también proporciona un conjunto similar de devoluciones
de llamada.
Recuerda llamar a los callbacks de AppController
dentro de los callbacks
del controlador hijo para mejores resultados:
//use Cake\Event\EventInterface;
public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);
}
Middleware del controlador
-
Cake\Controller\Controller::middleware($middleware, array $options = [])
Middleware puede ser definido globalmente, en
un ámbito de enrutamiento o dentro de un controlador. Para definir el middleware
para un controlador en específico usa el método middleware()
de tu método
initialize()
del controlador:
public function initialize(): void
{
parent::initialize();
$this->middleware(function ($request, $handler) {
// Haz la lógica del middleware.
// Verifica que devuelves una respuesta o llamas a handle()
return $handler->handle($request);
});
}
El middleware definido por un controlador será llamado antes beforeFilter()
y se llamarán a los métodos de acción.