jueves, 10 de junio de 2010

Simulando objetos con Mock Objects

Entre otras facilidades, la Test Suite de CakePHP utiliza la capacidad de SimpleTest de crear Mock Objects, o lo que es lo mismo, simulaciones de objetos que no hacen nada, pero que reproducen todos los métodos de los objetos simulados y que puedes programar para que tengan un comportamiento determinado que te interese.

Esto es útil cuando quieres probar una clase que usa otras clases. En lugar de emplear las clases reales, utilizamos sus simulaciones. De este modo, nos evitamos las posibles interferencias de su funcionamiento sobre los resultados del test y garantizamos que sólo probamos el código de la clase en cuestión.

Un ejemplo sencillo típico sería tratar de probar un Controller que usa un Component.

Lo que tenemos que hacer son básicamente dos cosas:

  • Crear una simulación o Mock del Component
  • Asociar el Component simulado al Controller

Para crear una simulación de una clase usamos Mock::generate('Clase'). Esto nos crea una clase que podemos instanciar para asociar a la clase testada.

Imagina que quieres probar PostsController, habiendo declarado Auth como Component. El código necesario sería algo así como:


App::import('Controller', 'Posts');
App::import('Component', 'Auth');

class PostsTestCase extends CakeTestCase {
   
    var $Posts;
   
    function startTest() {
        $this->Posts = ClassRegistry::init('PostsController');
        Mock::generate('AuthComponent');
        $this->Posts->Auth = new MockAuthComponent();
    }
}

?>


El código de startTest hace lo siguiente:

En primer lugar, instancia PostsController y lo pone en la variable Posts de PostsTestCase, así lo tenemos disponible en cualquiera de los métodos del test.

Seguidamente, se genera el Mock de AuthComponent.

Finalmente, asociamos el AuthComponent simulado a $this->Posts, de manera que haga el papel que haría el AuthComponent real.

Si fuese necesario, tendríamos que simular o hacer a mano cualquier otra operación que fuese necesaria para reproducir el funcionamiento normal de las clases implicadas.

Los métodos de AuthComponent están simulados en MockAuthComponent, pero no hacen nada. Para que el Mock sea útil necesitaremos indicarle que devuelva ciertos datos al llamar a alguno de sus métodos. Para esto utilizamos el método setReturnValue(), de este modo:


...
$this->Posts->Auth->setReturnValue('user', array('User' => array(...)));
...


La línea anterior le indica al objeto simulado que cuando se llame a su método 'user' devuelva el valor indicado. Gracias a esto podemos simular determinados escenarios que nos interesa probar.

Los parámetros que pueda tener el método son indiferentes en este caso. Sin embargo, es posible indicar a setReturnValue que tenga en cuenta los argumentos del método (esto lo dejaré para otro artículo, pero lo puedes encontrar en la documentación de SimpleTest).

Por ejemplo, ¿qué pasa si no hay un usuario autentificado en la acción edit? Para hacerlo, necesitamos indicarle al Auth simulado, que el método 'user' devuelva false:


...
$this->Posts->Auth->setReturnValue('user', false);
$result = $this->Posts->edit(123);
...


Con el código anterior conseguimos simular que Auth->user no devuelve ningún valor (no hay usuario autentificado) y así probamos cómo responde nuestro método a esa situación. Seguidamente podemos hacer otro test, simulando que sí existe un usuario autentificado.

Si fuese necesario podemos "anidar" objetos simulados. Es decir, si tenemos que simular una clase que contiene otras clases, podemos simular estas y asociarlas.

Para simular propiedades (variables de clase) no tenemos más que asignarles el valor deseado.

Ahora, supón que un método de un objeto simulado es llamado varias veces en el método probado y necesitas que cada vez devuelva un valor distinto. En este caso, debes pasarle primero los valores con setReturnValueAt.

$objeto->setReturnValue('value', false);
$objeto->setReturnValueAt(0, 'value', 'Sample');
$objeto->setReturnValueAt(1, 'value', 'Another sample');


