lunes, 10 de marzo de 2008

Logs específicos para la aplicación

Todas las clases de CakePHP descendiente de Object cuentan con el método log para registrar los mensajes que necesitemos en archivos de log. Basta llamarlo con el contenido del mensaje y un indicador, opcional, del tipo de mensaje. Los archivos de los están en la carpeta APP/tmp/logs

Cake PHP soporta varios tipos de mensajes, definidos mediante alguna de estas constantes, que es bastante autodescriptiva:

Van al archivo debug.log

LOG_NOTICE
LOG_INFO
LOG_DEBUG

Van al archivo error.log
LOG_ERR
LOG_ERROR
LOG_WARNING

Es decir, puedes registrar un determinado hecho escribiendo una línea como:

$this->log('mensaje', $tipo)

Siendo $tipo una de las constantes indicadas. Cake guardará el mensaje en el archivo que corresponda a cada tipo. Aparte, Cake por sí mismo va anotando diversos errores y problemas durante la ejecución.

Ahora bien, supongamos que nos gustaría tener el registro de actividades de los usuarios de una aplicación en un archivo separado de los anteriores para que sea más fácil saber lo que ocurre y no mezclarlo con la información de errores o de depuración de PHP.

Pues basta con indicar un nombre personalizado para el log y CakePHP se encargará tanto de crear el archivo de log y de escribir en él los mensaje que le dirijamos. Por ejemplo:

$this->log('mensaje', 'miLog');

Esto nos permite unas cuantas cosas interesantes. Podríamos, por ejemplo, crear un archivo de log por usuario, por controlador, por modelo, por fecha o por cualquier criterio que nos parezca. Lo único que tenemos que proporcionar es un identificador o nombre para el archivo.

Por ejemplo, para un log diario el nombre podría ser date('Ymd'), el cual nos daría archivos como 20080310.log y sucesivos. date('Ym') valdría para un log mensual.

Puede ser buena idea sobreescribir el método log para personalizarlo en clases particulares, por ejemplo, para asegurarnos de que los logs de un controlador se escriben en un archivo determinado, dar un formato a los mensajes, etc. Y llamar luego al método "padre".

Lo siguiente es justamente un ejemplo de cómo se podría personalizar. En este caso, se comprueba si hay un usuario autentificado mediante el component Authentication y, si lo hay, se añade su nombre e id al mensaje y luego se anota. La intención es saber qué usuario estaba haciendo qué cosa para el caso de que ocurra el algún comportamiento extraño de la aplicación.


function log($message) {
if ($usuario = $this->Auth->user()) {
$message = $usuario['Usuario']['usuario'].' (id: '.$usuario['Usuario']['id'].') '.$message;
} else {
$message = '(Anón) '.$message;
}
parent::log($message, 'miLog');
}

lunes, 18 de febrero de 2008

Safari, AjaxHelper y las codificaciones

Safari es un buen navegador, pero a veces tiene sus "cositas".

La última que he descubierto es un problema con las codificaciones y las llamadas Ajax. El caso es que éstas llamadas me volvían con caracteres mal codificados. Sin embargo, hasta que no lo probé en Firefox no caí que era un problema de Safari, y no de mi forma de trabajar con el Ajax Helper de CakePHP.

El problema concreto lo explican en este artículo, y tiene que ver con un fallo de la configuración del servidor donde alojemos la aplicación, combinado con un fallo de Safari a la hora de determinar cómo se codifica el contenido que se devuelve a una petición Ajax.

Sencillamente, si el servidor no tiene como juego de caracteres por defecto UTF-8, Safari tampoco sabe cómo manejar esa situación correctamente. Resultado: la codificación sale mal.

La solución:

Si tienes acceso a la configuración del servidor, o al htaccess de la raíz de la aplicación, añade esta línea:

AddDefaultCharset UTF-8


Si no, pide al adminsitrador del servidor que lo haga o que te prepare el servidor para servir contenido UTF-8.

Y con esto funciona estupendamente. (Hay pseudosoluciones a base de enviar cabeceras desde la aplicación, pero a mí no me ha funcionado ninguna de ellas).

domingo, 3 de febrero de 2008

Temas para las aplicaciones Cake

Como estoy manteniendo un desarrollo que voy a usar en diferentes proyectos, me interesaba estudiar el asunto de crear temas para cada uno de ellos.

Por si no tienes claro de qué estoy hablando, los temas tienen que ver con el aspecto visual de las aplicaciones. A mí me interesa sobre todo, poder tener un tema diferente para cada proyecto concreto, aunque compartan buena parte de la funcionalidad y el código de la mayoría de las vistas.

