Hace pocos días, hablando con 2 cracks de PHP me preguntaban si conocía SPL, Standard PHP Library. En primera instancia me quedé pensando y dije que no. Pasadas un par de horas me puse a buscar información acerca de SPL y llegué a la documentación de php.net. Tras una rápida ojeada de los componentes que forman parte de esta librería me di cuenta de que indirectamente conocía esta librería y quizá a ti te pase lo mismo.
Me explico, seguro que conoces las clases DirectoryIterator, IteratorIterator, RecursiveDirectoryIterator o las interfaces Countable o RecursiveIterator. Pues bien, estas clases forman parte del componente Iterators e Interfaces de SPL.
Standard PHP Library (SPL) es una librería PHP que está compuesta por los siguientes componentes:
Esta extensión de PHP no es nueva, se incorporó parcialmente y por defecto en la versión 5.0.0 de PHP y se aumentó en 5.3, además, en esta última versión SPL no puede ser desactivada. Aun así, quizá aun sea desconocida para muchos programadores PHP.
Por mi parte voy a empaparme por completo de cómo funcionan todos los componentes y espero poder hacer uso de cada uno de ellos cuando los requerimientos me lo pidan :). Cuando estudié "Estructuras de Datos" por la facultad (con Java) vi de gran utilidad las clases que el JDK facilita para el almacenamiento masivo de datos y lo eché de menos en PHP, donde se suele hacer uso exhaustivo del array como contenedor de elementos y no siempre es lo correcto. A partir de ahora habrá que tener en cuenta más a menudo SPL y todas las posibilidades que ofrece.
Hoy en día, la mayoria de aplicaciones web hacen uso de tareas o comandos de optimización, limpieza de datos, captura de eventos, etc. Estas tareas se lanzan a nivel de sistema operativo mediante una tarea Cron (Cron jobs) que hace llamada a una dirección URL o programa.
Planteando el escenario: en uno de los últimos proyectos en los que estoy trabajando he necesitado la ejecución de una tarea para que libere la cola de envío de emails del tipo Newsletter, es decir, el usuario genera un Newsletter asociado a un listado de suscriptores y lo lanza, una vez landazo, éste queda almacenado en una cola de envío. Para liberar esta cola es necesario lanzar la tarea de Symfony project:send-emails.
Esta tarea se puede ejecutar de forma muy sencilla mediante un Cron un par de veces al día, pero ¿qué ocurre si por impedimentos del sistema no puedo depender de un sistema de Cron? No voy a entrar a explicar ahora el porqué no puedo disponer del Cron del sistema, voy a centrarme en comentar cómo lo he solventado.
Básicamente, lo que he hecho ha sido hacer uso de la función exec() de PHP pero con la peculiaridad de lanzarla en segundo plano. Esto es muy sencillo de hacer y el usuario no depende de la finalización de la tarea para que el navegador le devuelva respuesta sino que, la respuesta es inmediata y la tarea queda en ejecución de forma transparente para el usuario.
Veamos el pequeño script PHP:
<?php
$symfonyPath = sfConfig::get('sf_root_dir');
$symfonyCommand = '/symfony project:send-emails --application=backend';
$output = ' > /dev/null &';
exec($symfonyPath . $symfonyCommand . $output);
?>
Vamos a explicarlo línea a línea:
$symfonyPath = sfConfig::get('sf_root_dir');
$symfonyCommand = '/symfony project:send-emails --application=backend';
Esto no es más que la ruta donde se encuentra el ejecutable de Symfony y la llamada al task en sí, poco más..
$output = ' > /dev/null &';
Aquí está el punto más interesante. Con esta línea desviamos la salida que produce la tarea hacia una ruta que actúa como "papelera". Con esto y el caracter &, se consigue que la tarea se ejecute en background y el script PHP no requiera estar esperando la ejecución completa del task hasta tener de nuevo el control sino que, la tarea Symfony la va a gestionar directamente el SO como hilo aparte.
Por último, solo queda lanzar la tarea:
exec($symfonyPath . $symfonyCommand . $output);
De esta forma he conseguido vaciar la cola de envío de emails sin necesidad de una ejecución por medio de Cron y además tengo el control de cuando hacerlo. De esta forma, tras el click del usuario en "Enviar Newsletter" no solo se almacena la cola de envío sino que acto seguido el sistema empieza a procesarla y la gracia de todo esto es que todo queda transparente para el usuario que ejecuta la acción.
Los días 29 y 30 de octube se celebra en Barcelona la 3ª edición de la PHP Barcelona Conference. El año pasado no estuve muy atento de la fecha de celebración y finalmente, cuando me enteré, era demasiado tarde para organizar el viaje. Este año sin embargo me enteré con bastante tiempo de antelación y no me lo pensé..
Con todo reservado solo quedaba elegir las ponencias a las que asistir, ya que hay ponencias en tres salas diferentes y obviamente solo se puede asister a una por horario. La decisión no es fácil, hay una gran variedad de temáticas y ponentes de enorme nivel (Ilia Alshanetsky, Fabien Potencier, Stefan Priebsch, Lorenzo Alberton, Enrico Zimuel, etc.). Tras un difícil decisión, la lista elegida en principio, es la siguiente:
Viernes:
Sábado:
Aquí está el listado completo de ponencias y los horarios de éstas.
Tras la lectura de dos maravillosos libros acerca de métodologías ágiles de desarrollo:
Me dispongo a iniciarme en el mundo de TDD. Para los que no sepáis qué es TDD, se trata de una metodología de desarrollo basada en pruebas: "Test Driven Development" (Desarrollo guiado por pruebas).
Hace varios días que comencé a escribir pruebas unitarias y de integración en mis aplicaciones Symfony y una vez que me noto con soltura y empiezo a cogerle el ritmo (y el gustillo) he decidido que es un buen momento para iniciarme en TDD. Para ello he elegido un libro escrito por Carlos Ble: Diseño Ágil con TDD.
Aclarando conceptos iniciales:
Esto es todo por el momento. Prometo postear con lo que vaya aprendiendo sobre TDD.
Algunos enlaces de interés:
Inspirado por un buen post de Asier Marqués: Desarrolo ágil con symfony, doctrine y mysql workbench en el que nos habla de como usar Mysql workbench y un plugin que nos permite exportar nuestra base de datos a formato YAML, he decidido contar mi corta experiencia con Doctrine migrations.
Hace poco comencé a usar esta opción que nos brinda a todos el ORM Doctrine. Tras dos o tres task lanzados, ya me enteraba de como funciona, es más, empecé a cogerle el gusto...
La idea parte teniendo un schema.yml inicial, una BBDD en el servidor y la necesidad de incorporar cambios en ambos. El uso es mucho más extenso, pero me voy a centrar en los siguiente:
Teniendo el schema.yml
# config/doctrine/schema.yml
Category:
columns:
id:
type: integer(11)
notnull: true
autoincrement: true
primary: true
unsigned: true
name:
type: string(255)
Y la BBDD en el servidor exactamente tal y como se declara en el schema.
Se necesita aplicar un cambio en la BBDD (añadir/eliminar columna, modificar una relación, etc.).
# config/doctrine/schema.yml
Category:
columns:
id:
type: integer(11)
notnull: true
autoincrement: true
primary: true
unsigned: true
name:
type: string(255)
Item:
columns:
id:
type: integer(11)
notnull: true
autoincrement: true
primary: true
unsigned: true
category_id:
type: integer(11)
notnull: true
unsigned: true
name:
type: string(255)
relations:
Category:
local: category_id
foreign: id
type: one
foreignAlias: Items
Cómo hacemos esto?
1. Modificar el schema.yml para aplicar el cambio.
2. Lanzar el task $ php symfony doctrine:generate-migrations-diff. Esto crea una estructura de ficheros en el directorio lib/ del proyecto:
// lib/migration/doctrine/1283537548_version1.php
class Version1 extends Doctrine_Migration_Base
{
public function up()
{
$this->createTable('item', array(
'id' =>
array(
'type' => 'integer',
'autoincrement' => '1',
'primary' => '1',
'unsigned' => '1',
'length' => '11',
),
'category_id' =>
array(
'type' => 'integer',
'notnull' => '1',
'unsigned' => '1',
'length' => '11',
),
'name' =>
array(
'type' => 'string',
'length' => '255',
),
), array(
'primary' =>
array(
0 => 'id',
),
));
}
public function down()
{
$this->dropTable('item');
}
}
// lib/migration/doctrine/1283537549_version2.phpclass Version2 extends Doctrine_Migration_Base
{
public function up()
{
$this->createForeignKey('item', 'item_category_id_category_id', array(
'name' => 'item_category_id_category_id',
'local' => 'category_id',
'foreign' => 'id',
'foreignTable' => 'category',
));
$this->addIndex('item', 'item_category_id', array(
'fields' =>
array(
0 => 'category_id',
),
));
}
public function down()
{
$this->dropForeignKey('item', 'item_category_id_category_id');
$this->removeIndex('item', 'item_category_id', array(
'fields' =>
array(
0 => 'category_id',
),
));
}
}
3. Lanzar el task $ php symfony doctrine:migrate. Aplica en la BBDD los cambios que se crearon en el paso anterior. Se le pueden pasar parámetros (entorno, up / down, etc.)
4. Por último se reconstruyen las clases del modelo: $ php symfony doctrine:build --all-classes
5. No nos olvidemos de borrar la cache..
Con esto, ya tenemos los cambios que requerimos, aplicados tanto en nuestro proyecto Symfony como en nuestra BBDD. Fácil y rápido.
Enlaces:
Proyecto de prueba creado para el post