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»>

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.

17. Juegos multiusuario. AJAX polling con CakePHP y Couperin

¡Hola, compi! He empezado a dar mis primeros pasos con AJAX hace poco y por momentos me pierdo porque no tengo muy bien asimilados algunos de los conceptos. Proceso de cliente por aquí, tiempo de espera por acá, proceso de servidor por allá… Todo un lío, vaya. Sé que hay que ordenar todas estas cosas de algún modo; de hecho, me ha dado por pensar que una aplicación AJAX es algo así como una orquesta cuyo director somos nosotros, los programadores. ¿Te gusta esta analogía?

Sí, como también coincide que últimamente me ha dado por escuchar a Couperin (a quien recién descubrí, por cierto), inserto a continuación este video que espero que te guste. A ver si su música nos ayuda a estimular el sistema cognitivo y nuestras aplicaciones AJAX dejan de lanzar errores por todas partes, como las mías ahora.

Pero vayamos al grano. Resulta que estoy trabajando en un juego multiusuario que implementa la técnica AJAX polling. Por cierto, estoy casi convencido de que sería mejor utilizar otras técnicas -long polling, sockets o COMET- pero finalmente utilizo esta porque para mí es la más sencilla. Bueno, también me tranquilizó un poco saber que esto puede hacerse así y que está muy bien para acercarse a AJAX long polling y a COMET.

¿Qué es AJAX polling?

AJAX polling es una técnica de programación que consiste en solicitar a intervalos de tiempo regulares la ejecución de la lógica determinada que implementa el servidor. El principal inconvieniente de esta técnica es su escalabilidad porque el número de peticiones puede llegar a incrementarse drásticamente si ese intervalo regular de tiempo es pequeño. Por ejemplo, si hay 1000 usuarios conectados a la aplicación y cada uno de ellos hace una petición por segundo, entonces el servidor recibe 2000 peticiones por segundo.

Además de esto, otra de las desventajas de AJAX polling es que el servidor siempre responde a las peticiones que recibe de los clientes, independientemente de si tiene alguna información nueva que entregarles. En otras palabras, los clientes siempre ejecutan la lógica de turno (actualizan la página cada segundo, por ejemplo) cada n segundos aunque no sea necesario.

Implementación de AJAX polling con CakePHP

Finalmente, mi aplicación multiusuario tiene varias tablas a las que acceden los jugadores de forma dinámica. Además de consultar y actualizar estas tablas, los jugadores calculan sus puntuaciones a partir de ellas y las visualizan en su pantalla en tiempo real. He implementado esta idea utilizando el método remoteTimer:


   echo $ajax->remoteTimer (  array(   'url' => array('controller' => 'multijugadores', 'action' => 'preguntar_sistema'),
                                       'update' => 'aciertos',
                                       'frequency' => 1
                                    )
                           );

Este método, envoltorio de los objetos Prototype PeriodicalExecuter y Ajax.Updater, pide cada segundo la ejecución de la acción preguntar_sistema. Esta acción es la que se encarga de realizar las consultas a las tablas comunes (un conjunto de tablas que todos los usuarios comparten) e imprimir las puntuaciones, calculadas a partir de éstas.

Estoy trabajando en local, la aplicación funciona y de momento no sé qué consecuencias puede tener esta implementación en el rendimiento del servidor compartido al que estoy a punto de subirla. ¿Tienes alguna experiencia con aplicaciones de este tipo? Si es así, cuéntanos… 😉

¿Y la música… De dónde sale?

Bueno, compi, pues Couperin aparece para ayudarme a abordar el problema de una forma no racional. Me refiero al problema de definir la idea de la aplicación multiusuario. Sí, Couperin es la fuente de inspiración que ha derribado las barreras lógica, hipotética y deductiva de la perspectiva izquierda de mi pensamiento y me ha ayudado a crear la idea. Seguro que las hay mejores (¡explícanos alguna, si las conoces!), pero estoy contento porque esta funciona. Espero que te animes a probarla y, si quieres, la comentamos aquí.

3 de marzo de 2009. ¡Hola mundo!

Hola visitante, ¿cómo va todo? Ahora mismo estoy ocupado en otros frentes y durante este tiempo no he escrito más experiencias Cake :-(, aunque he dedicado algún tiempecito, eso sí, a traducir algunos espacios de http://book.cakephp.org/ al español. Bueno, visitante, ¡espero volver a verte pronto por aquí! 🙂

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.