La mejor referencia que he encontrado sobre el particular es este artículo de Tarique Sani y es tan sencillo que apenas lleva un par de minutos preparar una aplicación para dar un soporte básico de los temas. Reproduzco los pasos básicos

En el código

En tu app_controller añade la siguiente línea

var $view = 'Theme';


Básicamente la línea anterior le dice a Cake que vamos a usar temas en la aplicación.

En beforeFilter o beforeRender de los Controllers adecuados (o en el app_controller) añade una línea para indicarle a CakePHP el nombre del tema que vas a utilizar

$this->theme = 'mitema'


El nombre lo puedes aportar directamente, o bien leyendo una variable de configuración, o un ajuste de usuario guardado en la sesión... de dónde quieras.

Cómo se hacen los temas

Como se supone que ya sabes hacer vistas y hojas de estilo y demás, ya sabes hacer temas. Para empezar a crear temas para la aplicación Cake lo primero que tienes que hacer es crear una carpeta "themed" dentro de "views" y otra "themed" dentro de webroot.

Dentro de cada una de ellas crearás una carpeta por cada uno de los temas que vayas a crear. Por ejemplo, "mitema".

Así, la ruta quedará así

/app/views/themed/mitema


/app/webroot/themed/mitema


¿Y qué habremos de colocar ahí? Pues la versión "tematizada" de nuestras vistas, hojas de estilo, javascripts o imágenes que necesitemos, de manera que la estructura habitual de views y webroot quede reproducida bajo la carpeta del tema.

Así, por ejemplo, si necesitamos una vista "tematizada" para la acción /posts/index crearemos el archivo

/app/views/themed/mitema/posts/index.ctp


Y si, por otro lado, queremos un hoja de estilo específica para un tema, usaremos

/app/webroot/themed/mitema/css/estilos.css


Sin embargo, no necesitas reescribir todas tus vistas ni recursos.

Fall-back

Lo mejor de todo es que Cake tiene un sistema de "red de seguridad" para los temas. Esto es, cuando usamos temas, CakePHP buscará layouts, vistas y demás recursos en la carpeta del tema, y si no lo encuentra, buscará en la carpeta básica.

De este modo, puedes escribir tus vistas, hojas de estilo, recursos web comunes y luego tematizar únicamente aquellos que realmente lo necesiten. Por ejemplo, a lo mejor sólo tienes que tocar unas hojas de estilo y un layout. O alguna vista para una acción en particular.

lunes, 28 de enero de 2008

tinyint(1) es un Bit

Pues eso. Que tinyint(1) es un Bit.

¿Y qué significa? Significa que si defines un campo de una tabla de Mysql como Tinyint con un tamaño de 1, CakePHP lo considerará como un bit y cualquier valor que le intentes poner que no sea 1 ó 0 será convertido a 1.

En Mysql 5 ocurre exactamente eso (en versiones anteriores creo que no).

Es un poco "contraintuitivo" ya que la definición de tinyint es básicamente un byte (de 0 a 255).

En fin, yendo a lo práctico, si necesitas tener un campo tinyint para almacenar valores numéricos pequeños, algo normal para flags de estado e indicadores de tipos con pocas opciones, asígnale tamaño 2.

Esto lo he aprendido hoy, tras una hora y pico de desconcierto con un modelo que tiene un campo de "estado" que puede tomar valores 0, 1 y 2, y que siempre se guardaba como 1.

martes, 22 de enero de 2008

Más diversión con el Form Helper: campos de fecha (actualización)

El método input de FormHelper está muy bien para resolver los problemas generales de creación de formularios, pero si por alguna razón nos resulta insuficiente tenemos más recursos a nuestra disposición.

Por ejemplo. Cuando se trata de un campo para fechas, el método no resulta muy flexible (al menos en su versión actual) ya que siempre nos fuerza a un determinado formato. FormHelper tiene un método dateTime que se usa precisamente para crear este tipo de campos y que de hecho es llamado desde input. Sin embargo, si lo usamos nosotros directamente podemos personalizarlo a gusto.

Lo único que tenemos que saber es cómo trabaja Input para generar el código y reproducirlo con nuestras preferencias.

$campoPublicacion = $form->label ('Circular.publicacion', 'Fecha de publicación');
$campoPublicacion .= $form->datetime ('Circular.publicacion', 'DMY', null, null, null, true);
echo $html->div('input', $campoPublicacion);


