Sistema de Módulos Para CakePHP (Parte 2) - Scripts de instalación

El objetivo de los scripts de instalación para el sistema de módulos para cakephp es automatizar el proceso de descarga, instalación, actualización y desinstalación de módulos en cakePHP.

Antes de seguir, veamos a que llamamos módulo.

Un módulo es un grupo de funcionalidades, agrupadas por alguna razón en especial. La idea es tener módulos para tareas que siempre usamos y queremos “tirar” dentro de un nuevo proyecto. Un módulo podría ser un sistema básico de usuarios, la expansión del mismo a un sistema de grupos y permisos más elaborado, una lista de email, páginas cms, etc, etc. Básicamente un paquete de funcionalidad que queremos agregar a un sitio.

Por que no usar plugins? Bueno, los módulos podrían ser plugins, ya que hasta ahora no hemos hablado de la estructura del módulo en si, podemos decir que el módulo, aparte de los archivos mencionados en el post anterior, puede contener cualquier archivo que necesite ser instalado en nuestro directorio APP. Con esto podemos tener módulos con controllers, models, views, y también con helpers, behaviors, components, vendors, imágenes, css, scripts, y casi cualquier cosa que nos imaginemos, sin el problema que representa copiar archivos de un proyecto a otro.

Teniendo en cuenta que la instalación, mantenimiento y desinstalación serán automáticas, el único problema que nos queda es que los archivos de distintos módulos no se solapen. A esto lo podemos lograr al tener ciudado y ser metódicos a la hora de crear los módulos, pero nuestro sistema también deberia avisarnos si un módulo que estemos por instalar sobreescribirá un archivo de otro módulo ya instalado, o cualquier archivo ya existente.

Resumiendo el proceso de instalación tenemos:

  1. Descargar la versión requerida del repositorio (Pedir usuario, password y versión de ser necesario)
  2. Chequear que el módulo sea instalable (Tenga los archivos necesarios y se cumplan las dependencias)
  3. Si el módulo ya se encuentra instalado, se pregunta al usuario que curso de acción tomar.
  4. Ejecutar script pre_install del módulo.
  5. Guardar copias de los archivos existentes si son del mismo módulo.
  6. Informar si existe un archivo que no corresponde a otra version del modulo y será reemplazado.
  7. Instalar archivos nuevos.
  8. Guardar en la DB de modulos los datos del nuevo módulo con estado “instalando”
  9. Procesar el archivo schema.sql del módulo.
  10. Ejecutar script de post_install
  11. Registrar el módulo como instalado.

Esta es la linea general a seguir por el script de instalación del módulo. Aspiramos a un script que se pueda usar de la siguiente forma

module-get install|update|uninstall <module-name> [version]

Y creo que en sin querer bautizé a mi script. La idea es usar el mismo script para las tres tareas, instalar, actualizar y desinstalar un módulo. El parámetro de versión seria opcional y si no está presente, el script instalará la ultima versión del módulo especificado.

Para terminar (por ahora), cabe aclarar que las operaciones de instalar, actualizar y desinstalar deberían ser “lo más atómicas posible”, es decir que si algo nos impide seguir con la instalación, este script debería poder dejar las cosas tal y como estaban antes de que comienze a hacer cambios (tanto en archivos como en db).

Sistema de Módulos Para CakePHP (Parte 1)

Hace bastante que vengo trabajando con cake, y una de sus mejores ventajas es la reusabilidad del código que uno hace. El único punto débil en este aspecto es la ausencia de un sistema bien organizado y modular.
Lo que planteo en este artículo es definir un sistema de módulos y una serie de scripts que instalen, configuren, y desinstalen automáticamente.


Antes de continuar, cabe aclarar que este post no plantea ser una solución definitiva, sino una idea que puede evolucionar con el tiempo en algo muy útil.

Dicho sistema debe ser de fácil mantenimiento y actualización. Inspirado en el sistema de paquetes de Debian, los módulos deben tener dependencias,scripts de pre y post instalación/desinstalación, auto configuración de la base de datos, y un sistema de registro que nos diga qué módulos están instalados y cuáles no.

