Translate to

Buscar

20 de mayo de 2011

Unobstrusive wev dev

Lea el artículo original en Unobstrusive web dev.

18 de mayo de 2011

Un entorno de desarrollo Drupal portable con XAMPP y FreeCommander

Uso habitualmente Windows 7 y XAMPP 1.7.1 (por la compatibilidad con Drupal 6.x). Hay proyectos que avanzo en casa y hay otros que avanzo en la oficina. En muchas ocasiones requiero consultar cómo hice algo en tal o cual site y me gustaría poder tenerlos al mismo tiempo.

Portatil
Ir cargando una laptop podría ser ser, pero creo que preferiría otra opción.

USB
Podría tener mi entorno de desarrollo en el USB y trabajar directamente allí. XAMPP tiene versiones portables que se pueden instalar en un USB. XAMPP Lite es incluso más ligera.

Sin embargo, mi experiencia es que es realmente muy lento.

Hosting
Un tiempo probé hacer el desarrollo directamente en un hosting. La ventaja es que el desarrollo se hace en un solo lugar. La desventaja es que la edición via FTP es mucho más lenta.

Además, es una opción vulnerable porque el hosting puede quedar fuera de servicio o haber problemas de conectividad, lo que me ocurrió algunas veces (es para ponerse a pensar, si uno ha puesto bastante de su vida en la red).

Mejor parece ser tener una copia local y sincronizar con una copia en la red.

rsync
rsync es un comando que puede ayudarme a sincronizar un site que estoy desarrollando localmente con otro en un hosting.

Para subir un directorio de Windows a un host Linux puedo usar comandos similares a:

cd drupal-dev
rsync --progress -vKrtDzp --chmod=u+rwx,g+rx,o+rx --exclude='.git/*' --exclude='settings.php' . myuser@myhost:public_html/drupal

Y para bajar el directorio del host Linux al directorio Windows puedo usar algo como:

cd drupal-dev
rsync --progress -vrtzL myuser@myhost:public_html/drupal/. .

Así que podría usar cualquier hosting como un punto central de sincronización de archivos.

Opcionalmente, se podría publicar el site si se toma el trabajo extra de actualizar una base de datos en el hosting.

Dropbox
Dropbox es un servicio que permite sincronizar automáticamente un directorio del disco con otro en la red. Entonces, sincronizo el directorio mydropbox de la casa y luego, en la oficina, espero a que la sincronización automática actualice el directorio mydropbox.

Esto funciona bastante bien para compartir información como documentos, manuales e imágenes entre varias computadoras.

Sin embargo, en el caso de Drupal, los cambios que ocurren en los archivos temporales de la base de datos MySQL sobrecargan mucho el tráfico de la red.

Mejor parece ser deterner la sincronización automática mientras se trabaja y luego reanudarla al final.

FreeCommander
FreeComander, es un navegador de archivos alternativo al Explorador de Archivos de Windows. Es libre y se puede descargar de http://www.freecommander.com/. Hay también versiones portables como la de http://portableapps.com/apps/utilities/freecommander_portable.

FreeCommander tiene una opción para sincronizar directorios:


Esto puede ser más fácil de usar que rsync para el caso de sincronizar con un directorio en un USB. Además, usando las versiones portables, podría tener tanto FreeCommander, como XAMPP en el USB.

Algunas soluciones
  1. Usar un hosting para contener una copia intermedia de todo el árbol de xampplite, y usar rsync para las sincronizaciones.
  2. Usar Dropbox para contener una copia intermedia de todo el árbol de xampplite, cuidando de apagar el programa de sincronización automática al empezar y encenderlo al terminar.
  3. Usar un USB para contener una copia intermedia de todo el árbol de xampplite, y usar FreeCommander para las sincronizaciones.
Cuestión de probar, y ver que otras opciones pueda haber :)

Una idea
Me gustaría que se pudiera poner en el USB un Linux portable, que se pudiera bootear rápidamente y permitiera persistencia, para instalar nuevas cosas (como xampplite) y avanzar proyectos.