La explicación del código anterior es bastante sencilla: lo primero que hacemos es generar la etiqueta del campo (label). Después, generamos el control mediante el método dateTime, indicando el formato y como queremos mostrarlo por defecto. Finalmente, empaquetamos eso en un DIV con la clase input.

El resultado es el mismo código que se generaría con input. Sólo que esta vez con el formato deseado. Puedes consultar la API del método dateTime, que es bastante clarita para saber cómo pasar los argumentos.

Actualización 22-1-2008

Con la salida de la versión 1.2 beta ya se ha corregido el asunto del formato de los campos de fecha y es posible pasar un formato en el método input, lo anterior se puede escribir así:


echo $html->input('Circular.publicacion', array('label' => 'Fecha de publicación', 'dateFormat' => 'DMY'));

lunes, 21 de enero de 2008

Buscando en relaciones habtm

Supongamos un típico ejemplo de CMS con su modelo de Post y Tags, los cuales se asocian entre sí mediante una relación de tipo HasAndBelongsToMany (o muchos a muchos en otras terminologías).

Este tipo de asociaciones require una tabla íntermedia de unión (join table) que siguiendo las convenciones de CakePHP nombraríamos en este caso posts_tags. Ya sabes.

Post habtm Tag a través de posts_tags

Cuando hacemos un búsqueda en Post, CakePHP automágicamente incluye en los resultados las Tag asociados a cada Post. Pero, ¿podemos buscar los Post que estén asociados a una o varias Tag además de otras condiciones?

La respuesta es que directamente no podemos hacerlo ya que en las relaciones habtm no se hace el "join" automático de las tablas (al contrario de lo que ocurre en las relaciones hasMany o belongsTo).

Crear una query en SQL para utilizar con Model::query() es una solución, pero tiene el inconveniente de que perdemos algunas de las facilidades propias de CakePHP (básicamente los callbacks como afterFind o beforeFind) y otras ventajas.

En CakePHP 1.2 es posible hacer esto de una manera bastante sencilla aunque require "modelizar" la tabla intermedia. Primero, tenemos que crear un modelo para la tabla posts_tags y especificar sus relaciones con las tablas Post y Tag. Siguiendo las convenciones de CakePHP nos queda así:

<?php

class PostsTag extends AppModel {
var $belongsTo = array(
'Tag',
'Post');
}

?>


En el modelo Post tenemos que especificar que la relación con Tag se va a hacer "con" (with) ese modelo intermedio.

var $hasAndBelongsToMany = array (
'Tag' => array(
'className' => 'Tag',
'with' => 'PostsTag'
)
);


Hasta aquí el trabajo preparatorio. Ahora veamos un ejemplo de su uso. En el model Post tengo un método "publicados" que me devuelve los Post cuya fecha de publicación es anterior a la actual, cuyo flag de publicar está a 1 y que están etiquetados con una tag que le pasamos al método.

function publicados ($tag = false) {
$now = date ('Y-m-d');
$conditions = array (
'Tag.tag' => $tag,
'Post.publicar' => '1',
'Post.publicacion' => "<= $now"
);
return $this->PostsTag->find('all', array('conditions' => $conditions));
}


Espero que se entienda. Las condiciones no deberían tener mucha dificultad. Toda la clave es el find que hacemos sobre el modelo intermedio (PostsTag) asociado a Post, no directamente a Post. (Nota: he suprimido la parte del código que gestiona lo que hay que hacer si no se pasa ninguna $tag).

Algunas referencias que me sirvieron de punto de partida:

Modelizing HABTM join tables in CakePHP 1.2: with and auto-with models de Mariano Iglesias

Modeling relationships in CakePHP (faking Rails’ ThroughAssociation) de Felix Geisendörfer

lunes, 14 de enero de 2008

El truco del espacio tras el tag de php

No recuerdo de dónde saqué la información. Un problema típico de las vistas (los archivos .ctp o thml de CakePHP en general) es que si usas el tag de php la página generada "se come" el retorno de carro que hubiera (o hubiese) chafando lo que de otro modo sería un limpio y ordenado HTML (o texto puro para un email).

La solución consiste en añadir un espacio después del cierre del tag.

viernes, 11 de enero de 2008

Claves memorizables en PHP (y razonablemente seguras)

Bueno, ya veo que hace más de dos meses que no asomo el pelo por aquí. El caso es que he estado ocupado y después de una temporada, por fin puedo volver a CakePHP un rato.

Estoy trabajando en la parte de gestión de usuarios de una aplicación y se me ocurrió que necesitaba un generador de contraseñas para automatizar la creación de las mismas al dar de alta manual o automáticamente a usuarios (no creo que esta aplicación lleve autorregistro).