Usaremos una base de datos sqlite, que se integrará al sistema cake sin problemas y podremos mantener la información de todos los módulos instalados.

La primera tabla que necesitamos es modules (hare todo en inglés para mantener la consistencia con el resto del sistema) cuya estructura es (o podría ser) la siguiente:
modules(id,codename,name,version,description,status)
Con esto podemos guardar datos sobre los módulos instalados o a medio instalar.

El directorio APP/conf me parece el lugar ideal para guardar los datos de los módulos, así, tendremos todo en APP/conf/modules, un archivo sqlite (modules.sqli) y subdirectorios con el nombre de cada módulo instalado.

Dentro de cada directorio de módulo tendríamos los siguientes archivos:

  • module_specs.php
  • db_schema.sql
  • install_scripts.php

El primero (module_specs.php) es el que indicará a nuestro script de instalación los detalles del módulo. Debe contener valores para las variables:

  • $name: opcional, el nombre del módulo. El default es el nombre del directorio.
  • $description: opcional, la descripción del módulo.
  • $depends: un array de nombres de módulo de los que éste depende.

El archivo db_schema.sql contiene las ordenes sql para crear la(s) tabla(s) necesaria(s) para el módulo. También puede contener datos iniciales para las mismas.

Este sería el esquema básico para el sistema de módulos para cakephp (hasta ahora podría usarse independientemente del framework).
En el próximo post describiré el proceso de instalación de módulos.

Localización e internacionalización en cakephp 1.2

Uno de los agregados más importantes de cake 1.2 es el soporte para i18n y l10. Para activarla, cualquier controller que procese contenido localizado, debería usar la clase L10n que cake nos provee.

En mi caso, la aplicación completa usará localización, entonces agrego al principio de app_controller.php:

uses('L10n');
class AppController extends Controller {

Luego de esto, necesitamos crear archivos para manejar el texto localizado, cake maneja esto en el directorio APP/locale. Cada lenguaje tiene un conjunto de cadenas de texto con sus repectivas claves, o id. Cada lenguaje es un subdirectorio dentro de APP/locale con el código de tres caracteres correspondiente al lenguaje (ISO 639-2, ver lista).

Cada subdirectorio de lenguaje debe contener un archivo default.po dentro del directorio LC_MESSAGES con las cadenas y sus claves. Por ejemplo, mi .po para inglés es APP/locale/eng/LC_MESSAGES/default.po

Hay que tener en cuenta que cada clave debe ser única y debe tener un valor asociado.

Los archivos .po deber tener un encoding ISO-8859-1 y los strings no pueden tener más de 1014 caracteres y deben estar formados de la siguiente manera:

msgid "clave o id del texto"
msgstr "cadena asociada en el idioma  actual."

Luego te tener esto listo, debemos indicar a cake el lenguaje a utilizar. podemos hacer esto en el controller, mi método preferido: en app_controller.php

function beforeFilter(){
        $this->L10n = new L10n();
        $languages=array('es','en');
        $paramLang=strtok($_SERVER['HTTP_HOST'],'.');
        $lang = $this->Session->check('lang') ? $this->Session->read('lang'):'en';
        if(isset($paramLang) && in_array($paramLang,$languages) ){
            $lang = $paramLang;
            $this->Session->write('lang',$lang);
        }else{
            $this->Session->write('lang',$lang);
        }
        $this->L10n->get($lang);
        Configure::write('Config.language', $lang);
    }

me permite guardar el lenguaje en sesión, y cambiarlo con subdominios. Una vez elegido el método para guardar y cambiar el lenguaje actual, las líneas