Que tuviera un LAMP ligero que corriera en un RAM disk en lugar de sobre el USB, para agilizar las operaciones de lectura/escritura. La escritura en el USB sería al final o cuando fuera conveniente.

Me suena a Druppix :) Cuestión de probarlo también.

11 de mayo de 2011

uphp para Drupal

En días recientes, navegando por Internet, me topaba con las opiniones de algunos desarrolladores sobre Drupal.

Comparado con otros frameworks, como los tipo MVC, CakePHP o similares, Drupal es raro. Brillante, pero raro. No es MVC y se apoya, más bien, en una arquitectura que usa hooks para permitir la colaboración de los módulos que se instalen.

Drupal tiene su modo particular de hacer las cosas y cuesta un poco acostumbrarse. A su modo de tratar los formularios, de hacer los temas, de intervenir en el flujo.

Sin embargo, el hecho es que Drupal está floreciendo más rápido que otros frameworks.

Muchos puristas de OOP critican que no use objetos, aunque Drupal dice que implementa los conceptos de OOP de modo diferente.

Quizás sea eso. Que Drupal ofrece una forma diferente de hacer las cosas que, una vez que se aprenden, ayuda a ser más colaborativo y productivo.

Pero, si uno tiene un sistema modelado en OOP tradicional, puede encontrar algunos inconvenientes para pasarlo a Drupal.

Aunque me ha ayudado mucho, Drupal tampoco tiene una forma que me haga sentir completamente cómodo.

Drupal tiene un sistema de tablas que usa para funcionar. Una de las tablas vitales es node. O, al menos, era lo que yo pensaba.

Resulta que node era una tabla opcional que se volvió obligatoria en algún momento del tránsito de la versión 4 a la 5.

¿Sería posible un Drupal sin node? ¿Sería posible un Drupal sin base de datos?

Parece que hay iniciativas al respecto. Aunque node es la base para luego poner CCK y Views, dos de sus módulos más destacados, hay quienes piensan que fue un error hacer que node se volviera obligatorio y que eso se podría corregir (Make Node module optional).

Inspirado por esa posibilidad, traté de ver si podría lograr que Drupal procesara archivos HTML sin necesitar usar tanto la base de datos para ello. Creé un módulo llamado html con la idea de que procesara los archivos que colocara en sites/default/html. Fui incorporando ideas de uphp y, poco a poco, me di cuenta que, en realidad, estaba portando uphp hacia Drupal.

uphp tiene la idea de empezar el desarrollo web con páginas estáticas e ir incorporándole comportamiento dinámico desde afuera, a la manera de unobstrusive javascript. En javascript, jQuery ayuda en eso. En PHP, es QueryPath.

La forma Drupal de hacer las cosas ha sido una inspiración al desarrollar uphp. No me considero un experto en Drupal, sino más bien un principiante, así que me sorprendió que uphp pudiera incorporarse de modo tan natural a Drupal. Sin estudiar mucho las cuestiones del core de Drupal, parece que lo que había imaginado resultó bastante similar.

Así que el módulo pasó a llamarse uphp. Los archivos estáticos se colocan en sites/default/files/html.

Me pareció que podría ser de utilidad a la comunidad y he aplicado para ver si es aceptado como proyecto en drupal.org.

Mientras tanto, he subido el archivo al repositorio de uphp en SourceForge.

4 de mayo de 2011

Resolviendo node.save en Services

El módulo services viene con algunos servicios por default. Node Service pone a nuestra disposición varios servicios relativos a nodos. Uno de ellos es node.save.

node.save permite publicar un nodo desde otro site. En particular, otro site Drupal.

Sin Keys
Servidor
Suponiendo que el site donde queremos publicar es http://myserver.example.com, allí se instala services. Hay que activar los módulos:
  • Services
  • XMLRPC Server
  • Node Service
En la interfaz de administración (admin/build/services), en la pestaña Browse, puede navegar por los servicios disponibles.

Como está activado Node Service, estarán disponibles:
  • node.view
  • node.get
  • node.save
  • node.delete
Cuando elige un servicio, se le mostrará su sintaxis y un formulario de prueba. En el caso de node.save, se requiere como parámetro un array node.

