Ejercicio 8. Configura el componente Auth de CakePHP en un plisplás

Ejercicio 8. Configuración del componente Auth

Vamos a configurar el componente Auth de CakePHP en cinco pasos; es decir, en un plisplás. Este ejercicio supone que no hemos creado el modelo Usuario y su tabla correspondiente, así que lo haremos por el camino. Are you ready? Sí? 1, 2, 3… Go!

1. Copia el siguiente código.

function inicializarAuth(){
   $this->Auth->userModel = 'Usuario';
   $this->Auth->fields = array('username' => 'login', 'password' => 'password');
   $this->Auth->loginAction = array('controller' => 'usuarios', 'action' => 'login');
   $this->Auth->loginRedirect = array('controller' => 'usuarios', 'action' => 'index');
   $this->Auth->logoutRedirect = array('controller' => 'pages', 'action' => 'display');
   $this->Auth->loginError = 'El nombre de usuario y/o la contraseña no son correctos. Por favor, inténtalo otra vez';
   $this->Auth->authError = 'Para entrar en la zona privada tienes que autenticarte';
   $this->Auth->allow('registro');
   $this->Auth->allow(array('display'));
   $this->Session->write('Auth.redirect', null);
} // fin de function inicializarAuth
function beforeFilter(){
   $this->inicializarAuth();
} // fin de function beforeFilter()

2. Copia el archivo /cake/libs/controller/app_controller.php en /app/app_controller.php. Ahora, edita el recién creado /app/app_controller.php y pega el código que has copiado antes en el interior de la clase AppController. Cuando tu clase AppController quede como la del siguiente ejemplo, guárdala sin miedo. Date prisa.


 class AppController extends Controller {
   var $components = array('Auth');
   function inicializarAuth(){
      $this->Auth->userModel = 'Usuario';
      $this->Auth->fields = array('username' => 'login', 'password' => 'password');
      $this->Auth->loginAction = array('controller' => 'usuarios', 'action' => 'login');
      $this->Auth->loginRedirect = array('controller' => 'usuarios', 'action' => 'index');
      $this->Auth->logoutRedirect = array('controller' => 'pages', 'action' => 'display');
      $this->Auth->loginError = 'El nombre de usuario y/o la contraseña no son correctos. Por favor, inténtalo otra vez';
      $this->Auth->authError = 'Para entrar en la zona privada tienes que autenticarte';
      $this->Auth->allow('registro');
      $this->Auth->allow(array('display'));
      $this->Session->write('Auth.redirect', null);
   } // fin de function inicializarAuth
   function beforeFilter(){
      $this->inicializarAuth();
   } // fin de function beforeFilter()
}

3. Ahora tenemos que crear el modelo Usuario. Para ello, ve a /app/models y crea el archivo usuario.php con el siguiente código. ¡Corre!


class Usuario extends AppModel{
   var $name = 'Usuario';
   var $validate = array(  'login' =>     array(
                                                'rule' => array('minLength', 1),
                                                'required' => true,
                                                'allowEmpty' => false,
                                                'message' => 'Por favor, introduce el login'
                                             ),
                           'password' =>  array(
                                                'rule' => array('minLength', 1),
                                                'required' => true,
                                                'allowEmpty' => false,
                                                'message' => 'Por favor, introduce la contraseña'
                                             ),
                           'email' =>     array(
                                                'rule' => 'email',
                                                'required' => true,
                                                'allowEmpty' => false,
                                                'message' => 'Por favor, introduce una dirección de correo válida'
                                             )
                     );

}

4. Una vez tenemos el modelo Usuario, escribimos el SQL que crea usuarios, su tabla correspondiente. ¡Ya casi está!


create table usuarios(

id mediumint unsigned NOT NULL auto_increment,

login varchar(20) NOT NULL,

email varchar(100) NOT NULL,

data datetime NOT NULL,

password varchar(40) NOT NULL,

primary key(id)

);

5. Finalmente, como estamos hechos unos campeones, nos tomamos un kit kat.

Qué hemos hecho?

Working like a machine? Muy buenas de nuevo, hemos vuelto de la publicidad y ahora es el momento de ver qué hemos hecho exactamente.

Paso 1