        $this->L10n->get($lang);
        Configure::write(’Config.language’, $lang);

son las que indican a cake que debe usar el lenguaje especificado para imprimir las cadenas asociadas cuando usamos __().

Las vistas en cakephp 1.2

Siguiendo con el post anterior, sigo investigando lo nuevo que trae la version 1.2 de cakephp.
Mi primera aplicación es una migración de una con la que vengo trabajando hace bastante, no es nada específico sino más una especie de sitio base, o template para crear aplicaciónes.

Lo primero que encuentro en mi recién creado proyecto, es un montón de archivos .ctp en la vista. En la nueva versión, los .thtml se llaman ahora .ctp (por cake template), pero siguen siendo archivos php y la idea debajo es la misma.

Además, en estos archivos .ctp hay llamadas a una nueva función __(), que maneja la localización y traducciones, la función se declara de la siguiente forma:

__(string $string_id, boolean $return = false)

Y nos permite imprimir el texto asociado al id para el idioma actual. Una cosa importante a notar es que si no existe el texto con el id indicado, esta función imprimirá dicho texto, asi que es buena costumbre usarla siempre para el texto que queramos mostrar en nuestra aplicación.

Por lo demás, las vistas son prácticamente iguales que en cake 1.1, archivos php para mostrar html y el contenido que nos pasa el controlador.

Cake 1.2 trae también nuevos y mejorados helpers, por ejemplo, el FormHelper tiene funciones mucho más completas (y se movió todo lo relacionado a forms a este helper) como:

  • create() para abrir un form
  • end() para cerrarlo
  • input() para crear inputs automágicos (solo $form->input(”nombre_del_campo”) nos crea el campo de formulario correcto, un gran reemplazo para los distintos métodos de cake 1.1, que todavía podemos usar de ser necesario.

El nuevo HtmlHelper ya no tiene métodos relacionados con formularios, y esta disponible por default para todas las vistas. El mismo nos provee functiones para imprimir html bien formado, incluyendo charsets, meta, doctypes, estilos (css), imagenes, divs, links, urls, parrafos, tablas, etc.

Tambien tenemos los ya conocidos JavascriptHelper y AjaxHelper que nos ayudan a agregar efectos dinámicos a la aplicaicón, usando prototype y script.aculo.us y no han cambiado mucho.

NumberHelper, TextHelper y TimeHelper mejorados y con más funciones que nos ayudan a dar formato a sus respectivos tipos de datos siguien facilitandonos la vida en el maravilloso mundo de cakephp.

Instalando cakephp 1.2

Hace bastante tiempo que estoy usando este framework, cakephp está inspirado en rails y plantea ser un framework de desarrollo rápido de aplicaciones web para php.

Me toca en este momento actualizar a la versión 1.2. La versión 1.1 es la versión estable desde hace tiempo, pero la nueva 1.2 es un beta que podría ser considerado “la versión oficial impuesta por el uso”. Muchos sitios están usando cake 1.2 y no es el objetivo de este post hablar de cake, sino documentar mi experiencia del paso de 1.1 a 1.2.

La versión 1.2 trae muchas características nuevas, incluyendo algunas que a veces son totalmente necesarias, como internacionalización (i18n) y localización (l10n).

Entre las más notables entonces tenemos:

  • Mejoras en la validación: se agregan reglas para validación automática como alfanumérica, email (brillaba por su ausencia), url (idem), y en definitiva se provee de métodos de validación mucho más completos (por ejemplo la validación “custom” que nos permite usar expresiones regulares. La lista es gigante, y puede encontrarse aqui.
  • Mejoras en el manejo de caché: cake 1.1 tiene un sistema básico de cache que es ampliado enormemente en la versión 1.2. Ahora podemos elegir entre distintos métodos de caching, e incluso cambiarlos dinámicamente.
  • Soporte para temas: ahora es muy fácil asignar distintos grupos de vistas y crear temas totalmente distintos.
  • Se agrega una consola cake, desde la cual ejecutamos el nuevo script bake, que está bastante mejorado.
  • Se incluye una suite de testing que en estos días es un requerimiento más para el desarrollo.
  • Facilidades para generación de rss y xml
  • paginación: directamente en el core de cake.
  • Mejoras en autenticación
  • Componente email: para generar emails facilmente y desde templates.
  • Mejoras en los helpers, se mueven funciónes para mejorar cohesión y se agrega funcionalidad a los helpers existentes.
  • Manual! si, cake 1.2 sale con un manual completito y bastante bueno cuya versión beta está temporalmente aca

Bueno, suficiente por ahora sobre lo nuevo. instalemos cake:

Primero lo primero, yo tengo la siguiente estructura de directorios:

~/cake_1.2 : aqui va la ultima version de cake 1.2
 ~/public_html : aqui crearemos el nuevo sitio.

entonces, estando en ~/cake_1.2/cake/console tenemos el nuevo script cake que es la consola de la que hablaba antes.

cd ~/cake_1.2/cake/console

lo ejecutamos, queremos especificar que vamos a usar el comando bake, y queremos crear un sitio en ~/public_html

./cake -app ~/public_html/demosite bake

Luego nos hace preguntas sobre nuestro sitio, y en general los defaults estan bien, asi que le damos enter, enter, y ya tenemos nuestro sitio. Y si vamos al browser (con apache instalado y userdirs andando) a http://localhost/~nuestro_login/demosite deberiamos ver la bienvenida de cake. Yo lo tengo configurado como subdominio, asi que entro como demosite.localhost. Como sea que tengamos nuestro servidor de desarrollo, al ir al sitio vemos la bienvenida de cake:

Sweet, “Demosite” got Baked by CakePHP!

Si todo está bien, vemos mensajes de confirmación sobre

  • el directorio tmp (Your tmp directory is writable.)
  • el cache (The FileEngine is being used for caching. To change the config edit APP/config/core.php)
  • la base de datos (Your database configuration file is NOT present.)

Claro que todavia no seteamos la base de datos, para esto, creamos la db y usuario con nuestra herramienta preferida, volvemos a la consola y tipeamos nuevamente:

./cake -app ~/public_html/demosite bake

Y nos pregunta sobre la base de datos (de forma más bonita que el 1.1), le ponemos
name: default,
driver: en mi caso mysql o postgres, el que usemos en el servidor de prueba.
persistent connection: lo que corresponda segun sus necesidades, yo le pongo no
database host: localhost o el servidor de bases de datos que corresponda
port: n usa el puerto default
user y password: lo que corresponda aqui.

Si todo sale bien, vemos un mensaje de confirmación y cuando refrescamos el sitio nos dice “Your database configuration file is present.” y “Cake is able to connect to the database.

En este momento tenemos el sitio básico de cake funcionando y conectado a la db. Es muy parecido a la version 1.1 pero a mi me resultó un poco más cómodo tener el CAKE_CORE_PATH bien configurado por bake :)

Con esto terminamos la instalación básica de cakephp 1.2 que no es mucho más que hacerlo con la 1.1 y ya podemos empezar a crear nuestro sitio.

Como conclusión, no es muy distinto de instalar cake 1.1 solo que el nuevo bake es más inteligente.

Frameworks de desarrollo web

He probado algunos (no todos) de los frameworks web que hay en el mercado, para Java (Struts), PHP (Cakephp), y Ruby (Ruby on Rails). Paso a enumerar ventajas y desventajas segun mi humilde opinion:

* Struts: Ventajas: ninguna, es pesado, hay que configurar uno o más xmls gigantes, en fin, no me gusta para nada, y no lo volvería a usar.

* Cakephp: Muy buen entorno inspirado en rails, anda bien con php 4 y 5, anda en casi cualquier hosting (todos soportan php). Desventajas: es php, es un lenguaje bastante feo. y le falta la capacidad sintáctica de ruby como para tener un framework realmente excelente.

* Ruby On Rails: La diva de los entornos de desarrollo web,
excelente, super cómodo, elegante, rápido, extensible, etc etc. desventajas: no todos los hostingss soportan ruby on rails. Mi hosting (bluehost) si. y la verdad que anda bastante lindo.

Conclusión: Estoy volviendo al poderoso Ruby on Rails. Subiendo la productividad y la calidad de los sitios :)

El Truncate de cakephp me rompe el layout

Y si, para un sitio que hice (un blog), en la pagina de listado tenia el primer pedazo de cada post cortado a, digamos 600 caracteres… el problema: abria un div y no lo cerraba y me cortaba justo al medio. Como estoy usando cakephp termine con la siguiente solucion:

agregar en views/helpers/advtext.php

class AdvtextHelper extends Helper { /** * Truncates text. * * Cuts a string to the length of $length and replaces the last characters * with the ending if the text is longer than length. * * @param string  $text String to truncate. * @param integer $length Length of returned string, including ellipsis. * @param string  $ending Ending to be appended to the trimmed string. * @param boolean $exact If false, $text will not be cut mid-word * @param boolean $considerHtml If true, HTML tags would be handled correctly * @return string Trimmed string. */ function truncate($text, $length = 100, $ending = ‘…’, $exact = true, $considerHtml = false) {        if ($considerHtml) {            // if the plain text is shorter than the maximum length, return the whole text            if (strlen(preg_replace(’/&lt;.*?&gt;/’, ”, $text)) &lt;= $length) {                return $text;            }