Como la publicación se hará como usuario anónimo, hay que darle los permisos necesarios para crear el tipo de nodo que deseamos. Por ejemplo, si queremos crear de este modo un nodo de tipo 'job', entonces habrá que darle a los usuarios anónimos permiso para eso. Además del permiso load node data del módulo node_service.

Cliente
En algún módulo del cliente, podrá usar un código similar a:

function mymodule_job_plandevuelo_publicar($nid) {
  $plandevuelo_url = 'http://myserver.example.com/';
  $node = node_load($nid);
  $node->type = 'job';
  unset($node->nid);
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', 'node.save', $node);
  return $xmlrpc_result;
}

En este ejemplo, el cliente obtiene, de sus propias tablas, un nodo con nid especificado. Luego, usa la función xmlrpc para llamar al servicio node.save en el servidor.

Notar que el url es el del archivo xmlrpc.php del servidor. También que el nombre del service es el segundo parámetro de xmlrpc(). A partir de allí, los parámetros restantes son pasados en el mismo orden a la función que atiende el servicio, en el servidor. Así que $node será el primer parámetro para la función que atiende node.save.

Aunque es importante seguir las reglas del service, a veces puede ser frustrante. Por ejemplo, para crear un nuevo nodo, el service requiere que el nodo no tenga nid. Si lo tiene, se obtiene un mensaje de Acceso Denegado. También requiere que se indique el tipo del nodo ('job', en este ejemplo).

Con Keys
Servidor
Se configura el servidor como en el caso Sin Keys.

Se requiere activar, además, los módulos:
  • Key Authentication
  • System Service
También, en la interfaz de administración (admin/build/services), en la pestaña Opciones, elegir en el combo que el módulo de autentificación es Key authentication.

Si marca la casilla Use keys, cuando guarde las opciones, aparecerá una pestaña Keys donde puede definir llaves para limitar el acceso a services por dominio.

Por ejemplo, podría definir una llave My Client, para el dominio myclient.example.com, marcando alli node.save y otros servicios que se deseen.

Deje marcada la casilla Use sessid para usar incluir un session id en la identificación. El session id se obtiene con ayuda del servicio system.connect.

Entrando a Browse y eligiendo node.save, podrá ver que la sintaxis ha cambiado y requiere más parámetros antes del node.

a) Si dejó desmarcada la opción Use keys, node.save requerirá dos parámetros:
  1. sessid
  2. node
b) Si dejó marcada la opción Use keys, node.save requerirá seis parámetros:
  1. hash
  2. domain_name
  3. domain_time_stamp
  4. nonce
  5. sessid
  6. node
Cliente

a) Sin Use keys
Si se dejó desmarcada la opción Use keys en el servidor, en el cliente se podrá usar un código similar a:

function mymodule_job_plandevuelo_publicar($nid) {
  $plandevuelo_url = 'http://myserver.example.com/';
  $node = node_load($nid);
  $node->type = 'job';
  unset($node->nid);

  $domain_name = $_SERVER['HTTP_HOST'];
  $domain_time_stamp = (string) time();

  // connect
  $method = 'system.connect';
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method);
  $sessid  = $xmlrpc_result['sessid'];

  // save
  $method = 'node.save';
  
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method,
    $sessid,
    $node
  );
  
  return $xmlrpc_result;
}

Notar que ahora se requiere invocar a system.connect para obtener sessid.

b) Con Use keys
Si se dejó marcada la opción Use keys en el servidor, podrá usar un código similar a:

function mymodule_job_plandevuelo_publicar($nid) {
  $plandevuelo_url = 'http://myserver.example.com/';
  $node = node_load($nid);
  $node->type = 'job';
  unset($node->nid);

  $domain_name = $_SERVER['HTTP_HOST'];
  $domain_time_stamp = (string) time();

  // connect
  $method = 'system.connect';
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method);
  $sessid  = $xmlrpc_result['sessid'];

  // save
  $method = 'node.save';
  $nonce = user_password();
  $domain_time_stamp = (string) time();
  $hash = hash_hmac('sha256', $domain_time_stamp .';'.$domain_name .';'. $nonce .';'.$method, '90a84268ba54e7ea02571524586997db');
  
  $xmlrpc_result = xmlrpc($plandevuelo_url.'xmlrpc.php', $method,
    $hash,
    $domain_name,
    $domain_time_stamp,
    $nonce,
    $sessid,
    $node
  );
  
  return $xmlrpc_result;
}