Mis usuarios odian las contraseñas puramente aleatorias del tipo ajku87hsdf6, así que pensé en utilizar uno de esos generadores de contraseñas legibles o memorizables. No encontré ninguno que me gustase, así que acabé escribiéndolo y esta es la segunda versión (muy difícil no es).

Lo de las contraseñas memorizables es bastante sencillo. Se trata de series de letras construidas de tal modo que parecen palabras y son bastante fáciles de pronunciar y memorizar por humanos. Por ejemplo: hanicu, corchuela o gafimocho.

La base del generador es la construcción de sílabas válidas en español (en este caso), combinando una consonante (o grupo consonántico) con una vocal y opcionalmente con otra consonante válida para finalizar una sílaba.

He añadido un factor de fortaleza que afecta a todo el proceso de generación de la contraseña aumentando la probabilidad de que se generen sílabas con más letras. Esto se traduce en claves más largas, con más posibilidad de repetición de caracteres, etc.

Algunas mejoras posibles serían la posibilidad de convertir al azar algunas letras en mayúsculas o incorporar más caracteres (por ejemplo los acentuados).

El uso de la función es muy simple:

$nuevaClave = claveLegible ();


Lo anterior nos daría claves de fortaleza media

o indicando la fortaleza deseada:

// baja, pasar 'l' de 'low'

$nuevaClave = claveLegible('l');

//alta, pasar 'h', de 'high'

$nuevaClave = claveLegible('h');



<?php

/**
* Genera una clavve legible que se puede memorizar por humanos con mayor facilidad
* sin que por eso se pierda demasiada seguridad
* @param $fortaleza: 'l' (baja), 'm' (media), 'h' (alta). Por defecto es 'm'. Indica
* la solidez teórica de la contraseña generada (más larga y con palabras más difíciles
* de localizar en un diccionario0)
*
* @return string La clave generada
* @author Frankie
**/
function claveLegible($fortaleza = 'm') {
// Preparamos los parámetros de la generación a partir de la indicación de fortaleza
$fortaleza = strtolower($fortaleza);
if ($fortaleza == 'h') {
$factor = 0;
$numeroSilabas = 5;
} elseif ($fortaleza == 'l' ) {
$factor = 4;
$numeroSilabas = 3;
} else {
$factor = 2;
$numeroSilabas = 4;
}
// Fuentes de los caracteres, si quieres modificar la probabilidad de cada uno, añade los que desees
$consonantes = array('b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'ñ', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z', ' ');
$grupos = array('b', 'bl', 'br', 'c', 'ch', 'cl', 'cr', 'd', 'f', 'fl', 'fr', 'g', 'h', 'j', 'k', 'l', 'll', 'm', 'n', 'ñ', 'p', 'pr', 'pl', 'q', 'r', 's', 't', 'tr', 'v', 'w', 'x', 'y', 'z', ' ');
$vocales = array('a', 'e', 'i', 'o', 'u');
$diptongos = array('a', 'e', 'i', 'o', 'u', 'ai', 'au', 'ei', 'eu', 'ia', 'ie', 'io', 'oi', 'ou', 'ua', 'ue', 'uo');
$finales = array(' ', 'n', 'l', 's', 'r', 'd');
// Generación de la contraseña. Cada sílaba se construye con una consontante o grupo inicial, una vocal y una consonante final. Se introduce un factor de aleatoriedad regulado por la fortaleza para generar sílabas más o menos simples.
$clave = '';
for ($i=0; $i < $numeroSilabas; $i++) {
$consonante = rand(0,$factor) ? $consonantes[rand(0, count($consonantes)-1)] : $grupos[rand(0, count($grupos)-1)] ;
$vocal = rand(0, 2*$factor) ? $vocales[rand(0, count($vocales)-1)] : $diptongos[rand(0, count($diptongos)-1)];
$final = rand(0, 4*$factor) ? '' : $finales[rand(0, count($finales)-1)];
$clave .= trim($consonante . $vocal . $final);
}
return $clave;

}

// Test
//
// for ($i=0; $i < 20; $i++) {
// echo claveLegible('m') . chr(10);
// }

?>


Addenda: Uso en CakePHP

Yo he creado una carpeta Utilidades bajo Vendors y he puesto la función en un archivo clave_legible.php.

Cuando necesites usarla en Cake, no tienes más que cargarla con una llamada

vendor('utilidades/clave_legible')

miércoles, 3 de octubre de 2007

Diversión con el Form Helper

O más bien con el método input, el encargado de crear los campos de formulario de este Helper.