En resumen, utilizando Mock Objects pueden prescindir de las dependencias de la clase probada, lo que hará que tus tests sean más flexibles, fiables y precisos.

miércoles, 9 de junio de 2010

BeforeRedirect

Un recordatorio por si usas el callback beforeRedirect en un Component: recuerda que debe devolver una URL, de otro modo los resultados pueden ser absolutamente desquiciantes.

miércoles, 2 de junio de 2010

Model->displayField en plugins

Al menos en CakePHP 1.3 (me imagino que puede pasar en 1.2) la propiedad displayField de los modelos puede tener un comportamiento un poco errático si te has olvidado de especificar correctamente las relaciones con modelos que están en plugins.

En otras palabras. Si tienes un modelo relacionado con otro que se encuentra en un plugin, no olvides indicarlo "prefijándolo" con el nombre del plugin. Por ejemplo, si tienes un modelo que tiene una relación hasMany con el modelo Item que está en el plugin Contents debes expresarlo así:


var $hasMany = array('Item' => array('className' => 'Contents.Item'));


Si no lo haces, métodos como find('list') no serán capaces de usar correctamente la propiedad displayField del modelo relacionado.

sábado, 15 de mayo de 2010

Yo, la autorización y Cake (II): Entendiendo los sistemas de autorización

Sistemas de permisos "a la Unix"

Una forma de afrontar al problema de la autorización es partir del sistema de permisos Unix. En este sistema, cada recurso tiene asociados varios permisos (lectura, escritura, ejecución) que se asignan a un usuario, a un grupo y al resto del mundo. Es bastante fácil empaquetar estos permisos en un sólo atributos y chequearlos usando operaciones binarias (and, or...).

Puedes incorporar esta información de permisos en la propia tabla del modelo que quieres controlar, o normalizarlo y guardar la información en una tabla de permisos, lo que te permite hacerlo más flexible.

La dificultad puede estar en cómo relacionar esta información de permisos con las acciones de los controladores de tu sistema. Una opción es a través de un mapeado acción-tipo de permiso, o bien creando un permiso específico para cada acción.

En el primer caso, las acciones se agruparían en si son de lectura, escritura o cualquiera de los tipos básicos de permiso que hayas establecido.

En el segundo caso, tendrías que registrar cada acción de un controlador y relacionarla con el recurso y los usuarios con acceso.

El componente ACL de Cake permite una forma de uso que utiliza un sistema de permisos de este estilo.

Sistemas basdos en Listas de Control de Acceso (ACL)

ACL son las siglas de Access Control List. Las listas de control de acceso son, como su nombre indica, listas que relacionan a los sujetos con objetos. Es decir, nos dicen quién tiene acceso a qué en el sistema, o quién tiene prohibido el acceso a qué.

Normalmente, el quién son los usuarios, que pueden estar organizados en grupos. En ese caso, si un grupo tiene ciertos privilegios, sus miembros los heredan gracias a la estructura en árbol de las ACL proporcionadas por CakePHP. Esto nos permite no tener que asignar permisos de forma individual a cada usuario (lo que en una organización grande puede ser un trabajo ímprobo), sino que podemos definirlos con una cantidad mínima de reglas o entradas en la lista de control.

A estos sujetos que Reclaman acceso se les llama técnicamente AROs (Access Request Objects: Objetos que Reclaman Acceso).

El qué son los recursos y objetos de la aplicación. En este caso el concepto es un poco más difuso, ya que los recursos pueden ser de diversos tipos. Por ejemplo, en una aplicación de blogs podemos pensar que los posts son recursos. Pero también son recursos las acciones, o sea, las url de la aplicación a las que acceden los usuarios.

A lo objetos cuyo acceso Controlamos se les llama técnicamente ACOs (Access Controlled Objects: Objetos de Acceso Controlado).

En resumen:

  • ACL: Lista de control de acceso.
  • ARO: Sujeto que reclama acceso, habitualmente usuarios o grupos (pero no necesariamente).
  • ACO: Objetos a los cuales los ACO quieren acceder.

En consecuencia, una ACL es una lista en la que definimos si un ARO puede o no acceder a un ACO. El sistema de autorización consulta esa lista y toma una decisión en función de los datos obtenidos.

