miércoles, 22 de octubre de 2008

Duplicable behavior

Este behavior facilita la tarea de duplicar registros de un modelo. Puedes tomar un modelo guardado como plantilla y aplicar automáticamente algunos cambios a sus campos, así como duplicar los modelos relacionados si los tiene, etc.

Uso básico:

añade una entrada a tu lista de behaviors:

'duplicable' => array(
'cascade' => true; // Duplicar asociados también
'changeFields' => array(), // List of fields to change their content ...
'changeString' => '%s dup.', // ... applying this format string (%s is a container for the original)
'whitelist' => array(), // List of fields to change (empty will duplicate all). Fields not in list will be empty or default
'associations' => array() // List of associations to duplicate (with cascade = true)
)

lunes, 20 de octubre de 2008

AclUpdate Behavior

Un problema de Acl Behavior es que si cambias el "padre" de un modelo (por ejemplo, si cambias el grupo al que pertenece un usuario), el Nodo Acl correspondiente no se actualiza para reflejar la nueva situación.

Este Behavior sirve precisamente para poder superar este problema. Lo he puesto en el bin.cakephp.org

AclUpdateBehavior

Posiblemente se pueda mejorar bastante.

jueves, 16 de octubre de 2008

Media Player Helper, actualización

Act: Tomy Muliady encontró un error en este Helper. Lo he modificado y ahora debería funcionar bien.

He escrito un Helper para insertar el JW FLV Media Player en las Views. Lo he colgado del bin.cakephp.org, Las instrucciones están en el interior.

martes, 7 de octubre de 2008

La encuesta

Bueno, los resultados de la (nada) científica encuesta (pocos votos) evidencian que si se pasan por aquí los teóricos del patrón MVC nos corren a gorrazos y nos echan al pilón por herejes.

Ha salido ganadora la opción de dejar a la vista lidiar con las diferencias en estructuras de datos.

Yo soy más partidario de la segunda opción, hacer que el controller elija la vista aunque él mismo obtenga estructuras de datos un poco diferentes.

La otra opción muchas veces produce métodos demasiado parecidos que atentan bastante contra el principio DRY ("no te repitas"). Tampoco es que haya que respetar estos principios hasta la última consecuencia (en último término te bloquean), sino tomarlo como guía. En el ejemplo que yo proponía adopté finalmente esta opción.

A medida que progreso en el aprendizaje de CakePHP me voy dando cuenta de que trato de eliminar código PHP de las vistas. Es decir, cada vez creo más en las vistas "tontas", que sólo saben recibir datos y mostrarlos, de modo que el código PHP que haya en ellas sólo sirva para convertir formatos o preparar información para helpers o elements. Pero casa vista debe saber recibir un tipo de datos y mostrarlo de una única manera.

Al Controller intento dejarle algo más de inteligencia, pero no demasiada. En ocasiones, como es el caso que proponía, las diferencias son lo bastante pequeñas como para darle al Controller cierto margen de decisión, en este caso, escoger la View, o incluso el layout, en función de ciertos parámetros.

Upload behavior: para subir archivos (CADUCADO)

CADUCADO: La información de este post apesta por lo vieja. Es posible que ya no sea válida con las versiones más recientes de CakePHP. Se mantiene público  para vergüenza y escarnio del autor.

Aunque no está del todo terminado, lo cierto es que este behavior funciona bastante bien. Básicamente sirve para gestionar la subida de archivos por un formulario. Para usarlo, en el modelo tenemos que poner:

var $actsAs = array ('Upload' => array ('imagen' => array ('ruta' => 'test')));

Upload es, por supuesto, el nombre del behavior.

Imagen, en este caso, es el nombre del campo en el que se sube el archivo. Si vamos a poner varios campos para subir archivos, tendríamos que indicarlos como pares "campo" => "opciones". En el ejemplo, la opción ruta nos permite especificar una ruta específica (bajo el directorio base por defecto) para guardar los archivos subidos mediante ese campo.

Las opciones para cada campo son varias y vienen explicadas en el código, en la definición de var $opcionesDefecto y también un poco más abajo.

No tienes que hacer nada más. El behavior se dispara en el beforeSave. CakePHP pone el array de datos de un archivo recién subido en el campo del modelo. Upload behavior mira a ver si hay algún campo del modelo que contenga ese tipo de estructura y en caso afirmativo lo procesa. Si hay otro tipo de información, simplemente lo ignora.