El método input es una especie de Juan-Palomo-yo-me-lo-guiso-yo-me-lo-como, porque basta pasarle como parámetro el nombre del campo para que él mismo indague por su cuenta y genere un código html bastante adecuado.

El comportamiento estándar de FormHelper::input consiste en generar una DIV que incluye LABEL y el correspondiente INPUT, adecuado al tipo de datos de la columna de la base de datos. De este modo genera controles para fechas, para cajas de texto, checkboxes, etc.

El método admite un segundo parámetro en forma de array, para pasar cuantas opciones nos parezcan necesarias para modular su comportamiento y obtener el resultado más adecuado. La forma de pasarlas es muy sencilla, el nombre de la opción y su valor se pasan como pares key => value.

class sirve para añadir un atributo class al tag INPUT, excepto que su valor sea "required", en cuyo caso el atributo se añada a la DIV contenedora, que queda de la forma DIV class="input required". De este modo puedes asignarles reglas CSS para que tengan un aspecto visual diferente. Si pones otro nombre en class, se aplicará sólo a INPUT.

size añade el atributo size a los INPUT de tipo texto. CakePHP los genera con el tamaño máximo asignado según lo hayamos definido en la base de datos, pero size nos permite controlar el tamaño visible. Si queremos definir el tamaño de una caja de texto usaremos los atributos rows y cols.

type, como su nombre indica sirve para definir qué tipo de control queremos: text, checkbox, radio o select.

label nos permite indicar una etiqueta de texto para el campo. CakePHP utilizará el nombre de campo definido en la base de datos, pero nosotros podríamos preferir utilizar otro.

div nos permite dirigir opciones a la div contenedora del campo. En este caso, el valor de div es un array con las opciones aplicables. De este modo, podríamos asignarle una class distinta. Eso sí, se sobreescribe la class por defecto (input), lo que deberás tener en cuenta si te interesa mantenerla. Por ejemplo, haciendo así

...'div' => array ('class' => 'input miclase')...


Si el type es select, puedes pasar unas cuantas opciones al campo:

options es un array con las opciones que vas a ofrecer al usuario, en la forma 'valor' => 'texto visible'. Es decir, puedes indicar el valor que pasas a la aplicación aunque pongas un texto visible diferente más comprensible para el usuario.

selected te permite indicar si quieres presentar una opción como seleccionada. El valor null toma el valor que tenga el modelo, en su caso.

empty es el texto que quieres que aparezca cuando no hay nada seleccionado. El lugar adecuado para poner aquello de "Selecciona una opción, por favor".

<?php
echo $form->input ('actividad',
array(
'type' => 'select',
'options' => array(
'Taller' => 'Taller práctico herramientas TIC creativas',
'Reunión' => 'Reunión/encuentro del Proyecto Grimm'
),
'selected' => null,
'empty' => 'Selecciona una actividad, por favor.'
)
)
?>


Se pueden usar otras opciones, como before y after, que te permiten añadir lo que consideres conveniente antes y después del código del campo (siempre dentro de la DIV).

Y esto es todo, de momento, me quedan algunas cosas más que no he podido mirar en profundidad, pero con esto ya tienes para generar unos buenos formularios.

lunes, 3 de septiembre de 2007

Cake Bake y MAMP

Llevaba un tiempo bastante molesto para mi incapacidad para usar las herramientas de generación de CakePHP, la antigua utilidad Bake (actualmente cake bake). El problema es que siempre me salía con un error de conexión con la base de datos.

Yo utilizo en mi máquina de trabajo el paquete MAMP (Apache-MySQL-PHP para Mac OS X), instalado tal como viene por defecto.

El caso es que he podido entender el problema y solucionarlo.

bake busca comunicarse con la base de datos a través del socket

/var/mysql/mysql.sock

Pero si instalamos MAMP sin tocar su configuración para nada, el socket está en:

/Applications/MAMP/tmp/mysql/mysql.sock

La solución es crear un enlace simbólico:

ln -s /Applications/MAMP/tmp/mysql/mysql.sock /var/mysql/mysql.sock

Pero hay un par de requisitos previos. Primero tenemos que crear un directorio mysql bajo var:

cd /var
sudo mkdir mysql

y luego crear el enlace

sudo ln -s /Applications/MAMP/tmp/mysql/mysql.sock /var/mysql/mysql.sock

Y listo.

Nota final

Una cosilla... uso sudo porque al menos en Mac OS X mi usuario de trabajo no tiene permisos sobre la carpeta var para crear el directorio y el enlace simbólico, sin embargo, éste funciona sin ningún problema.