Esto es lo que te proporciona CakePHP con el componente ACL.

Sistemas basados en Roles (RBAC)

Sin embargo, las ACL "puras" no son el único sistema de control de autorización. Otra metodología la componen los sistemas de acceso basados en roles.

Un rol define lo que los usuarios que ejercen ese rol pueden hacer en el sistema. Un mismo usuario puede tener varios roles, definidos por el administrador del sistema.

Hay un par de roles que podríamos considerar comunes en la base de todo sistema:

  • self: la capacidad de los usuarios para editar los datos de su propio perfil o cuenta.
  • root: capaz de acceder a cualquier recurso

En cierto modo, se puede pensar en los sistemas basados en roles como en otra forma de interpretar la idea de las listas de control de acceso. La diferencia es que, mientras que las ACL tradicionales suelen responder a la pregunta "¿Puede el usuario U hacer lo que solicita", los sistemas basados en roles responden más bien a "¿Qué puede hacer el usuario U estando aquí?".

Poder hacer esta pregunta y, sobre todo, poder obtener una respuesta es bastante útil. Entre otras cosas, te permite exponer al usuario sólo aquella parte de la aplicación a la que tiene acceso.

Yo, la autorización y Cake (I)

Una de las bases de una aplicación corporativa es un buen sistema de autentificación y de autorización. Este último es uno de los aspectos que me resulta más complicado resolver al desarrollar una aplicación. Hasta ahora mis soluciones en este campo han sido funcionales, pero difíciles de mantener y de escalar.

Conceptualmente son procesos bastante sencillos de entender:

Autentificación es el proceso por el que el sistema determina que un usuario es quien dice ser, o por lo menos que presenta las credenciales correctas. Un sistema de autentificación básicamente toma las credenciales aportadas (habitualmente un usuario y contraseña) y las contrasta con alguna fuente de referencia, que puede ser una tabla de una base de datos, un servidor de autentificación, etc.

Autorización es el proceso por el cual se controla qué puede hacer en el sistema el usuario autentificado, es decir: a qué recursos puede acceder. Un sistema de autorización comprueba si el usuario tiene permiso para realizar las acciones que solicita sobre ciertos recursos disponibles, para lo cual consulta alguna fuente de referencia, como atributos de permisos en los recursos, listas de control de acceso, reglas, etc.

CakePHP proporciona algunas herramientas para gestionar estos procesos y construir nuestros sistemas de autentificación y autorización:


  • Componente Auth. Proporciona una forma sencilla de integrar el control de identidad en nuestro desarrollo, permitiendo también bastante flexibilidad.
  • Componente ACL. Se trata de un sistema genérico que nos permite construir listas de control de acceso jerárquicas y usarlas para comprobar si un usuario tiene capacidad de acceder al recurso que solicita.


El componente Auth funciona muy bien y es muy fácil de integrar en nuestros sistemas. No voy a extenderme en él porque hay buenos tutoriales tanto en el CookBook como en otras fuentes.

Sin embargo, el componente ACL suele ser considerado como más difícil de usar. Creo que esto es debido a varias razones. Al menos estas son las que yo he identificado que me han impedido utilizarlo con éxito hasta ahora:


  • No tener claro su fundamento y su funcionamiento, para saber qué esperar del sistema y cómo utilizarlo
  • Es muy genérico. No está diseñado para un tipo de uso específico, sino que hay que vincularlo a las entidades que van a intervenir en el proceso de autorización y mantenerlo sincronizado
  • Se estructura en torno a un árbol jerárquico que impone algunas limitaciones, como que un mismo usuario no puede estar a la vez en varios grupos
  • Un problema que no resulta fácil de resolver es cómo determinar los recursos a los que tiene acceso un usuario en un momento dado. Me explico. Si diseñamos un sistema basado en ACL que nos permita saber si un usuario puede ejecutar, o no, una acción, nos queda resolver el problema de a qué objetos puede acceder dentro de ella.

jueves, 13 de mayo de 2010