Una cosa importante es que este behavior puede alterar el nombre del archivo. Si éste contiene caracteres que puedan dar problemas según los servidores, los "sanea". De este modo se previenen problemas posteriores de archivos correctamente subidos que luego no se pueden descargar y similares.

Opciones por archivo subido

Que conste que el código parece complicado porque intento poder gestionar muchas cosas:

  • modo: distintos modos de trabajar con el archivo recién subido. Los modos son : url para indicar que se sube el archivo a una ubicación y el campo se trata como una url para acceder a ese archivo (ideal para imágenes o descargas); ruta para indicar que se sube un archivo y el campo se trata como una ruta del sistema de archivos; contenido es para copiar el contenido del archivo en un campo especificado del modelo. En el futuro podría haber soporte para un modo más: bd, que serviría para almacenar la información en una base de datos, pero para eso hay que crear el modelo y algunas cosas más.
  • ruta: especificar una ruta específica para guardar el archivo que hemos subido.
  • mime_permitidos: controlar qué archivos se pueden subir por su tipo mime general o específico (p.ej, podríamos usar image, o bien image/jpeg). Si no se especifica nada, entonces no se controla este tema.
  • ext_permitidas: controlar qué archivos se pueden subir por su extensión. Si no se especifica, se admite cualquiera.
  • sobreescribir: si existe un archivo con el mismo nombre: true lo machaca.
  • crear_ruta: crea las carpetas necesarias.
  • campo_contenido: copia el contenido del archivo al campo especificado.

El código

Para pegar en /app/models/behaviors/upload.php
Bueno, he cambiado el código y lo he puesto aquí, para que sea más fácil leerlo y descargarlo y también más fácil de mantener actualizado.

Procesa feeds

Hay cosas en CakePHP que consiguen asombrarme. Aquí tienes cómo procesar un feed:
$url = "http://cakephpilia.blogspot.com/feeds/posts/default";
App::import('Xml');
$x = new Xml($url);
$data = Set::reverse($x);
debug($data);
¿Dónde colocar este código? Bueno, tal cual está funciona en una View (¡detente, pecador!) o en un Controller, que quizá sea mejor sitio. Depende un poco de lo que estés haciendo, ya que podrías escribir un Model para procesar feeds, llamarlo desde el controller y pasárselo a la vista.

Hay un ejemplo "antiguo" de Felix Geisendörfer en el que define un modelo RSS dentro de una familia de "webModels" para procesar este tipo de cosas. Yo había empezado a utilizarlo, pero leyendo este otro artículo de Fahad Ibnay Heylaal sobre cómo parsear XML y la API de la clase XML me di cuenta de que se podían simplificar bastante las cosas. Y tanto. De dos modelos a 3 líneas de código.

Ojo: funciona mal con feeds redireccionados. O directamente no funciona.

Otra cosa importante, tienes que detectar el formato (es fácil), según sea atom o rss2, ya que  la estructura es diferente. Eso sugiere hacerlo en un modelo (o idealmente un dataSource, pero yo no sé como hacerlo) que pudiese tomar los feeds y normalizar los datos a una estructura, como por ejemplo, el array de datos típico de los modelos en CakePHP.

Esa tarea la dejo para otra ocasión, o como suele decirse, lo dejo como ejercicio para el lector.



miércoles, 1 de octubre de 2008

Notas sobre Localización

¿De dónde saca CakePHP el idioma para localizar e internacionalizar?

Según mi lectura del código de L10n y I18n, la cosa es más o menos así:

L10n::get(), si no se indica ningún argumento, tratará de averiguarlo mediante la información procedente del navegador o bien utilizar el contenido de la constante DEFAULT_LANGUAGE, que se podría definir en bootstrap.php.

Además, se supone que L10n::get() hace un Configure::write('Config.language') para establecer una preferencia de la aplicación.

Por otro lado, I18n::translate(), que es la base de las funciones del tipo __(), que se usan para tener las cadenas de texto localizables (o traducibles) obtiene el idioma a través de la variable de sesión Config.language, y si no hay una establecida, lo hace a partir de Configure::read('Config.language).

En conclusión, una vez que determinas qué idioma hay que servir, hay que registrarlo en la sesión y/o en la preferencia Config.language.

Curiosamente yo no consigo que L10n::get() me funcione bien para fijar el idioma preferido como se menciona en muchos tutoriales  al respecto. De hecho, estoy trabajando en una aplicación que gestiona todo eso sin recurrir a esta clase (básicamente con Configure y con Session) y me va bien.