Aquí sólo hemos copiado un trozo de código; luego veremos qué hace exactamente. De momento, empero, vamos a intentar desentrañarlo un poco.

Los componentes de CakePHP encapsulan lógica que van a compartir varios controladores. En particular, Cake viene de fábrica con el componente Auth, cuya razón de ser es autenticar a los usuarios de una aplicación CakePHP. Como veremos un poquito más adelante en esta explicación, cuando añadimos el componente Auth al programa, entonces, de forma predeterminada, se bloquean todas las acciones a excepción de login y logout.

Paso 2

Todos los controladores de la aplicación Cake extienden la clase AppController; en consecuencia, si queremos añadir algo que compartan todos ellos, debemos hacerlo aquí. Por eso, estimado visitante, primero nos hemos ido a /cake/libs/controller/app_controller.php, luego hemos copiado este archivo en /app/app_controller.php, y, finalmente, hemos pegado el código aquí. ¿Quieres saber por qué es necesaria esta copia de app_controller.php? 😉 Haz clic aquí, pues.

En efecto, querido Watson, en AppController hemos añadido el componente Auth y lo hemos configurado en el método beforeFilter(). Por cierto, recordemos que beforeFilter() es una acción callback que se ejecuta justo antes de llamar a cualquier acción del programa CakePHP. Elemental. Bueno, visitante, en realidad bromeo un poco 😉 y puede que esto no sea tan elemental. Puedes echar un vistazo a las retrollamadas en http://book.cakephp.org/es/view/60/Callbacks.

Sin embargo, la dificultad de este paso número dos se encuentra en la función inicializarAuth porque es aquí donde debemos definir las propiedades del componente y esto implica que las conocemos. Quiero decir, que para escribir esta función tenemos que conocer un poco a Auth para decirle lo que queremos hacer con él. Vamos a hacerlo ahora.

Por defecto, la tabla donde se almacenan los usuarios se llama users, pero no siempre es así. Si quieres utilizar otra configuración puedes utilitzar los pasos 3 y 4 de este ejercicio, ¡pero recuerda decírselo a Auth en el beforeFilter! Por ejemplo, para utilizar el modelo Usuario, escribimos esto:

$this->Auth->userModel = 'Usuario';

Por otra parte, Auth espera que los campos que almacenan el nombre de usuario y la constraseña se llamen username y password y para decirle, “Oye, por favor, Auth, fíjate que, en vez de username y password, mi tabla usuarios tiene los campos login y password. Gracias”, escribimos esta línea:

$this->Auth->fields = array('username' => 'login', 'password' => 'password');

Esta línea le dice a Auth dónde está la acción que autentica a los usuarios:

$this->Auth->loginAction = array('controller'=>'usuarios','action'=>'login');

 Y esta otra, dónde debe redireccionar a los usuarios que salen correctamente del sistema: 

$this->Auth->logoutRedirect = array('controller'=>'pages', 'action'=>'display');

Estas dos tienes que adivinarlas tú: 