Forzar la actualización de un registro de un Model

Mi vida como desarrollador es un poco caótica, ya que no me dedico a tiempo completo y últimamente otras demandas me quitan montones de tiempo y toneladas de concentración. Por eso, a pesar de llevar trabajando con CakePHP desde hace ya un par de años, tengo dudas en conceptos básicos o tengo que empezar desde cero con algunos proyectos ya que "pierdo el hilo".

Un ejemplo de estos conceptos básicos que tenía dudosos es lo que pasa cuando creas registros en un modelo y cuando manipulas sus id.

Cuando haces un Model->save() sin indicar un id para el modelo, se crea un nuevo registro. Por tanto, para actualizar un registro existente debes indicar un id.

Esto lo puedes hacer tanto en la propiedad Model->id, como en el array de datos que pases a create o a save. Por ejemplo:


array('Model' => array('id' => 12));


Además, se siguen unas cuantas reglas más:

* Si Model->id tiene un valor y en el array de datos se pasa un valor nuevo, prevalece éste último.
* Si el id que has pasado no existe en la base de datos, se crea un registro nuevo con este id

Saber esto es interesante cuando nos interesa controlar la creación de id's mediante un sistema propio.

Por cierto, ¿qué pasa si nuestro id es de tipo integer pero no tiene auto_increment y no lo especificamos en el Model? Resulta que CakePHP es capaz de emular el auto_increment.

miércoles, 28 de abril de 2010

Pregunta abierta: ¿conoces alternativas a getID3?

Funciona bien y td eso, ¿pero conoces alguna alternativa para extraer metadatos de archivos multimedia?

miércoles, 21 de abril de 2010

Obtener la extensión de un archivo

Esta línea nos proporciona la extensión de un archivo conociendo su nombre o su path


$extension = substr($filename, strrpos($filename, '.') + 1);

jueves, 15 de abril de 2010

Valores por defecto para parámetros de un Element

Esta es una técnica sencilla que nos servirá para definir valores por defecto (y también documentarlos) para los parámetros que pasamos a un element.

Como sabrás, los parámetros pasados a los element, están disponibles como variables en el mismo. La idea es definir un array asociativo con los valores por defecto y luego extraer las claves a variables.

El quid de la cuestión es usar el parámetro EXTR_SKIP para que extract no extraiga del array los parámetros que han sido pasados. Es decir, en caso de conflicto, el Element debe quedarse con el parámetro pasado y si no ha sido pasado, toma el valor por defecto.

Esto nos evita una serie de if(isset(...)), y hace muy legible el código y muy fácil documentar los parámetros del Element.

$defaults = array(
    'channel' => false,
    'count' => 1,
    'mode' => 'public',
    'type' => 'full'
    );
  
extract($defaults, EXTR_SKIP);

domingo, 7 de marzo de 2010

CakePHP, MAMP y el socket de Mysql (actualizado)

Actualización

Como comenta Nigeon, basta con poner el path al socket en el parámetro port de la configuración de la conexión.

Al que, por cierto, viene documentado en el manual.


'connect' => 'mysql_connect',
'host' => 'localhost',
'port' => '/Applications/MAMP/tmp/mysql/mysql.sock',


Dejo la entrada anterior porque puede ser útil en algunos casos.

En una entrada anterior ya he comentado el tema de como ajustar las cosas para que CakePHP pueda comunicarse con Mysql. Para ello, hay que crear un enlace simbólico del socket /Applications/MAMP/tmp/mysql/mysql.sock en el lugar adecuado, que en el artículo señalado era /var/mysql/mysql.sock.

Hace poco, tras varias actualizaciones los shells empezaron a "pedir" un socket en /tmp/mysql.sock, por lo que creé un nuevo enlace, pero olvidé la opción -s y creé un enlace duro en lugar de simbólico.

Pues bien, que sepas que los enlaces duros no valen para el caso y los shells no eran capaces de conectar a la base de datos. Ha sido cambiarlo a enlace simbólico y volver a funcionar todo como es debido.

Por su parte, la aplicación web se conectaba perfectamente.