Una forma de hacer update

node.save requiere el $node->nid y además un $node->changed actualizado, por ejemplo:

...
  /* // CREATE
  $node->type = 'job';
  unset($node->nid);
  */
  // UPDATE
  $node->nid = 16;
  $node->changed = time();
...

Algunas precauciones
Aunque en este ejemplo he pasado a node.save casi directamente el $node que obtenía con node_load(), he notado que puede ser más conveniente formar un array con sólamente los valores requeridos y pasar recién ese array a node.save.

De otro modo, puede tener resultados inesperados. Por ejemplo, si el nodo en el local no tiene habilitado la opción de comentarios, mientras que en el destino se desea que sí lo tenga, si uno pasa el node completo se pasan también esas preferencias. Crearía un nodo sin la opción de comentarios o, al actualizar, eliminaría los comentarios que se le hubieran asociado.

Referencias:

3 de mayo de 2011

Usando XMLRPC con Services

Services es un módulo que permite definir web services que responden a solicitudes usando diversos protocolos. Por default, viene con soporte para XMLRPC.

En este ejemplo, hay un proveedor de web services y un cliente de web services.

Proveedor
Supongamos que el proveedor del web service tenga el url http://webservice-provider.com

Luego de instalar el módulo services (y activar Services y XMLRPC Server) puede entrar al administrador, admin/build/services, para navegar por los services definidos por default.

Uno puede agregar más services, colocando un módulo adecuado en modules/services/services/.

Por ejemplo, si tengo un tipo de contenido llamado job:

modules/services/services/job_service/job_service.info
name = Job Service
description = Provides a job service
package = Services - services
dependencies[] = services
core = 6.x


modules/services/services/job_service/job_service.module
/**
 * Implements hook_service
 */
function job_service_service() {
  return array(
    array(
      '#method' => 'job.all',
      '#callback' => 'job_service_all',
      '#return' => 'array',
      '#args' => array(
        array(
          '#name' => 'fields',
          '#type' => 'array',
          '#optional' => TRUE,
          '#description' => t('List of fields')
        ),
        array(
          '#name' => 'limit',
          '#type' => 'int',
          '#optional' => TRUE,
          '#description' => t('To limit result')
        )
      ),
      '#help' => t('Returns a list of the trabajo content type nodes.'),
      '#access callback' => 'job_service_all_access'
    )
  );
}
/**
 * Callback
 */
function job_service_all($fields=array(), $limit=0) {
  $sql = "SELECT * FROM {node} WHERE type='job'";
  if ($limit>0) {
    $sql .= " LIMIT $limit";
  }
  $result = db_query($sql);
  $nodes = array();
  while ($node = db_fetch_object($result)) {
    $nodes[] = services_node_load(node_load($node->nid), $fields);
  }
  return $nodes;
}
/**
 * Access Callback
 */
function job_service_all_access() {
  return TRUE;
}

Una vez definido, se puede probar en la lista de services que aparece en admin/build/services.

Cliente
En el cliente del web service, se puede probar el acceso al web service del proveedor con algo como:

/**
 * Implemens hook_menu
 */
function misc_menu() {
  $items = array();
  $items['misc/test'] = array(
    'page callback' => 'misc_test',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

function misc_test() {
  $items = xmlrpc('http://webservice-provider.com/xmlrpc.php', 'job.all');
  pr($items);
  pr($openid_provider['url'].'xmlrpc.php');
  return 'OK';
}

Como se observa, xmlrpc() permite invocar al service job.all. El url es el del archivo xmlrpc.php localizado en el proveedor.

Si hubiera más argumentos, serán pasados en el mismo orden al método que atiende el service. En este caso, job_service_all().

Más artículos