$this->Auth->loginError('El nombre de usuario y/o la contraseña no son correctos. Por favor, inténtalo otra vez';
$this->Auth->authError('Para entrar en la zona privada tienes que autenticarte');

¿Recuerdas que decíamos que cuando añadimos el componente Auth en AppController se bloquean todas las acciones a excepción de login y logout? Pues bien, si nos interesa desbloquear otras acciones para hacerlas accesibles a todos los visitantes del sitio web, entonces utilizamos la variable allow. Un caso típico es el de la acción que permite que los usuarios se puedan registrar. Efectivamente, si queremos que todo el mundo pueda acceder, entonces escribimos esto:

$this->Auth->allow('registro');

Si queremos que la vista /app/views/pages/home.ctp también esté disponible, escribimos:


$this->Auth->allow(array('display'));

Auth se comporta de forma que recuerda y redirecciona al URL al que el usuario intentaba acceder antes de entrar correctamente en el sistema. Por ejemplo, si el usuario escribe http://www.miaplicacioncakephp.com, y, a continuación, entra en http://www.miaplicacióncakephp.com/usuarios/login y se autentica, Auth no le envía a la vista login.ctp, como muchos podríamos esperar, sino a index.ctp. Esta dirección se guarda en la variable de sesión Auth.redirect; para deshabilitar este comportamiento, escribimos esto (hay una discusión interesante al respecto en http://book.cakephp.org/comments/index/392):


$this->Session->write('Auth.redirect',null);

Pasos 3 y 4

En estos pasos hemos creado el modelo Usuario y la tabla usuarios porque en este ejemplo no nos interesa seguir las convenciones de CakePHP y utilizar el modelo User y su tabla correspondiente, users. Finalmente, recordamos otra vez que, haciendo esto, es necesario comunícarselo a Auth; en concreto, como hemos visto, esto se hace a través de las variables userModel y fields.

Resumen

En este post hemos visto que los controladores de CakePHP extienden la clase AppController y que si queremos que una lógica determinada esté disponible en todos los controladores de la aplicación entonces debemos añadirla a la clase /app/app_controller.php.

CakePHP viene de fábrica con el componente Auth, y cuando lo añadimos en AppController se bloquean todas las acciones de todos los controladores a excepción de login y logout.

Para configurar Auth debemos conocer los parámetros de que dispone: userModel, fields, loginAction, allow, etc. En cualquier caso, todos ellos se parametrizan enel método beforeFilter de AppController porque esta acción se ejecuta antes de cualquier acción de la aplicación CakePHP.

Nota:

Ha pasado un tiempo desde que escribí en este blog el post https://tutorialcakephp.wordpress.com/2008/11/20/20-de-noviembre-de-2008-de-cakephp-11-a-cakephp-12-el-baile-de-los-errores-parte-ii/. Aquí explicaba mi primer acercamiento al componente Auth. ¡Espero que este otro lo encuentres un poco mejor y te sea más útil!

Ejercicio 7. Declara el tipo de tus documentos con Cake

No DOCTYPE found!

Esta tarde me he dedicado a validar las páginas XHTML de mi aplicación CakePHP con http://validator.w3.org/, el servicio de validación de marcado del W3C (W3C Markup Validation Service) pero… ¡sorpresa!: resulta que, al principio, no podía hacerlo porque había olvidado añadir la declaración del tipo de documento justo al comienzo de éstas. Resultado: el mensaje de error del título de este párrafo.

Confieso, pues (no se lo digas a nadie), que hasta hace muy poco no tenía claro qué era el DOCTYPE. Aunque lo he añadido algunas veces (justo antes de validar el código de turno), siempre lo he dejado para el final y esta vez se me había olvidado. ¿Qué tal si rescatamos al DOCTYPE del olvido y lo ponemos siempre al principio del archivo a partir de ahora? Seguro que es una buena idea. Todo esto si tú hacías como yo, claro. 😀

En concreto, el mensaje que lanza el http://validator.w3.org/es es algo así como este: “No se ha podido encontrar o no se ha reconocido una declaración de tipo de documento en este archivo. Generalmente, esto significa que, al principio, el documento no declara su tipo de documento. También puede ser que haya un error en la declaración o que no esté utilizando la sintaxis correcta”.

A continuación, el programa verifica el documento como si se tratara de un HTML 4.01 Transitional.

1. ¿Qué es el DOCTYPE?

Dejando a un lado algunos aspectos más o menos técnicos y profundos que he encontrado en Internet (que no sé si comprendo del todo), según lo que muchos dicen y sí que entiendo, el DOCTYPE es algo así como una instrucción que tiene que ponerse al comienzo del documento (X)HTML para que el navegador sepa qué va a encontrarse a continuación y pueda interpretarlo en consecuencia. La mayoría de explicaciones que he leído coinciden en que el DOCTYPE no es una etiqueta, sino “una declaración”. A continuación, adjunto esta explicación de www.eslomas.com, que está muy bien:

“El DOCTYPE es una parte fundamental de todas aquellas páginas que quieran cumplir los estándares, tanto HTML como XHTML. Esta declaración indica que versión de (X)HTML se usa en la página, de forma que los navegadores pueden saber qué sintaxis y gramática se usa, y los validadores puedan comprobar su validez.[…] Aparte de esto la declaración DOCTYPE se utiliza por los navegadores para activar su modo estándar o estricto, o su modo compatibilidad (quirk)”.

Esto mismo explica el mismo W3C en este documento. Ciertamente, no debemos olvidar añadir el doctype porque las herramientas que procesan los documentos HTML necesitan conocer el DTD que utilizan los documentos. Esta es una información clave que necesitan los navegadores y las herramientas que procesan el documento; por ejemplo, el servicio de validación de marcado del W3C.

Por último, los navegadores interpretan el documento más rápidamente y de una forma más consistente cuando se define el doctype; es decir, analizan sintácticamente el archivo en modo estándar y no tienen que “hacer conjeturas”. El resultado: nos evitamos sorpresas.

2. ¿Qué declaraciones de tipo de documento podemos utilizar?

Hay una lista del W3C con todas ellas, http://www.w3.org/QA/2002/04/valid-dtd-list.html.

3. ¿Cómo se escribe una DTD en CakePHP?

Se utiliza $html->docType() para escribir la declaración del tipo de documento. En función del DTD que queramos utilitzar, se usa el parámetro correspondiente: html, html4-strict, html4-trans, xhtml-strict, etc. Todos estos parámetros están en http://book.cakephp.org/view/206/Inserting-Well-Formatted-elements, el manual de CakePHP.

Por ejemplo, como la sintaxis que usan mis documentos es la XHTML 1.0 Strict, he escrito este código en el archivo /layout/default.ctp de mi aplicación CakePHP:


<?php 

echo $html->docType('xhtml-strict'); 

?>

Si ahora ejectuo el programa y visualizo el código fuente de cualquier página, entonces ya aparece la DTD antes de la etiqueta <html>:

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”&gt;

Finalmente, si vuelvo a validar el código de la página, en http://validator.w3.org/, me sale este otro mensaje:

Errors found while checking this document as XHTML 1.0 Strict!

Es decir, el validador, que reconoce la sintaxis de mi archivo, encuentra varios errores en él. No obstante, como diría Michael Ende en La Historia Interminable, esa es otra historia que debe ser contada en otra ocasión.

Ejercicio 6. Mi primer datasource. Consultas al API de YouTube

Hola visitante, ¿qué tal la semana? En este post vamos a hacer lo mismo que hicimos en 5. Listado de YouTube con SimpleXML de esta categoría, YouTube y CakePHP. Esta vez, sin embargo, en vez de implementar la lógica en un controlador, como hicimos aquella vez, abstraeremos el servicio web de YouTube en un datasource. Esta idea nace del artículo Datasources, Models, Components, Behaviors de Felix Geisendörfer, gracias a la respuesta que obtuve en este hilo del Grupo Google CakePHP en español.

¿Qué es un datasource?

Los desarrolladores de CakePHP han creado varios recursos que sirven para extender la funcionalidad de las diferentes partes de una estructura MVC con el objetivo de mantener bien organizado el código de los modelos, las vistas y los controladores.

En efecto, cuánto más crece la complejidad de una aplicación, es más difícil mantener bien organizado el código de los archivos. Los helpers, por lo tanto, ayudan a mantener bien organizado el código de las vistas; los componentes, el de los controladores; finalmente, los datasources y los behaviors nos ayudan con los modelos.

Funcionamiento de los datasources

Como hemos dicho, los datasources extienden la funcionalidad de los modelos para que estos últimos no tengan que preocuparse de montar las consultas SQL y de realizar las operaciones de conexión con la base de datos. En otras palabras, los datasources actúan como una capa situada entre el modelo y la fuente de datos (MySQL, PostgreSQL, Oracle, XML, etc).

Veamos cómo trabajan juntos el modelo y el datasource a través de un ejemplo concreto en que la fuente de datos es una base de datos MySQL. En este caso, el modelo ejectua una de sus  funciones (find, por ejemplo); el datasource la recoge, arma una cadena SQL y se conecta a la base de datos; a continuación, el datasource recoge el conjunto resultado de MySQL, le da formato y devuelve el resultado al modelo.

Creación del datasource YoutubeSource

Del mismo modo que los helpers se crean en la carepta app/views/helpers y los controladores se crean en app/controllers/components, los datasources se crean en la carpeta app/models/datasources.

Nuestro datasource para YouTube es este:


class YoutubeSource extends DataSource{
   var $description = 'Fuente de datos YouTube';
   var $respuestaYoutube;
   function __construct($config=null){
      parent::__construct($config);
      $this->connected = $this->connect();
      return $config;
   }
   function __destruct(){
      $this->connected = $this->close();
      parent::__destruct();
   }
   function connect(){
      $apiURL = 'http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed';
      $ch = curl_init();
      $timeout = 0;
      curl_setopt($ch, CURLOPT_URL, $apiURL);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
      $this->respuestaYoutube = curl_exec($ch);
      curl_close($ch);
      if (!$this->respuestaYoutube){
         return false;
      } else {
         return true;
      }
   }
   function close(){
   }
   function read(){
   }
   function query(){
   }
   function describe(){
   }
   function column(){
   }
   function isConnected(){
   }
   function showLog(){
   }
   function videosMasVistos(){
      App::import('Core','Xml');
      return Set::reverse(new Xml($this->respuestaYoutube));
   }
}

He seguido el capítulo 14 del libro Beginning CakePHP From Novice to Professional de David Golding para hacer este ejercicio. En él, David Golding nos indica que es necesario definir un esqueleto mínimo con las funciones __construct(), __destruct(), connect(), close(), read(), query(), describe(), column(), isConnected() y showLog().

YoutubeSource implementa el método connect() y hace una llamada al API REST de YouTube para obtener el feed Atom con la información de los vídeos más vistos. Por otra parte, como veremos un poco más adelante cuando creemos el modelo YouTube, también implementa el método videosMasVistos(). 

La clase del datasource se llama YoutubeSorce y tiene que guardarse en el archivo youtube_source.php de la carpeta app/models/datasources.

Incluir nuestro datasource en app/config/database.php

Del mismo modo que los ayudantes y los componentes se incluyen en las matrices $helpers y $components del controlador, respectivamente, los datasources se definen en la clase DATABASE_CONFIG de app/config/database.php.

Efectivamente, para incluir el datasource que acabamos de crear en nuestra aplicación CakePHP podemos escribir lo siguiente justo después de la declaración de $default:


var $youtube = array(
  'datasource' => 'youtube'
 );

Creación del modelo Youtube y del método feedsMasVistos()

Ahora tenemos que crear la tabla youtubes en nuestra base de datos y el modelo YouTube en app/models/youtube.php.


class Youtube extends AppModel{
   var $name = 'Youtube';
   function videosMasVistos(){
      $this->setDataSource('youtube');
      $youtube = $this->getDataSource();
      return $youtube->videosMasVistos();
   }
}

Último paso: llamar al método videosMasVistos en el controlador y crear la vista

Finalmente, ya podemos probar nuestro datasource. Para ello tenemos que llamar desde el controlador al método videosMasVistos() del modelo YouTube:


$this->set('feedAtom', $this->Youtube->videosMasVistos());

También hay que crear la vista app/views/youtube/mas_vistos.ctp para mostrar el resultado en la pantalla:

[/sourcecode language='php']

echo ‘<h3>Los videos más vistos son…</h3>’;
foreach ($feedAtom->entry as $entry){
echo $entry->title.'<br/>’;
}

[/sourcecode]

¡Y esto es todo! Espero que este post te haya gustado, lo encuentres útil, y te sirva para dar tus primeros pasos con los datasources.

Ejercicio 5. Mi primer helper CakePHP: el turbo thumbnail, o el turbo miniaturizador de imágenes.

Hoy me he dedicado a buscar en Internet cómo se pueden miniaturizar imágenes al vuelo y he descubierto la librería GD. También he agregado a mis favoritos este tutorial, que explica de una forma muy amena cómo utilizar sus funciones. Claro, al ver todo esto me he emocionado y he pensado que podrían programarse algunos ayudantes HTML con las funciones de la librería GD para obtener los mismos efectos que explica Sargento aquí, pero en CakePHP.

Como no sabía si esto ya está hecho o si se puede hacer, en algún momento me puse a hacer pruebas. El resultado fue un poco desastroso y conseguí armar algo que parecía funcionar, pero que en vez de imprimir imágenes en la pantalla imprimía letras y números.

Seguí buscando en Internet y consulté al oráculo, el fantástico grupo Google CakePHP en español.  Aquí descubrí este artículo, que me ha servido de inspiración para construir mi turbo miniaturizador de imágenes:


class ImagenHelper extends Helper{

   function redimensionar($imagen, $altura){

      $tamano = getimagesize('img/'.$imagen);

      return $this->output('<img src="/img/'.$imagen.'" height="'.$altura.'" width="'.intval($tamano&#91;'0'&#93;*$altura/$tamano&#91;'1'&#93;).'"/>');

   }

De momento, el turbo miniaturizador funciona, y se puede llamar en una vista así:


echo $html->link($imagen->redimensionar('imagen.jpg',200), '/empresas/editar_imagen/'.$empresaId, array(), false, false, null );

También se puede llamar sin poner un enlace, por supuesto, así:


echo $imagen->redimensionar('imagen.jpg',200);

Este helper thumbnail sólo necesita dos cosas: la ruta de la imagen original y la altura en píxels de la miniatura. La función getimagesize de PHP obtiene la altura y la anchura de la imagen original, y el helper guarda la matriz asociativa que genera esta función en $tamano. En $tamano[‘0’] tenemos la anchura en píxels de la imagen original y en $tamano[‘1’] tenemos su altura; la operación


 intval($tamano['0']*$altura/$tamano['1'])

recupera la parte entera de la regla de tres que aplicamos para obtener la anchura correspondiente a la altura.

En otras palabras, el turbo miniaturizador de imágenes -o helper thumbnail- toma la altura en píxels que el usuario quiere y calcula su anchura proporcional. Para una fotografía de 1024 x 800 y una altura de 10 píxels para la miniatura el helper hace 1024 x 10 / 800.

Creo que no es necesario añadir nada más y queda mono en una sola línea. Sí, me voy a arriesgar a probarlo, jeje. ¿Y tú? Me haría ilusión que probaras mi primer helper y lo comentaras, pero recuerda: si se produce un error catastrófico tú eres el único responsable.

Ejercicio 4. Conocer los helpers, o ayudantes, de CakePHP

El enfoque de este ejercicio es práctico y su objetivo no es profundizar en el funcionamiento de CakePHP, como el anterior, sino conocer y aplicar las funciones que el framework incorpora para hacernos la vida más fácil y ahorrar tiempo. En este ejercicio, jordicakephp, principiante, se convierte por un momento en este Tony Ronald de 1974 e invita al lector a hacer lo mismo en la oficina: ¡Help, ayúdame!

Objetivos

1. Tomar conciencia de que CakePHP incorpora muchas funciones que posiblemente desconoce el programador principiante.

Notas e introducción

Ilustraremos esto con el siguiente ejemplo. El programa de este ejemplo tiene las tablas empresas y categorias, con sus respectivos modelos Empresa y Categoria. Como una empresa pertenece a una categoría y una categoría tiene muchas empresas, el programador implementa en Empresa una relación de tipo belongsTo; en el modelo Categoria implementa una de tipo hasMany. El principiante no conoce la existencia de los helpers HTML tableHeaders y tableCells y escribe la siguiente vista, views/categorias/listar.thtml, que muestra un listado de las categorías de las empresas:

<h2>Listado de las categorías de las empresas</h2>
<table>
<tr>
   <th>Id</th>
   <th>Nombre</th>
   <th>Cambios</th>
</tr>
<?php foreach ($categorias as $fila) { ?>
<tr>
   <td><?php echo $fila[‘Categoria’][‘id’];?></td>
   <td><?php echo $fila[‘Categoria’][‘nombre’];?></td>
   <td><?php echo $html->link(‘Editar’,’/categorias/editar/’.$fila[‘Categoria’][‘id’]) ?></td>
   <td><?php echo $html->link(‘Eliminar’,’/categorias/eliminar/’.$fila[‘Categoria’][‘id’]) ?></td>
</tr>
<?php } ?>
</table> 

La vista views/categorias/listar.thtml es correcta y funciona pero utiliza muchas etiquetas HTML.

Resolución

La vista views/categorias/listar.thtml también se puede construir con los ayudantes HTML tableHeaders y tableCells:

<h2>Listado de las categorías de las empresas</h2>
<table>
<?php echo $html->tableHeaders(array(‘Id’,’Nombre’,’Gestionar’)); ?>
<?php
   foreach ($categorias as $fila) {
      echo $html->tableCells( array (  $fila[‘Categoria’][‘id’],
                                       $fila[‘Categoria’][‘nombre’],
                                       $html->link(‘Editar’,’/categorias/editar/’.$fila[‘Categoria’][‘id’]),
                                       $html->link(‘Eliminar’,’/categorias/eliminar/’.$fila[‘Categoria’][‘id’])
                                    ),
                              array (‘bgcolor’=>’#ccffff’)
                           );
   }
?>
</table>

Es importante señalar que la etiqueta <table> tiene que escribirse manualmente en la vista.

Conclusiones

Los ayudantes HTML de este ejercicio, tableHeaders y tableCells, reducen la cantidad de código HTML que se puede llegar a introducir en una vista.

Ejercicio 3. Alta de un usuario en varios pasos en CakePHP 1.1: ¿desmontando el patrón MVC?

Este post no tiene como objetivo criticar el patrón MVC, ni los frameworks que lo implementan, como CakePHP, Symfony, o Zend Framework -entre otros muchos que puedes ver aquí-. Intentamos profundizar un poco en algunos aspectos del MVC y en algunas características de CakePHP.

Este ejercicio es un poco diferente a los anteriores. Me gustaría poder hacerlo, pero no sé, así que queda para otra persona con más experiencia. El planteamiento es muy sencillo: se trata de dar de alta a un usuario en varios pasos de forma que las vistas envíen los datos por POST no a sus acciones asociadas (las que tienen el mismo nombre que las vistas), sino a las siguientes en la secuencia. Todo esto se lleva a cabo validando los datos y utilizando el ayudante HTML tagErrorMsg.

Concretando lo anterior, el usuario se da de alta en tres pantallas: primera_pantalla.thtml, segunda_pantalla.thtml y tercera_pantalla.thtml. Sucede que primera_pantalla.thtml envía su $data a la acción segunda_pantalla, y segunda_pantalla.thtml lo envía a la acción tercera_pantalla -estas acciones se definen en la clase UsuariosController-. Tercera_pantalla.thtml no pide ningún dato al usuario; muestra lo que ha introducido en las pantallas anteriores y presenta un botón “Guardar todos los datos”.

La vista primera_pantalla.thtml empieza así:

<h2>Alta de usuario</h2>
<h3>Paso 1 de 3. Por favor, introduce estos datos.</h3>

<form method=”post” action=”<?php echo $html->url(‘/usuarios/segunda_pantalla’)?>”>

La vista segunda_pantalla.thtml, así:

<h2>Alta de usuario</h2>
<h3>Paso 2 de 3. Por favor, introduce estos datos.</h3>

<form method=”post” action=”<?php echo $html->url(‘/empresas/tercera_pantalla’)?>”>

Y, finalmente, tercera_pantalla.thtml, así:

<h2>Alta de usuario</h2>
<h3>Paso 3 de 3. ¡Estás a punto de darte de alta! ¿Es todo esto correcto?</h3>

<form method=”post” action=”<?php echo $html->url(‘/empresas/????????’)?>”>

No sabemos a quien envía los datos tercera_pantalla.thtml.

Las cuestiones que plantea este ejercicio son:

1. ¿Cómo se validan los datos que el usuario introduce en primera_pantalla.thtml y segunda_pantalla.thtml, mostrando los mensajes de error, con $html->tagErrorMsg?

2. ¿Quién procesa el $data de tercera_pantalla.thtml? ¿Cómo? ¿Por qué?

3. ¿Entra en conflicto este planteamiento con el MVC, o con la implementación que hace Cake de este patrón de arquitectura de software?

Ejercicio 2. Sesiones con CakePHP

Vamos a ver cómo se utiliza el componente de sesiones a través de un sencillo ejemplo en que un usuario del sistema introduce su nombre y su contraseña en un formulario de acceso. Este ejemplo es el de la documentación oficial, que puedes ver aquí.

Objetivos

1. Repasar la forma en que CakePHP ejecuta las acciones de los controladores.

2. Conocer los métodos mágicos de tipo FindBy<campo> de los modelos.

3. Conocer el componente de sesiones de CakePHP.

Notas

Se utiliza la primera instalación de CakePHP y no se cambia el nombre de la carpeta c:\wamp\www\discografia

Resolución

Creamos la base de datos ejercicio02 y la tabla usuarios:

create table usuarios (
id int(11) not null auto_increment,
login varchar(255) not null,
password varchar(32) not null,
nombre varchar(255) not null,
primary key(id)
);

Configuramos el archivo database.php para que CakePHP trabaje con la base de datos que acabamos de crear:

class DATABASE_CONFIG {
var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => '',
'database' => 'ejercicio02',
'prefix' => '',
);
var $test = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => '',
'database' => 'ejercicio02',
'prefix' => '',
);
}

Creamos el modelo para la tabla usuarios:

class Usuario extends AppModel
{
   var $name = 'Usuario';
}

Creamos la vista login.thtml, que se ha traducido de la documentación oficial:

<?php if ($error): ?>

El nombre de usuario o la contraseña no son correctos: inténtalo otra vez.
<?php endif; ?>
<form action="<?php echo $html->url('/usuarios/login'); ?>" method="post">
   <fieldset>
      <label for="login">Usuario:</label>
      <?php echo $html->input('Usuario/login', array('size' => 20)); ?>
      <label for="password">Contraseña:</label>
      <?php echo $html->password('Usuario/password', array('size' => 20)); ?>
      <?php echo $html->submit('Entrar'); ?>
   </fieldset>
</form>

Creamos el archivo usuarios_controller.php y escribimos la lógica de la función login():

class UsuariosController extends AppController{
   function login(){
         $this->set('error', false);
         if (!empty($this->data)){
            $someone = $this->Usuario->findByLogin($this->data['Usuario']['login']);
            if(!empty($someone['Usuario']['password']) && $someone['Usuario']['password'] == $this->data['Usuario']['password']){
                  $this->Session->write('Usuario', $someone['Usuario']);
                  $this->redirect('/empresas/anadir_empresa');
               }
               else{
                  $this->set('error', true);
               }
            }
         }
}

Conclusiones

1. Sobre el método login() de UsuariosController. Los controladores se utilizan para manejar la lógica de un modelo y pueden tener cualquier número de acciones, que ejecuta automáticamente el despachador de CakePHP -generalmente se utilizan para mostrar vistas-. Cuando escribimos, en la barra de direcciones, http://localhost/discografia/usuarios/login, el despachador de Cake pasa la petición al controlador UsuariosController y éste ejecuta la acción.

2. Sobre el método mágico findByLogin() de Usuario. Hasta ahora, para recuperar los datos de las tablas, hemos utilizado el método findAll, que recupera todas las filas. En este ejercicio se utiliza por primera vez el método mágico findByLogin -de tipo findBy<nombreCampo>-, que se utiliza para recuperar los registros de la tabla usuarios cuyo login coincide con el nombre que introduce el usuario en el formulario. Como dice la documentación oficial, “estas funciones mágicas se utilizan como atajo para recuperar un registro, a partir de una tabla, un campo y un valor”. Disponemos de un método findBy para cada campo del modelo.

3. Sobre el componente de sesiones de Cake. “El componente de sesiones se utiliza para interactuar con la información de la sesión, e incluye la lectura y escritura básicas. El componente de sesiones está disponible de forma predterminada en los controladores”, dice la documentación oficial aquí. Vamos a comprobar esto, yendo a esta línea de código del método login() de UsuariosController:

   $this->Session->write(‘Usuario’, $someone[‘Usuario’]);

Si el usuario introduce datos en el campo login y la contraseña de la tabla usuarios coincide con la del formulario, entonces, gracias al método write() de la clase Session, se escribe una variable de sesión.

El componente de sesiones de Cake se encuentra en su núcleo, en la carpeta c:\wamp\www\discografia\cake\libs\controller\components\session.php, y extiende la clase CakeSession, que está en c:\wamp\www\discografia\cake\libs\session.php. El código del método write() de la clase SessionComponent es este:

 

function write($name, $value = null) {
if ($this->__active === true) {
$this->__start();
if (is_array($name)) {
foreach ($name as $key => $value) {
if (parent::write($key, $value) === false) {
return false;
}
}
return true;
}
if (parent::write($name, $value) === false) {
return false;
}
return true;
}
return false;
}

Ya para acabar, se puede contrastar esta forma de trabajar con las sesiones, mediante esta clase de CakePHP, con esta otra, de PHP, que hace uso de la matriz asociativa superglobar $_SESSION[].