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')