            // splits all html-tags to scanable lines            preg_match_all(’/(&lt;.+?&gt;)?([^&lt;&gt;]*)/s’, $text, $lines, PREG_SET_ORDER);

            $total_length = strlen($ending);            $open_tags = array();            $truncate = ”;

            foreach ($lines as $line_matchings) {                // if there is any html-tag in this line, handle it and add it (uncounted) to the output                if (!empty($line_matchings[1])) {                    // if it’s an “empty element” with or without xhtml-conform closing slash (f.e. )                    if (preg_match(’/^&lt;(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)&gt;$/is’, $line_matchings[1])) {                        // do nothing                    // if tag is a closing tag (f.e. )                    } else if (preg_match(’/^&lt;\s*\/([^\s]+?)\s*&gt;$/s’, $line_matchings[1], $tag_matchings)) {                        // delete tag from $open_tags list                        $pos = array_search($tag_matchings[1], $open_tags);                        if ($pos !== false) {                            unset($open_tags[$pos]);                        }                    // if tag is an opening tag (f.e. &lt;b&gt;)                    } else if (preg_match(’/^&lt;\s*([^\s&gt;!]+).*?&gt;$/s’, $line_matchings[1], $tag_matchings)) {                        // add tag to the beginning of $open_tags list                        array_unshift($open_tags, strtolower($tag_matchings[1]));                    }                    // add html-tag to $truncate’d text                    $truncate .= $line_matchings[1];                }

                // calculate the length of the plain text part of the line; handle entities as one character                $content_length = strlen(preg_replace(’/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i’, ‘ ‘, $line_matchings[2]));                if ($total_length+$content_length &gt; $length) {                    // the number of characters which are left                    $left = $length - $total_length;                    $entities_length = 0;                    // search for html entities                    if (preg_match_all(’/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i’, $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {                        // calculate the real length of all entities in the legal range                        foreach ($entities[0] as $entity) {                            if ($entity[1]+1-$entities_length &lt;= $left) {                                $left–;                                $entities_length += strlen($entity[0]);                            } else {                                // no more characters left                                break;                            }                        }                    }                    $truncate .= substr($line_matchings[2], 0, $left+$entities_length);                    // maximum lenght is reached, so get off the loop                    break;                } else {                    $truncate .= $line_matchings[2];                    $total_length += $content_length;                }

                // if the maximum length is reached, get off the loop                if($total_length &gt;= $length) {                    break;                }            }        } else {            if (strlen($text) &lt;= $length) {                return $text;            } else {                $truncate = substr($text, 0, $length - strlen($ending));            }        }

        // if the words shouldn’t be cut in the middle…  if (!$exact) {            // …search the last occurance of a space…   $spacepos = strrpos($truncate, ‘ ‘);   if (isset($spacepos)) {                // …and cut the text in this position    $truncate = substr($truncate, 0, $spacepos);   }  }

        // add the defined ending to the text  $truncate .= $ending;

        if($considerHtml) {            // close all unclosed html-tags            foreach ($open_tags as $tag) {                $truncate .= ”;            }        }

  return $truncate;

 }}?&gt;

el helper, se usa igual que html-&gt;truncate, pero con un parametro extra, que en true toma en cuenta el html para le truncado, y hasta lo cierra bien y todo.

&lt;?=$advtext-&gt;truncate($text,600,’[…]’,false,true)?&gt; asi lo uso yo.

CakePhp y webroot

Las aplicaciones que estuve haciendo en cakephp andaban de lujo en mi server local, pero cuando las subía al server de producción, se rompía el htmlHelper y le agregaba /webroot/ a todas las urls,
es decir donde deberia decir www.example.com ponia www.example.com/webroot/ y asi
example.com/home era example.com/webroot/home etc.

Por lo que pude leer en la web, a nadie más le pasó, o a unos pocos, pero, hete aquí, que problema solucionado, simplemente edite webroot/index.php

define(’WEBROOT_DIR’, ‘/’);

y leesto.
un solo problema, no usen WEBROOT_DIR en la aplicación porque queda apuntando a /.

esto parece solucionar el problema, pero si a alguien se le ocurre algo mejor, espero comentarios o insultos!

hastalavista

CakePhp + phpcaptcha

excelente! necesitaba un captcha para cakephp, y este chabon lo hace re facil. El unico problema fue que me tiraba la ultima letra en blanco. A veces las ultimas dos.

El tema es que buscando y buscando en foros y demas, la unica respuesta que encontraba era algo como, phpcaptcha hace
$iLineColour = imagecolorallocate($this->oImage, rand(100, 250), rand(100, 250), rand(100, 250));
y entonces el “iluminado” concluia que es imposible que te escriba en blanco.

Ahora.

A vos.

Iluminado programador.

Tipito sobrador de los foros.

Si si, a vos que contestaste eso.

Una pregunta.

¿Que pasa cuando queres reservar más colores de los que podés con imagecolorallocate?
¿que te devuelve?
¿En que color pinta las cosas si usas el valor devuelto?

Bueno pibe, te doy la respuesta porque el resto de la gente que lee este foro (nadie) se lo merece. En BLANCO.

Ah! mira vos, asi que era eso?
y por que pasa?
no tengo idea, por que trata de reservar más colores de los que puede.

donde?
y bueno, reservando colores para las líneas…. o sea…
solucion rapida y pedorra: achicar el numero de lineas
cambiar
define(’CAPTCHA_NUM_LINES’, 70);
a
define(’CAPTCHA_NUM_LINES’, 50);

ya funciona(en mi caso)

pero lo que esto deberia hacer es reservar unos n colores aleatorios(ponele 50) al principio y luego utilizar un color al azar de ese grupo.
Queda como tarea para la casa.

Chabon del foro: NO TE TENEMOS MIEDO! que respuesta pelotuda que diste….

Configurar CakePhp

Que perno que es esto, pero el problema, como siempre, estaba entre la silla y el teclado, el problema que tengo (siempre) es que tengo dos configuraciones distintas, una de desarrollo en mi compu y otra, producción en el server que corresponda.
Ya me estoy acostumbrando a eclipse (o será que eclipse se está acostumbrando a mi??) pero siempre cometo los mismos errores al copiar un sitio local a un server.

El problema de hoy en particular fue que estuve unas 5 horas peleando con configuraciones y editando archivos… y era solo que me habia olvidado de copiar la base de datos… que moquero.

Pero estoy convergiendo a alguna configuración decente (espero).

Pero más allá de eso, crear un nuevo sitio con cakephp no debería tomar más de 10 minutos.
eso sí, no se olviden de apuntar bien CAKE_CORE_INCLUDE_PATH a donde tienen su cake, y de crear la base de datos…

Entradas siguientes »

AJAXed with AWP