Cake PHP
Cake PHP
CakePHP 3
7.1. Introducción
CakePHP es un marco de trabajo construido en PHP, con acceso a varios motores de bases de datos relacionales,
que implementa el patrón MVC y permite desarrollar de forma rápida y profesional proyectos web de cualquier
nivel de complejidad. Además, es libre, por tanto puede acceder a su código, aprender de él, modificarlo y difundir
esos cambios.
Es importante dejar claro que CakePHP no hace automáticamente la aplicación que queramos desarrollar por
nosotros. Más bien pone a su disposición una estructura básica sobre la que construir aplicaciones.
Entre sus características podemos destacar que utiliza convención sobre configuración, es decir, se determinan una
serie de normas a seguir por el desarrollador en lugar de cargar con múltiples parámetros la configuración de las
aplicaciones; dispone de multitud de plugins, REST, internacionalización, gestión de rutas, listas de control de
acceso, gestión de la seguridad, ayudantes para crear código HTML y formularios, uso de javascript, gestión de
sesiones y una larga lista más de características.
En la página web de CakePHP, http://www.cakephp.org, tiene un manual extenso, detallado y fácil de seguir en
multitud de idiomas.
Va a conocer un poco más de este framework instalando su última versión estable y creando un ejemplo simple.
No va a utilizar Bootstrap ni jQuery en este capítulo por razones de brevedad y para centrar el foco en CakePHP.
Tenga en cuenta que esta última versión, como requisito a destacar, debe tener instalada como mínimo la versión
5.6.0 de PHP o cualquier versión superior, como es nuestro caso con la versión 7.0.
7.2. Instalación
La forma mas sencilla de instalar CakePHP es utilizando Composer, una utilidad que se ejecuta en linea de
comandos y permite resolver las dependencias de librerias de una aplicación en PHP, descargarla y mantener
actualizadas esas librerías. Vamos a instalar Composer en primer lugar.
Si no tiene instalado el comando curl o no funciona por cualquier motivo, como alternativa ejecute las
siguientes sentencias en línea de comandos:
1
Curso PHP Medio Escuela de Administración Pública
Para comprobar que Composer está instalado correctamente ejecute la utilidad preguntando por la versión actual:
$ composer --version
Para terminar los preparativos para instalar CakePHP en nuestro sistema vamos a incluir dos extensiones de PHP
que este framework necesita para funcionar. Estas extensiones son intl y mbstring.
En el caso de linux es necesario instalar los dos paquetes desde el terminal de la siquiente manera:
Sin embargo, en macOS solo es necesario instalar la extensión intl. Para ello usamos la utilidad Homebrew:
C:\<ruta>\>composer –version
Compruebe también en php que las extensiones intl y mbstring estén ya incluidas y activas. Para ello es
necesario modificar el archivo de configuración de PHP (php.ini). Acceda a ese archivo y localize la linea siguiente:
;extension=php_intl.dll
Nota: El último parámetro indica el nombre de la carpeta que se creará para albergar el
proyecto de CakePHP.
Aparecen por pantalla varios mensajes informativos durante la instalación y se pausa para preguntar si desea
establecer los permisos de algunas carpetas. Pulse Enter para que realice esta acción ya que, en caso contrario,
podríamos tener problemas de permisos y, por tanto, CakePHP no funcionaría correctamente.
2
Curso PHP Medio Escuela de Administración Pública
Para conocer algunos de los directorios que contiene el framework, acceda a la carpeta catalogo. En este lugar
puede ver el directorio vendor, que contiene todas las librerías utilizadas, incluso el núcleo de CakePHP. También
puede ver la carpeta del proyecto el directorio src, que contiene el código del proyecto que quiere construir. Esta
carpeta contiene otras subcarpetas cuyos nombres le pueden resultar familiares. Como ya hemos indicado CakePHP
sigue el esquema MVC por lo que consta de controladores, modelos y vistas. Para contener las clases que
representan a estos tres elementos existen los directorios Controller, Model y Template (View).
En la carpeta Controller no existe aún ningún controlador propio de la aplicación de ejemplo, aunque sí existe el
controlador principal AppController en el fichero AppController.php, que es del que heredarán los nuevos
controladores, así como el controlador para los errores (ErrorController.php) y el de la página de inicio
(PageController.php). A medida que vaya creando controladores se irán almacenando aquí.
Los modelos contienen la lógica de negocio, para ello en el directorio Model se incluyen tres carpetas: Behaviour,
Entity y Table. Nos centraremos en Entity y Table para ver cómo accedemos a la información almacenada en base
de datos.
Finalmente, las vistas contenidas en el directorio Templates, constan de una carpeta por cada módulo que cree,
con un archivo por cada vista propia del controlador. Cada vista se asocia automáticamente con una acción de ese
controlador.
Otro directorio a tener en cuenta es config. Aquí se encuentran diversos ficheros relacionados con la configuración
de la aplicación que permiten establecer parámetros como conexiones a bases de datos, servicios de correo o activar
la depuración para obtener información cuando se produzcan errores o advertencias.
Hay dos consideraciones muy importantes que hacer antes de terminar la instalación. La primera se refiere a
cómo utiliza CakePHP las rutas indicadas en las URL de acceso. Como hemos visto anteriormente, el enrutado de
las URL se hace mediante reescritura con el módulo rewrite de Apache. CakePHP aprovecha esta utilidad
mediante un componente propio. Así, si queremos llamar a un método de una clase controladora pasando
parámetros se utiliza el esquema http://servidor/controlador/accion/parametro1/parametro2/.... Esta característica
debe ser implementada por el servidor web en el que despliegue CakePHP. Como también se indicó anteriormente,
en el ejemplo usará Apache que ya incluye esta característica a través de un módulo llamado mod_rewrite que
está activo para los tres sistemas. Puede que sea solo necesario activarlo en el caso de macOS.
La instalación está completa. Veamos cómo se ve nuestro proyecto, aún vacío, desde el navegador. Acceda a
http://localhost/catalogo. Debe tener un aspecto similar al de la figura 7.1.
3
Curso PHP Medio Escuela de Administración Pública
Ésta es la página por defecto que CakePHP crea para mostrar información al acceder a la aplicación. Su utilidad
radica en que hace una serie de indicaciones importantes para hacer funcionar el proyecto CakePHP.
En esta página se alerta sobre configuraciones correctas, en color verde y algún error en color rojo. Una vez que
consiga tener casi todos los mensajes en verde, al menos el de error, puede continuar con el desarrollo del proyecto.
La información se agrupa en cuatro secciones:
• Environment, muestra el resultado de comprobar las versiones tanto de PHP como de las extensiones que
son necesarias para hacer funcionar CakePHP. En nuestro caso disponemos de las versiones correctas.
• Filesystem, en ella se hace referencia a ciertos directorios del arbol de ficheros del proyecto que tienen que
tener permisos de escritura (tmp y logs), así como la modalidad de caché que se ha configurado.
• DebugKit, simplemente indica si se ha cargado este plugin, que proporciona una barra de herramientas
para facilitar la depuración del proyecto.
• Database, la única por ahora en rojo, con mensaje de error, indica si CakePHP es capaz de conectarse a la
base de datos configurada por defecto. En este caso, como es obvio, como no hemos indicado aun los
parámetros de acceso a la base de datos, aparece un mensaje de error.
Para subsanar el error vamos a modificar el archivo de configuración de la aplicación situado en el directorio
config con el nombre app.php. Ese fichero viene con una configuración de conexión a base de datos de ejemplo,
pero necesita una conexión real a una base de datos.
Para este ejemplo cree un esquema de base de datos en MySQL y conéctelo al proyecto. Abra MySQL
WorkBench y cree un nuevo esquema de base de datos con el nombre catalogo. Acceda al fichero config/app.php.
Como puede observar, este fichero contiene muchos más parámetros de configuración. Para acceder a los
parámetros concretos de las conexiones a bases de datos busque un apartado con el título Datasources. Su
estructura es la de un array, por lo que puede indicar varias conexiones a bases de datos. De hecho, existe a modo de
ejemplo una conexión con la clave default y otra con la clave test. Si no se indica lo contrario, CakePHP utiliza
la conexión default para su conexión y la de test para realizar las pruebas unitarias. Acceda a la configuración
de la conexión default y modifique sus valores para que sean los del listado (se han eliminado los comentarios a
efectos de claridad).
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'root',
'password' => 'root',
'database' => 'catalogo',
'encoding' => 'utf8',
'timezone' => 'UTC',
'flags' => [],
'cacheMetadata' => true,
'log' => false,
'quoteIdentifiers' => false,
'url' => env('DATABASE_URL', null),
],
…
Si actualiza la página verá un mensaje en verde informando que la aplicación puede acceder a una base de datos.
Ya está todo listo para empezar con el código del ejemplo.
7.3. Convenciones
Utilice MySQL WorkBench para crear las tablas que necesita. Construya una tabla con el nombre productos y
los campos id, nombre, precio y tipo_id y otra tabla tipos con los campos id y nombre. No se preocupe,
en breve trataremos el por qué de esos nombres. El aspecto de las tablas tiene que ser similar al de las figuras 7.2 y
7.3.
4
Curso PHP Medio Escuela de Administración Pública
Al comienzo del capítulo, señalamos que CakePHP utiliza convención sobre configuración. Al crear estas tablas
ya está siguiendo algunas convenciones.
La primera se refiere al nombre de controladores, modelos y tablas. Por defecto, CakePHP se basa en el nombre
de un controlador para obtener el nombre del modelo y el nombre de la tabla de la base de datos, así como el
nombre de la carpeta de las vistas, cumpliendo así la ruta completa para construir el patrón MVC de forma
automática.
Para ello establece que:
• el nombre de los controladores y la carpeta de vistas tiene que ser plural,
• el de los modelos singular en las entidades y plural en las tablas;
• y finalmente, el de las tablas de base de datos en plural.
Es decir, esto se resumen en que todo va en plural excepto el nombre de las entidades de los modelos.
Por ejemplo, si accede a http://localhost/catalogo/productos/index, la aplicación debería enrutar la petición para
que acceda al controlador ProductosController, a un método con el nombre index(), desde el que accederá
a la clase ProductosTable, y éste a su vez a la tabla productos. Finalmente, el resultado se muestra en la vista
index.ctp del directorio Template/Productos/.
Advertencia: Al ser un proceso automático la resolución de plurales y singulares basado
en el nombre del controlador, Cakephp ha de basarse en algún idioma. Por defecto,
CakePHP resuelve los plurales y singulares en inglés, no en castellano, por lo que debe
tener especial cuidado al nombrar controladores y tablas con plurales irregulares. Por
ejemplo, si utiliza un controlador Poblaciones, CakePHP buscará un modelo
Poblaciones y una tabla poblaciones. En adelante, utilizaremos la configuración por
defecto de CakePHP utilizando plurales regulares que no creen confusión. Aun así, tenga
cuidado porque los plurales latinos como Categorias son resueltos por CakePHP con su
singular en latín, Categorium. Si va a utilizar la configuración por defecto es
recomendable que utilice nombres en inglés ya que es muy probable que necesite tener
algún modelo con plural irregular o configurar CakePHP para que controle los plurales
regulares en castellano. CakePHP permite configurar la resolución de plurales y
singulares en otros idiomas.
5
Curso PHP Medio Escuela de Administración Pública
Todas las tablas en el esquema de base de datos, por defecto, se han de nombrar en plural, es decir, para que el
modelo de CakePHP pueda acceder a una tabla concreta de un controlador debe cumplir esta norma. Para nuestro
ejemplo utilizamos las tablas productos y tipos, por lo que sus controladores se llamarán
ProductosController y TiposController respectivamente. Además, sus modelos se componen de las
parejas de clases Producto, ProductosTable y Tipo, TiposTable. Las carpetas de vistas, View/Productos y
View/Tipos. Como puede observar, al ser plurales regulares no generan confusión.
Nota: Una diferencia a destacar con versiones anteriores de CakePHP es la forma en la
que se utilizan los modelos. Anteriormente solo existía un único fichero con la
responsabilidad de lidiar con la lógica de negocio almacenada en una base de datos. En la
versión actual, esa responsabilidad se distribuye en dos clases Entity y Table. Los
objetos de tabla (Table) o repositorios proporcionan acceso a colecciones de datos.
Permiten guardar nuevos registros, modificar o eliminar los existentes, definir relaciones
entre modelos y realizar operaciones masivas sobre esos datos. Sin embargo, las
entidades (Entity) representan registros individuales de datos y permiten definir el
comportamiento y la funcionalidad a ese nivel. Todos los modelos constan de una clase
que hereda de Table y otra de Entity. En nuestro ejemplo, como ya hemos indicado
más arriba son Tipo y TiposTable para tipos; y Producto y ProductosTable
para productos. Puede ver también, que están dispuestos en sus carpetas
correspondientes en el directorio Model.
Otra de las convenciones más importantes es la que hace referencia a los índices primarios de las tablas, que han
de llamarse con el nombre id. Cuando CakePHP accede a una tabla a través de su modelo busca este campo como
clave primaria y la utiliza para establecer relaciones con otras tablas.
CakePHP permite establecer relaciones 'pertenece a', 'uno a uno', 'uno a muchos' y 'muchos a muchos' entre los
modelos. En el ejemplo hemos utilizado una relación 'uno a muchos' entre productos y tipos: un tipo puede tener
muchos productos; y una relación 'pertenece a': un producto pertenece a un tipo. Por convención, para crear esta
relación y que sea reconocible por CakePHP se ha de incluir la clave primaria de tipos en la tabla productos como
una clave ajena. Para no depender del motor de base de datos en la declaración de claves ajenas, CakePHP establece
que una clave es ajena cuando su nombre se compone del nombre en singular de otra tabla seguida del sufijo '_id'.
Así, en nuestro ejemplo, la clave ajena de categoría en productos se llama tipo_id.
Existen muchas más convenciones en cuanto a modelos y relaciones, pero sobrepasan el objetivo de este curso.
Para más información acceda a la página oficial de CakePHP (https://cakephp.org/).
Es en ese archivo donde puede incluir todo el contenido que desee que aparezca cuando cualquier usuario acceda
inicialmente a su aplicación, es la página de inicio o home.
Tanto el título de la página en la cabecera como el título de la ventana tienen el texto que CakePHP configura por
defecto. Para personalizar estos contenidos tiene que acceder a un archivo concreto que contiene todo el código
HTML y PHP que se ejecuta cada vez que se muestra una vista. Es el contenedor encargado de cargar las etiquetas
principales de HTML así como las hojas de estilo y los archivos de javascript. En ese contenedor o layout se carga
cada vista cada vez que se hace una llamada a la acción de un controlador. Existe un layout por defecto que es el que
siempre es utilizado como contenedor si no se especifica otro. Acceda a él para modificar algunos elementos
importantes, como los títulos e incluir un pequeño menú para acceder a la gestión de tipos y productos. Al
mostrarse con cada vista, si incluye un menú en este contenedor, sera accesible desde todas las vistas.
Ese archivo se encuentra en catalogo/src/Template/Layout/default.ctp. Si observa su contenido podrá detectar
variables para establecer un título para la aplicación (<?=$this->fetch('title')?>), métodos para cargar la hoja de
6
Curso PHP Medio Escuela de Administración Pública
estilos por defecto de CakePHP ( <?=$this->Html->css('cake.css')?>) y para cargar scripts de javascript generados
desde CakePHP. En el siguiente listado se encuentra el contenido de ese layout para el ejemplo:
<?php $appDescription = 'Catálogo';?>
<!DOCTYPE html>
<html>
<head>
<?=$this->Html->charset()?>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<?=$appDescription?>:
<?=$this->fetch('title')?>
</title>
<?=$this->Html->meta('icon')?>
<?=$this->Html->css('base.css')?>
<?=$this->Html->css('cake.css')?>
<?=$this->fetch('meta')?>
<?=$this->fetch('css')?>
<?=$this->fetch('script')?>
</head>
<body>
<nav class="top-bar expanded" data-topbar role="navigation">
<ul class="title-area large-3 medium-4 columns">
<li class="name">
<h1><a href=""><?=$this->Html->link($appDescription, '/')?></a></h1>
</li>
</ul>
<div class="top-bar-section">
<ul class="left">
<li><?=$this->Html->link('Tipos', ['controller'=>'Tipos', 'action'=>'index'])?></li>
<li><?=$this->Html->link('Productos', ['controller'=>'Productos', 'action'=>'index'])?></li>
</ul>
</div>
</nav>
<?=$this->Flash->render()?>
<div class="container clearfix">
<?=$this->fetch('content')?>
</div>
<footer></footer>
</body>
</html>
Cada vez que acceda a una vista, si no indica otro layout, CakePHP cargará su contenido utilizando la función
fetch(), en la etiqueta <div> con el identificador content. Si además quiere enviar mensajes al usuario que
informen sobre el estado de cada acción, el método render() del ayudante HTML Flash mostrará todos los
mensajes que establezca en el controlador al que llame.
Puede ver el resultado del listado accediendo a su proyecto en http://localhost/catalogo. Tiene que ser similar al
que se muestra en la figura 7.4.
7
Curso PHP Medio Escuela de Administración Pública
Al hacer clic en el título de la aplicación le llevará siempre a la página de inicio. Si pincha en los enlaces del menú
se mostrarán varios errores debido a que aun no ha generado los módulos a los que intenta acceder.
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
Con estas pocas lineas de código ha creado la entidad para la tabla tipos. La propiedad $_accesible permite
indicar un mapa de los campos de la entidad, que se corresponden con los campos de la tabla de la base de datos,
que son accesibles para ser modificados de forma masiva. En este caso, se permite acceder a todos los campos de la
entidad Tipo excepto al campo identificador id. Hay que tener en cuenta que, aunque esta forma de asignación es
simple y facilita esa tarea, puede tener consecuencias importantes a nivel de seguridad. Por simplificar nuestro
ejemplo lo mantendremos así, pero tenga en cuenta que si añade campos a la tabla también estarán disponibles de
forma automática.
Veremos ahora el otro componente del modelo: TiposTable, localizado en
catalogo/src/Model/Table/TiposTable.php. Cree ese archivo e incluya el siguiente código:
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
$this->setTable('tipos');
$this->setDisplayField('nombre');
$this->setPrimaryKey('id');
$this->hasMany('Productos', [
'foreignKey' => 'tipo_id'
]);
}
8
Curso PHP Medio Escuela de Administración Pública
$validator
->requirePresence('nombre', 'create')
->notEmpty('nombre');
return $validator;
}
}
En primer lugar, se define el método que inicializa la clase, en la que se establece el nombre de la tabla de la base
de datos con la que está relacionada. A continuación define la variable $displayField para indicar qué campo
del modelo se utilizará como campo por defecto a mostrar cuando se utilice en los modelos relacionados, en nuestro
caso, el nombre del tipo. Y, finalmente, indica cuál es el campo que actúa como clave primaria.
En ese mismo método se define la relación con la tabla productos, estableciendo que ese modelo es gestionado
por la clase Productos (realmente ProductosTable) y que la clave ajena utilizada para crear la relación es el
campo tipo_id. Aún no ha generado el modelo de productos por lo que puede suponer que generará algún error.
En este caso no será así porque al usar sólo el módulo de tipos no va a acceder todavía a datos relacionados.
Una vez descritas la entidad y la tabla, debe crear el controlador, que será la clase responsable de recibir las
peticiones relacionadas con la información de tipos. Las acciones a definir tanto para este controlador como para el
de productos serán:
• index(). Es la acción que CakePHP busca por defecto cuando no se le indica ninguna. En ella obtiene un
listado de registros que serán mostrados en su vista en forma de tabla de datos paginada, con la opción de
ordenar por los campos mostrados.
• view(). Con esta acción simplemente se muestran la información del registro, sin formulario para realizar
cambios.
• add(). Esta acción realmente se compone de dos, una para mostrar el formulario para obtener los datos de
un nuevo registro y otra para añadir esos datos como un nuevo registro a la tabla de datos. El hacer una u
otra opción depende de si se le envian datos a la acción.
• edit(). Es la que lanza cuando quiera actualizar la información de uno de los registros. También se
compone de dos acciones: una para mostrar el formulario con los datos del registro a modificar y otra para
actualizar los datos modificados en la tabla de datos.
• delete(). Finalmente, la acción que completa el CRUD, es decir, la responsable de eliminar el registro
que se le indique.
9
Curso PHP Medio Escuela de Administración Pública
Acceda desde el navegador a http://localhost/catalogo y haga clic en el menú en la opción Tipos o acceda a
http://localhost/catalogo/tipos. Verá un listado sin ningún registro, con los nombres de los campos y con un menú
para acciones donde aparece un botón para crear un nuevo tipo (vea la figura 7.5).
10
Curso PHP Medio Escuela de Administración Pública
Con este método se realizan dos acciones dependiendo si se le envían datos o no por formulario. Si no se le
envían datos, esta acción se limita a mostrar su vista, que es el formulario con los campos para crear un nuevo tipo.
Si se le envían datos mediante el método POST, el controlador llama al modelo para crear una nueva entidad y
guardarla con los datos pasados. Para ello, mediante el método patchEntity() primero se validan los datos
pasados y posteriormente se asignan a las propiedades de la entidad. A continuación, el objeto resultante se
almacena en la tabla de base de datos con el método save(). Si esa acción tiene éxito lo comunica al usuario con
un mensaje mediante el método success() del componente Flash y redirige la petición al listado de tipos. Si,
por el contrario, no se ha podido almacenar el registro, simplemente lo comunica al usuario con un mensaje de
error, pero éste permanece en el formulario para añadir tipos.
Complete esta funcionalidad con el contenido del listado a continuación, creando la vista con el formulario para
añadir un nuevo registro en catalogo/src/Template/Tipos/add.ctp.
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading">Acciones</li>
<li><?=$this->Html->link('Tipos', ['action'=>'index'])?></li>
<li><?=$this->Html->link('Productos', ['controller'=>'Productos', 'action'=>'index'])?></li>
<li><?=$this->Html->link('Nuevo Producto', ['controller'=>'Productos', 'action'=>'add'])?></li>
</ul>
</nav>
<div class="tipos form large-9 medium-8 columns content">
<?=$this->Form->create($tipo)?>
<fieldset>
<legend>Nuevo Tipo</legend>
<?=$this->Form->control('nombre')?>
</fieldset>
<?=$this->Form->button('Crear')?>
<?=$this->Form->end()?>
</div>
Cree varios registros de tipos y observe cómo va aumentando el listado de tipos (figura 7.7). Por cada registro, en la
vista del listado, se muestran tres nuevos botones para ver el tipo, editarlo o eliminarlo. En esos botones se utiliza el
ayudante Html y Form de CakePHP para crear enlaces como elementos de acción. En el enlace para eliminar, se
incluye además la opción de que cuando se haga clic en él se muestre un diálogo con un mensaje para confirmar la
eliminación.
11
Curso PHP Medio Escuela de Administración Pública
El mensaje de información que creó con el ayudante Flash en el controlador se muestra en una banda en color
azul debajo de la barra de menú.
Con ese simple cambio ya tenemos disponibles la paginación desde el controlador, con páginas de diez registros.
Por defecto, CakePHP establece el límite en 100, por lo que es necesario redefinir esa variable a nivel de controlador
para acotar las páginas. Para paginar establece la variable $tipos con el método paginate(), es decir, realiza
también la consulta de los datos al igual que hicimos con el método find() pero paginando los resultados. El
contenido de la vista index.ctp queda de la siguiente forma:
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading">Actions</li>
<li><?=$this->Html->link('Nuevo Tipo', ['action'=>'add'])?></li>
<li><?=$this->Html->link('Productos', ['controller'=>'Productos', 'action'=>'index'])?></li>
<li><?=$this->Html->link('Nuevo Producto', ['controller'=>'Productos', 'action'=>'add'])?></li>
</ul>
</nav>
<div class="tipos index large-9 medium-8 columns content">
<h3>Tipos</h3>
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col"><?=$this->Paginator->sort('id')?></th>
12
Curso PHP Medio Escuela de Administración Pública
<th scope="col"><?=$this->Paginator->sort('nombre')?></th>
<th scope="col" class="actions">Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach($tipos as $tipo):?>
<tr>
<td><?=$this->Number->format($tipo->id)?></td>
<td><?= h($tipo->nombre)?></td>
<td class="actions">
<?=$this->Html->link('Ver', ['action'=>'view', $tipo->id])?>
<?=$this->Html->link('Editar', ['action'=>'edit', $tipo->id])?>
<?=$this->Form->postLink('Eliminar', ['action'=>'delete', $tipo->id],
['confirm'=>'¿Realmente quiere eliminar?'])?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="paginator">
<ul class="pagination">
<?=$this->Paginator->first('<< primero')?>
<?=$this->Paginator->prev('< anterior')?>
<?=$this->Paginator->numbers()?>
<?=$this->Paginator->next('siguiente >')?>
<?=$this->Paginator->last('último >>')?>
</ul>
<p><?=$this->Paginator->counter(['format'=>'Página {{page}} de {{pages}},
mostrando {{current}} registro(s) de {{count}}'])?></p>
</div>
</div>
Al utilizar el componente y el ayudante Paginator no sólo aparecen los registros de datos paginados sino que
también se proporcionan a los usuarios botones para acceder a las diferentes páginas y para navegar a la página
anterior y a la siguiente. Además, si observa el código donde se definen los títulos de las columnas de la tabla de
datos, puede percibir que se han cambiado para utilizar la función sort() del ayudante Paginator. Con esta
funcionalidad, al hacer clic en los títulos de las columnas, aparecerán los registros ordenados por la columna que
seleccione, tanto de forma ascendente como ascendente al hacer clic consecutivamente.
13
Curso PHP Medio Escuela de Administración Pública
Es necesario que incluya una nueva acción en el controlador de tipos. Para ello abra el archivo
catalogo/src/Controller/TiposController.php y añada como nuevo método, a continuación de add(), el
implementado en el listado a continuación.
Este método es similar al de añadir, pero añade más funcionalidad. En primer lugar recibe el identificador del
registro a editar como parámetro de entrada. En segundo lugar, obtiene el registro de datos del tipo indicado con ese
identificador. Si ese identificador no existe o no se le pasa ningún identificador, CakePHP lanza una excepción
indicando que no se ha encontrado ese registro mostrando una página de error. Si desea personalizar ese mensaje
debería controlar la excepción mediante un bloque try-catch. Si existe ese registro, continua y, como sucede al
añadir, evalúa no solo si se le están enviando datos en formato POST, sino también en formato PATCH o PUT. Si
no es así, quiere decir que la aplicación debe mostrar el formulario de edición con los datos del registro obtenido al
principio y pasados con set() a la vista en la variable $tipo.
Finalmente, como sucede al añadir, si llegan datos del formulario en formato POST, PATCH o PUT, se
almacenan con save(), se informa al usuario del éxito y se redirecciona al listado de registros. Si la operación
fracasa se advierte del error y se vuelve a mostrar el formulario de edición.
Para continuar con la edición es necesario crear su vista. Cree el fichero catalogo/src/Template/Tipos/edit.ctp e
incluya el código siguiente:
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading">Acciones</li>
<li><?=$this->Form->postLink('Eliminar', ['action'=>'delete', $tipo->id],
['confirm'=>'¿Realmente quiere eliminar?'])?></li>
<li><?=$this->Html->link('Tipos', ['action'=>'index'])?></li>
<li><?=$this->Html->link('Productos', ['controller'=>'Productos', 'action'=>'index'])?></li>
<li><?=$this->Html->link('Nuevo Producto', ['controller'=>'Productos', 'action'=>'add'])?></li>
</ul>
</nav>
<div class="tipos form large-9 medium-8 columns content">
<?=$this->Form->create($tipo)?>
<fieldset>
<legend>Editar Tipo</legend>
<?=$this->Form->control('nombre')?>
</fieldset>
<?=$this->Form->button('Actualizar')?>
<?=$this->Form->end()?>
</div>
Este formulario es prácticamente igual al de añadir tipos. Se diferencia en el botón de eliminar en el que se
incluye el identificador del registro. Por lo demás es idéntico. La figura 7.9 que contiene la imagen de la edición del
tipo 'Alimentación' es igual que la del formulario al añadir con la diferencia de que el contenido del registro se
incluye en los campos.
14
Curso PHP Medio Escuela de Administración Pública
Modifique sus registros y observará como al regresar al listado de tipos, éstos aparecerán con los cambios que haya
realizado.
Como puede observar, es una versión simplificada del método edit(). Obtiene la información del registro
mediante su id y lo pasa a la variable que usamos en la vista.
La vista es aun más simple. Incluya el siguiente código en el fichero catalogo/src/Template/Tipos/view.ctp:
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading">Acciones</li>
<li><?=$this->Html->link('Editar', ['action'=>'edit', $tipo->id])?></li>
<li><?=$this->Form->postLink('Eliminar', ['action'=>'delete', $tipo->id],
['confirm'=>'¿Realmente desea eliminar?'])?></li>
<li><?=$this->Html->link('Tipos', ['action'=>'index'])?></li>
<li><?=$this->Html->link('Nuevo Tipo', ['action'=>'add'])?></li>
<li><?=$this->Html->link('Productos', ['controller'=>'Productos', 'action'=>'index'])?></li>
<li><?=$this->Html->link('Nuevo Producto', ['controller'=>'Productos', 'action'=>'add'])?></li>
</ul>
</nav>
<div class="tipos view large-9 medium-8 columns content">
<h3><?=h($tipo->nombre)?></h3>
<table class="vertical-table">
<tr>
<th scope="row">Id</th>
<td><?=$this->Number->format($tipo->id)?></td>
</tr>
<tr>
<th scope="row">Nombre</th>
<td><?=h($tipo->nombre)?></td>
</tr>
</table>
</div>
15
Curso PHP Medio Escuela de Administración Pública
Si hace clic en Aceptar, será necesario que el controlador disponga de la acción delete(), de lo contrario la
aplicación generará un error.
Acceda al controlador de tipos en catalogo/src/Controller/TiposController.php y añada el método del siguiente
listado al final de la clase.
Como sucede al editar, al eliminar es necesario indicar qué registro se desea eliminar. Para ello se le pasa el
identificador y se comprueba si existe. Si no existe se provoca una excepción y, en caso contrario, obtiene una
entidad Tipo desde el repositorio Tipos con el método get(). A continuación, solo es necesario invocar el
método delete() del mismo repositorio pasando el objeto que se desea eliminar.
Si acepta eliminar el tipo, puede ver el resultado en la figura 7.11.
16
Curso PHP Medio Escuela de Administración Pública
17
Curso PHP Medio Escuela de Administración Pública
Las diferencias fundamentales con el controlador de tipos se refieren a cómo se relacionan los productos con los
tipos. En el método index() se incluye una opción dentro de la variable de paginación para que, al hacer la
consulta, obtenga el registro relacionado de tipos, mediante el índice contain del array paginate. De esa forma,
al mostrar el campo del tipo relacionado en el listado de productos no se muestra un índice numérico sino el
nombre real del tipo de producto. Sucede lo mismo en el método view().
Al añadir un nuevo producto es necesario mostrar una lista de tipos de producto para que el usuario indique a
qué tipo pertenece. Para ello en el método add() se incluye una variable $tipos que almacena los registros de la
consulta que se hace a Tipos y se pasa a la vista junto al objeto $producto. Se procede de igual forma en el
método edit(). Más adelante veremos las diferencias en las vistas.
Cree el archivo de entidad para el modelo en catalogo/src/Model/Entity/Producto.php con el contenido del
listado siguiente.
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
18
Curso PHP Medio Escuela de Administración Pública
$this->setTable('productos');
$this->setDisplayField('nombre');
$this->setPrimaryKey('id');
$this->belongsTo('Tipos', [
'foreignKey' => 'tipo_id',
'joinType' => 'INNER'
]);
}
return $validator;
}
El contenido es parecido al del modelo de Tipo con la diferencia de que la relación que define es una 'pertenece
a' con la variable $this->belongsTo, indicando la clase del modelo con la que se relaciona y el campo que actúa
como clave ajena.
Veamos los cambios que son necesarios en el fichero catalogo/src/Template/Productos/index.ctp con respecto al
mismo fichero que generamos para tipos.
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading">Acciones</li>
<li><?=$this->Html->link('Nuevo Producto', ['action'=>'add'])?></li>
<li><?=$this->Html->link('Tipos', ['controller'=>'Tipos', 'action'=>'index'])?></li>
<li><?=$this->Html->link('Nuevo Tipo', ['controller'=>'Tipos', 'action'=>'add'])?></li>
</ul>
</nav>
<div class="productos index large-9 medium-8 columns content">
<h3>Productos</h3>
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col"><?=$this->Paginator->sort('id')?></th>
<th scope="col"><?=$this->Paginator->sort('nombre')?></th>
<th scope="col"><?=$this->Paginator->sort('precio')?></th>
<th scope="col"><?=$this->Paginator->sort('tipo_id')?></th>
<th scope="col" class="actions">Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach($productos as $producto):?>
<tr>
<td><?=$this->Number->format($producto->id)?></td>
<td><?=h($producto->nombre )?></td>
<td><?=$this->Number->format($producto->precio)?></td>
<td>
19
Curso PHP Medio Escuela de Administración Pública
<?=$producto->has('tipo') ? $this->Html->link($producto->tipo->nombre,
['controller'=>'Tipos', 'action'=>'view', $producto->tipo->id]):''?>
</td>
<td class="actions">
<?=$this->Html->link('Ver', ['action'=>'view', $producto->id])?>
<?=$this->Html->link('Editar', ['action'=>'edit', $producto->id])?>
<?=$this->Form->postLink('Eliminar', ['action'=>'delete', $producto->id],
['confirm'=>'¿Realmente desea eliminar?'])?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="paginator">
<ul class="pagination">
<?=$this->Paginator->first('<< primero')?>
<?=$this->Paginator->prev('< anterior')?>
<?=$this->Paginator->numbers()?>
<?=$this->Paginator->next('siguiente >')?>
<?=$this->Paginator->last('último >>')?>
</ul>
<p><?= $this->Paginator->counter(['format'=>'Página {{page}} de {{pages}}, mostrando {{current}}
de {{count}}'])?></p>
</div>
</div>
Aunque aún no tenemos ningún registro para poder apreciarlo en el navegador, en esta vista podemos destacar
tres nuevos elementos.
El primero se refiere al nombre de la columna del campo relacionado. Antes de mostrar nada, pregunta si ese
campo tiene valor y a continuación muestra el nombre del tipo como un enlace que lleva a la vista (acción view())
de tipos.
Ese es el segundo elemento a destacar, es decir, la forma de mostrar el campo relacionado en cada fila de la tabla
de datos. Para ello usa un enlace con el ayudante HTML de CakePHP para que al hacer clic en él, accedamos a la
vista que muestra solo su información, sin posibilidad de editarla. Lo notable en este enlace es la forma de titular el
enlace con $producto->tipo->nombre, es decir, desde cada producto proporcionado por el controlador puede
acceder a los campos del modelo tipo relacionado.
El tercer elemento es simplemente la inclusión de dos elementos en el menú de la izquierda para acceder al
listado de tipos y crear un nuevo tipo desde esta vista.
Acceda con el navegador a http://localhost/catalogo/productos o haga clic en el menú Productos. El aspecto del
listado vacío de productos, a expensas de crear las vistas para añadir y editar es el de la figura 7.12.
20
Curso PHP Medio Escuela de Administración Pública
Como puede observar, CakePHP nos descarga de la necesidad de crear por nosotros el listado desplegable de tipos
para relacionar con el nuevo producto. Simplemente es necesario indicar, utilizando el ayudante de formularios, que
desea mostrar el campo relacionado, suministrarle el array $tipos y CakePHP se encargará del resto.
Cree varios productos y observe cómo el campo tipo relacionado aparece en el listado de productos (figura 7.14)
con su nombre y un enlace que le lleva a su formulario de edición.
El método view() de Productos para ver la información de un producto concreto es similar también a la vista
de tipo de producto con la diferencia del campo nombre de tipo a mostrar.
21
Curso PHP Medio Escuela de Administración Pública
Finalmente, debe permitir editar los productos. El cambio a realizar en el controlador es exactamente el mismo
que ha realizado en el método para añadir. Cree el archivo que le permitirá editar los registros en
catalogo/src/Template/Productos/edit.ctp con el siguiente contenido:
22
Curso PHP Medio Escuela de Administración Pública
Realmente, excepto el campo id, el resto es prácticamente idéntico al formulario para añadir productos. Sin
embargo, la peculiaridad de este formulario de edición es que al mostrar el producto, el tipo ya vendrá seleccionado
en la lista desplegable de tipos.
Es posible mostrar más información en la vista de los tipos de producto, como por ejemplo, los productos de un
tipo concreto. Así, al hacer clic en el enlace Ver de la lista de tipos, en la vista view.ctp aparecerá un listado de los
productos de ese tipo concreto. Para ello, incluya el siguiente código a continuación de la tabla en
catalogo/src/Template/Tipos/view.ctp:
<div class="related">
<h4>Productos Relacionados</h4>
<?php if(!empty($tipo->productos)):?>
<table cellpadding="0" cellspacing="0">
<tr>
<th scope="col">Id</th>
<th scope="col">Nombre</th>
<th scope="col">Precio</th>
<th scope="col" class="actions">Acciones</th>
</tr>
<?php foreach($tipo->productos as $productos):?>
<tr>
<td><?=h($productos->id)?></td>
<td><?=h($productos->nombre)?></td>
<td><?=h($productos->precio)?></td>
<td class="actions">
<?=$this->Html->link('Ver', ['controller'=>'Productos', 'action'=>'view', $productos->id])?>
<?=$this->Html->link('Editar',['controller'=>'Productos','action'=>'edit',$productos->id])?>
<?=$this->Form->postLink('Eliminar',
['controller'=>'Productos', 'action'=>'delete', $productos->id],
['confirm'=>'¿Realmente desea eliminar?'])?>
</td>
</tr>
<?php endforeach;?>
</table>
<?php endif;?>
</div>
Observe que es un listado similar al de index.ctp de cualquier módulo con la salvedad de que los productos que
aquí se muestran son los que tienen como categoría relacionada la que se está mostrando.
En el método view() del TiposController es necesario indicar que al obtener el registro que queremos mostrar,
incluya además la información de los productos relacionados usando el índice contain:
public function view($id = null)
{
$tipo = $this->Tipos->get($id, [
'contain' => ['Productos']
]);
$this->set('tipo', $tipo);
}
23
Curso PHP Medio Escuela de Administración Pública
7.7. Conclusión
Hoy en día, y no sólo sucede con PHP, es un riesgo y un esfuerzo considerable crear una aplicación sin utilizar
un framework de desarrollo que agilice el trabajo diario y facilite la mayoría de las utilidades y controles de
seguridad, MVC, pruebas unitarias, así como la integración con elementos de la interfaz de usuario (HTML, CSS,
javascript).
CakePHP es uno de los marcos de trabajo más utilizados, más estables y con una evolución continua muy
notable, mantenida por una comunidad muy viva. Le recomendamos su uso, aunque debe ampliar sus
conocimientos accediendo a la página oficial de CakePHP (http://www.cakephp.org). Si CakePHP no es de su
agrado o prefiere investigar otros frameworks, existen multitud de proyectos en PHP. Nosotros le aconsejamos
algunos ya maduros como Laravel (http://www.laravel.com) o Symfony (http://ww.symfony.com).
24