En ocasiones, es necesario reutilizar una porción de código desarrollada para alguna aplicación Symfony. Si se puede encapsular ese código en una clase, tan sólo es necesario guardar la clase en algún directorio lib/ para que otras aplicaciones puedan encontrarla. Sin embargo, si el código se encuentra desperdigado en varios archivos, como por ejemplo un tema para el generador de administraciones o una serie de archivos JavaScript y helpers que permiten utilizar fácilmente un efecto visual complejo, es muy complicado copiar todo este código en una clase.

Los plugins permiten agrupar todo el código diseminado por diferentes archivos y reutilizar este código en otros proyectos. Los plugins permiten encapsular clases, filtros, event listeners, helpers, archivos de configuración, tareas, módulos, esquemas y extensiones para el modelo, fixtures, archivos estáticos, etc. Los plugins son fáciles de instalar, de actualizar y de desinstalar. Se pueden distribuir en forma de archivo comprimido .tgz, un paquete PEAR o directamente desde el repositorio de código. La ventaja de los paquetes PEAR es que pueden controlar las dependencias, lo que simplifica su actualización. La forma en la que Symfony carga los plugins permite que los proyectos puedan utilizarlos como si fueran parte del propio framework.

Básicamente, un plugin es una extensión encapsulada para un proyecto Symfony. Los plugins permiten no solamente reutilizar código propio, sino que permiten aprovechar los desarrollos realizados por otros programadores y permiten añadir al núcleo de Symfony extensiones realizadas por otros desarrolladores.

17.4.1. Plugins disponibles para Symfony

El sitio web del proyecto Symfony dispone de una página dedicada a los plugins de Symfony. La página se encuentra dentro del wiki de Symfony, en la dirección: http://www.symfony-project.org/plugins/

Cada plugin que se muestra en ese listado, cuenta con su propia página con instrucciones para su instalación y toda la documentación necesaria.

Algunos plugins están desarrollados por voluntarios de la comunidad Symfony y otros han sido desarrollados por los mismos creadores de Symfony. Entre estos últimos se encuentran los siguientes:

  • sfFeed2Plugin: automatiza la manipulación de los canales RSS y Atom.
  • sfThumbnailPlugin: crea imágenes en miniatura, por ejemplo para las imágenes subidas por los usuarios.
  • sfMediaLibraryPlugin: permite gestionar la subida de archivos multimedia, incluyendo una extensión para los editores avanzados de texto que permite incluir las imágenes denro de los textos creados.
  • sfShoppingCartPlugin: permite gestionar un carrito de la compra.
  • sfPagerNavigationPlugin: dispone de controles para paginar elementos de forma clásica y mediante Ajax, basados en el objeto sfPager.
  • sfGuardPlugin: permite incluir autenticación, autorización y otras opciones de gestión de usuarios más avanzadas que las que proporciona por defecto Symfony.
  • sfPrototypePlugin: permite incluir los archivos de prototype y script.aculo.us como librerías de JavaScript independientes.
  • sfSuperCachePlugin: crea versiones cacheadas de las páginas web en el directorio de la cache bajo el directorio web raíz del proyecto, de forma que el servidor web pueda servirlas lo más rápidamente posible.
  • sfOptimizerPlugin: optimiza el código fuente de la aplicación para que se ejecute más rápidamente en el entorno de producción (el próximo capítulo muestra los detalles).
  • sfErrorLoggerPlugin: guarda un registro de todos los errores de tipo 404 y 500 en una base de datos e incluye un módulo de administración para gestionar estos errores.
  • sfSslRequirementPlugin: proporciona soporte para la encriptación SSL en las acciones.

El wiki también contiene otros plugins utilizados para extender los objetos Propel, que también se suelen llamar behaviors. Entre otros, están disponibles los siguientes:

  • sfPropelParanoidBehaviorPlugin: deshabilita el borrado de los objetos y lo reemplaza por la actualización de una columna llamada deleted_at.
  • sfPropelOptimisticLockBehaviorPlugin: implementa la estrategia optimistic locking para los objetos Propel.

Se recomienda visitar de forma habitual el wiki de Symfony, ya que se añaden plugins constantemente y normalmente proporcionan utilidades muy empleadas en el desarrollo de aplicaciones web.

Además del wiki de Symfony, también se pueden distribuir los plugins en forma de archivo para bajar, se puede crear un canal PEAR o se pueden almacenar en un repositorio público.

17.4.2. Instalando un plugin

El proceso de instalación de los plugins varía en función del método utilizado para distribuirlo. Siempre es recomendable leer el archivo README incluido en el plugin o las instrucciones de instalación disponibles en la página de descarga del plugin.

Los plugins se instalan en cada proyecto. Todos los métodos descritos en las siguientes secciones resultan en la copia de los archivos de cada plugin en el directorio miproyecto/plugins/[NOMBRE PLUGIN]/.

17.4.2.1. Plugins PEAR

Los plugins listados en el wiki de Symfony se distribuyen en forma de paquetes PEAR asociados con una página del wiki y disponiles en el canal PEAR oficial de los plugins de Symfony: plugins.symfony-project.org. Para instalar un plugin de este tipo, se utiliza la tarea plugin:install indicando el nombre del plugin, tal y como muestra el listado 17-10.

Listado 17-10 - Instalando un plugin del wiki de Symfony a través del canal PEAR oficial de los plugins de Symfony

> cd miproyecto
> php symfony plugin:install nombrePlugin

También es posible descargar los archivos del plugin e instalarlo desde un directorio del sistema. En este caso, se utiliza la ruta absoluta hasta el archivo del paquete descargado, como se muestra en el listado 17-11.

Listado 17-11 - Instalando un plugin mediante un paquete PEAR descargado

> cd miproyecto
> php symfony plugin:install /ruta/hasta/el/archivo/descargado/nombrePlugin.tgz

Algunos plugins están alojados en canales PEAR externos. En este caso, se pueden instalar mediante la tarea plugin:install después de registar el canal PEAR e indicando el nombre del canal, como se muestra en el listado 17-12.

Listado 17-12 - Instalando un plugin desde un canal PEAR externo

> cd miproyecto
> php symfony plugin:add-channel canal.symfony.pear.ejemplo.com
> php symfony plugin:install --channel=canal.symfony.pear.ejemplo.com nombrePlugin

Estas tres formas de instalar plugins utilizan paquetes PEAR, por lo que se utiliza el término "Plugins PEAR" para referirse de forma indistinta a los plugins del canal PEAR oficial, los de canales PEAR externos y los que se descargan en forma de paquete PEAR.

La tarea plugin:install dispone de varias opciones, tal y como se muestra en el listado 17-13.

Listado 17-13 - Utilizando opciones al instalar un plugin

> php symfony plugin:install --stability=beta nombrePlugin
> php symfony plugin:install --release=1.0.3 nombrePlugin
> php symfony plugin:install --install-deps nombrePlugin

Truco Como sucede con todas las tareas de Symfony, puedes obtener ayuda sobre las opciones y argumentos de plugin:install ejecutando el comando php symfony help plugin:install

17.4.2.2. Plugins de archivo

Algunos plugins se distribuyen en forma de un archivo o un conjunto de archivos. Para instalarlos, simplemente se descomprimen los archivos en el directorio plugins/ del proyecto. Si el plugin contiene un subdirectorio llamado web/, se copia o se realiza un enlace simbólico a este directorio desde el directorio web/ del proyecto, como se muestra en el listado 17-14. Por último, siempre se debe borrar la cache después de instalar el plugin.

Listado 17-14 - Instalando un plugin desde un archivo

> cd plugins
> tar -zxpf miPlugin.tgz
> cd ..
> ln -sf plugins/miPlugin/web web/miPlugin
> php symfony cc

17.4.2.3. Instalando plugins desde un repositorio de código

En ocasiones, los plugins disponen de su propio repositorio de código para el versionado de su código fuente. Estos plugins se pueden instalar simplemente descargando el código desde el repositorio hasta el directorio plugins/, pero este método puede ser problemático si el propio proyecto también utiliza el versionado de su código fuente.

Un método alternativo consiste en declarar el plugin como una dependencia externa, de forma que cada vez que se actualice el código fuente del proyecto, también se actualice el código fuente del plugin. Los repositorios de tipo Subversion, guardan las dependencias externas en la propiedad svn:externals. Como se muestra en el listado 17-15, se puede añadir un plugin simplemente editando esta propiedad y actualizando posteriormente el código fuente del proyecto.

Listado 17-15 - Instalando un plugin desde un repositorio de código

> cd miproyecto
> svn propedit svn:externals plugins
  nombrePlugin   http://svn.ejemplo.com/nombrePlugin/trunk
> svn up
> php symfony cc

Nota Si el plugin contiene un directorio llamado web/, se debe crear un enlace simbólico de la misma forma que la explicada para los plugins de archivos.

17.4.2.4. Activando el módulo de un plugin

Algunos plugins contienen módulos enteros. La única diferencia entre los módulos de plugins y los módulos normales es que los de los plugins no se guardan en el directorio miproyecto/apps/frontend/modules/ (para facilitar su actualización). Además, se deben activar en el archivo settings.yml, como se muestra en el listado 17-16.

Listado 17-16 - Activando un módulo de plugin, en frontend/config/settings.yml

all:
      .settings:
        enabled_modules:  [default, sfMiPluginModule]

Este funcionamiento se ha establecido para evitar las situaciones en las que los módulos de un plugin se puedan habilitar de forma errónea para una aplicación que no los requiere, lo que podría provocar un agujero de seguridad. Si un plugin dispone de dos módulos llamados frontend y backend, se debería habilitar el módulo frontend solamente para la aplicación frontend y el módulo backend en la aplicación backend. Este es el motivo por el que los módulos de los plugins no se activan automáticamente.

Truco El módulo default es el único módulo activado por defecto. Realmente no es un módulo de plugin, ya que es del propio framework (se guarda en el directorio $sf_symfony_lib_dir/controller/default/). Este módulo se encarga de mostrar las páginas de bienvenida, las páginas de error 404 y las de los errores de seguridad por no haber proporcionado las credenciales adecuadas. Si no se quieren utilizar las páginas por defecto de Symfony, se puede eliminar este módulo de la opción enabled_modules.

17.4.2.5. Listando los plugins instalados

Accediendo al directorio plugins/ del proyecto, se pueden observar los plugins instalados, pero la tarea plugin:list proporciona más información: el número de versión y el nombre del canal para cada plugin instalado (ver el listado 17-17).

Listado 17-17 - Listando los plugins instalados

> cd miproyecto
> php symfony plugin:list

Installed plugins:
sfPrototypePlugin               1.0.0-stable # plugins.symfony-project.com (symfony)
sfSuperCachePlugin              1.0.0-stable # plugins.symfony-project.com (symfony)
sfThumbnail                     1.1.0-stable # plugins.symfony-project.com (symfony)

17.4.2.6. Actualizando y desinstalando plugins

Los plugins PEAR se pueden desinstalar ejecutando la tarea plugin:uninstall desde el directorio raíz del proyecto, como muestra el listado 17-18. Si el plugin se instaló desde un canal PEAR diferente al canal oficial de Symfony, para desinstalar el plugin también se debe indicar el nombre del canal (se puede obtener el nombre del canal mediante la tarea plugin:list).

Listado 17-18 - Desinstalando un plugin

> cd miproyecto
> php symfony plugin:uninstall sfPrototypePlugin
> php symfony cc

Para desinstalar un plugin de archivo o un plugin instalado desde un repositorio, se borran manualmente los archivos del plugin que se encuentran en los directorios plugins/ y web/ y se borra la cache.

Para actualizar un plugin, se puede utilizar la tarea plugin:upgrade (para los plugins PEAR) o se puede ejecutar directamente svn update (si el plugin se ha instalado desde un repositorio de código). Los plugins de archivo no se pueden actualizar de una forma tan sencilla.

17.4.3. Estructura de un plugin

Los plugins se crean mediante el lenguaje PHP. Si se entiende la forma en la que se estructura una aplicación, es posible comprender la estructura de un plugin.

17.4.3.1. Estructura de archivos de un plugin

El directorio de un plugin se organiza de forma muy similar al directorio de un proyecto. Los archivos de un plugin se deben organizar de forma adecuada para que Symfony pueda cargarlos automáticamente cuando sea necesario. El listado 17-19 muestra la estructura de archivos de un plugin.

Listado 17-19 - Estructura de archivos de un plugin

nombrePlugin/
  config/
    *schema.yml        // Esquema de datos
    *schema.xml
    config.php         // Configuración específica del plugin
  data/
    generator/
      sfPropelAdmin
        */             // Temas para el generador de administraciones
          template/
          skeleton/
    fixtures/
      *.yml            // Archivos de fixtures
  lib/
    *.php              // Clases
    helper/
      *.php            // Helpers
    model/
      *.php            // Clases del modelo
    task/
      *Task.class.php  // Tareas de la línea de comandos
  modules/
    */                 // Módulos
      actions/
        actions.class.php
      config/
        module.yml
        view.yml
        security.yml
      templates/
        *.php
      validate/
        *.yml
  web/
    *                  // Archivos estáticos

17.4.3.2. Posibilidades de los plugins

Los plugins pueden contener numerosos elementos. Su contenido se tiene en consideración durante la ejecución de la aplicación y cuando se ejecutan tareas mediante la línea de comandos. Sin embargo, para que los plugins funcionen correctamente, es necesario seguir una serie de convenciones:

  • Los esquemas de bases de datos los detectan las tareas propel-. Cuando se ejecuta la tarea propel-build-model para el proyecto, se reconstruye el modelo del proyecto y los modelos de todos los plugins que dispongan de un modelo. Los esquemas de los plugins siempre deben contener un atributo package que siga la notación plugins.nombrePlugin. lib.model, como se muestra en el listado 17-20.

Listado 17-20 - Ejemplo de declaración de un esquema de un plugin, en miPlugin/config/schema.yml

propel:
  _attributes:      { package: plugins.miPlugin.lib.model }
  mi_plugin_foobar:
    _attributes:    { phpName: miPluginFoobar }
      id:
      name:         { type: varchar, size: 255, index: unique }
      ...
  • La configuración del plugin se incluye en el script de inicio del plugin (config.php). Este archivo se ejecuta después de las configuraciones de la aplicación y del proyecto, por lo que Symfony ya se ha iniciado cuando se procesa esta configuración. Se puede utilizar este archivo por ejemplo para extender las clases existentes con event listeners y comportamientos.
  • Los archivos de datos o fixtures del directorio data/fixtures/ del plugin se procesan mediante la tarea propel:data-load.
  • Las clases propias se cargan automáticamente de la misma forma que las clases que se guardan en las carpetas lib/ del proyecto.
  • Cuando se realiza una llamada a use_helper() en las plantillas, se cargan automáticamente los helpers de los plugins. Estos helpers deben encontrarse en un subdirectorio llamado helper/ dentro de cualquier directorio lib/ del plugin.
  • Las clases del modelo en el directorio miplugin/lib/model/ se utilizan para especializar las clases del modelo generadas por Propel (en miplugin/lib/model/om/ y miplugin/lib/model/map/). Todas estas clases también se cargan automáticamente. Las clases del modelo generado para un plugin no se pueden redefinir en los directorios del proyecto.
  • Las tareas del plugin están disponibles en la línea de comandos de Symfony tan pronto como se instala el plugin. Los plugins pueden crear nuevas tareas o redefinir el comportamiento de las tareas existentes. Una buena práctica consiste en utilizar el nombre del plugin como namespace de sus tareas. Si se ejecuta el comando php symfony en la línea de comandos, se puede ver la lista completa de tareas disponibles, incluyendo las tareas proporcionadas por todos los plugins instalados.
  • Los módulos proporcionan nuevas acciones, siempre que se declaren en la opción enabled_modules de la aplicación.
  • Los archivos estáticos (imágenes, scripts, hojas de estilos, etc.) se sirven como el resto de archivos estáticos del proyecto. Cuando se instala un plugin mediante la línea de comandos, Symfony crea un enlace simbólico al directorio web/ del proyecto si el sistema operativo lo permite, o copia el contenido del directorio web/ del módulo en el directorio web/ del proyecto. Si el plugin se instala mediante un archivo o mediante un repositorio de código, se debe copiar manualmente el directorio web/ del plugin (como debería indicar el archivo README incluido en el plugin).

Truco Registrar rutas con un plugin

Los plugins pueden añadir nuevas rutas al sistema de enrutamiento, pero no pueden utilizar un archivo de configuración similar a routing.yml para hacerlo. El motivo es que el orden en el que se definen las reglas es muy importante y la configuración en cascada de Symfony mezclaría todas las rutas de los archivos YAML. Por lo tanto, los plugins que añaden rutas deben registrar un event listener para el evento routing.load_configuration y deben añadir las rutas directamente en el listener:

// en plugins/miPlugin/config/config.php
$this->dispatcher->connect('routing.load_configuration', array('miPluginEnrutamiento', 'listenerEventoCargaConfiguracionEnrutamiento'));

// en plugins/miPlugin/lib/miPluginEnrutamiento.php
class miPluginEnrutamiento
{
  static public function listenerEventoCargaConfiguracionEnrutamiento(sfEvent $evento)
  {
    $enrutamiento = $evento->getSubject();
    // add plug-in routing rules on top of the existing ones
    $enrutamiento->prependRoute('mi_ruta', '/mi_plugin/:action', array('module' => 'miPluginInterfazAdministracion'));
  }
}

17.4.3.3. Configuración manual de plugins

Algunas tareas no las puede realizar automáticamente el comando plugin:install, por lo que se deben realizar manualmente durante la instalación del plugin:

  • El código de los plugins puede hacer uso de una configuración propia (por ejemplo mediante sfConfig::get('app_miplugin_opcion')), pero no se pueden indicar los valores por defecto en un archivo de configuración app.yml dentro del directorio config/ del plugin. Para trabajar con valores por defecto, se utilizan los segundos argumentos opcionales en las llamadas a los métodos sfConfig::get(). Las opciones de configuración se pueden redefinir en el nivel de la aplicación (el listado 17-26 muestra un ejemplo).
  • Las reglas de enrutamiento propias se deben añadir manualmente en el archivo routing.yml.
  • Los filtros propios también se deben añadir manualmente al archivo filters.yml de la aplicación.
  • Las factorías propias se deben añadir manualmente al archivo factories.yml de la aplicación.

En general, todas las configuraciones que deben realizarse sobre los archivos de configuración de las aplicaciones, se tienen que añadir manualmente. Los plugins que requieran esta instalación manual, deberían indicarlo en el archivo README incluido.

17.4.3.4. Personalizando un plugin para una aplicación

Cuando se necesita personalizar el funcionamiento de un plugin, nunca se debería modificar el código del directorio plugins/. Si se realizan cambios en ese directorio, se perderían todos los cambios al actualizar el plugin. Los plugins disponen de opciones y la posibilidad de redefinir su funcionamiento, de forma que se puedan personalizar sus características.

Los plugins que están bien diseñados disponen de opciones que se pueden modificar en el archivo app.yml de la aplicación, tal y como se muestra en el listado 17-21.

Listado 17-21 - Personalizando un plugin que utiliza la configuración de la aplicación

// Ejemplo de código del plugin
$foo = sfConfig::get('app_mi_plugin_opcion', 'valor');
# Modificar el valor por defecto de 'opcion' en el archivo
# app.yml de la aplicación
all:
  mi_plugin:
    opcion:       otrovalor

Las opciones del módulo y sus valores por defecto normalmente se describen en el archivo README del plugin.

Se pueden modificar los contenidos por defecto de un módulo del plugin creando un módulo con el mismo nombre en la aplicación. Realmente no se redefine el comportamiento del módulo original, sino que se sustituye, ya que se utilizan los elementos del módulo de la aplicación y no los del plugin. Funciona correctamente si se crean plantillas y archivos de configuración con el mismo nombre que los del plugin.

Por otra parte, si un plugin quiere ofrecer un módulo cuyo comportamiento se pueda redefinir, el archivo actions.class.php del módulo del plugin debe estar vacío y heredar de una clase que se cargue automáticamente, de forma que esta clase pueda ser heredada también por el actions.class.php del módulo de la aplicación. El listado 17-22 muestra un ejemplo.

Listado 17-22 - Personalizando la acción de un plugin

// En miPlugin/modules/mimodulo/lib/miPluginmimoduloActions.class.php
class miPluginmimoduloActions extends sfActions
{
  public function executeIndex()
  {
    // Instrucciones y código
  }
}

// En miPlugin/modules/mimodulo/actions/actions.class.php

require_once dirname(__FILE__).'/../lib/miPluginmimoduloActions.class.php';

class mimoduloActions extends miPluginmimoduloActions
{
  // Vacío
}

// En frontend/modules/mimodulo/actions/actions.class.php
class mimoduloActions extends miPluginmimoduloActions
{
  public function executeIndex()
  {
    // Aquí se redefine el código del plugin
  }
}

17.4.4. Cómo crear un plugin

Solamente los plugins creados como paquetes PEAR se pueden instalar mediante la tarea plugin:install. Este tipo de plugins se pueden distribuir mediante el wiki de Symfony, mediante un canal PEAR o mediante la descarga de un archivo. Por tanto, si que quiere crear un plugin, es mejor publicarlo como paquete PEAR en vez de como archivo normal y corriente. Además, los plugins instalados mediante paquetes PEAR son más fáciles de actualizar, pueden declarar las dependencias que tienen y copian automáticamente los archivos estáticos en el directorio web/.

17.4.4.1. Organización de archivos

Si se ha creado una nueva característica para Symfony, puede ser útil encapsularla en un plugin para poder reutilizarla en otros proyectos. El primer paso es el de organizar los archivos de forma lógica para que los mecanismos de carga automática de Symfony puedan cargarlos cuando sea necesario. Para ello, se debe seguir la estructura de archivos mostrada en el listado 17-19. El listado 17-23 muestra un ejemplo de estructura de archivos para un plugin llamado sfSamplePlugin.

Listado 17-23 - Ejemplo de los archivos que se encapsulan en un plugin

sfSamplePlugin/
  README
  LICENSE
  config/
    schema.yml
  data/
    fixtures/
      fixtures.yml
  lib/
    model/
      sfSampleFooBar.php
      sfSampleFooBarPeer.php
    tasks/
      sfSampleTask.class.php
    validator/
      sfSampleValidator.class.php
  modules/
    sfSampleModule/
      actions/
        actions.class.php
      config/
        security.yml
      lib/
        BasesfSampleModuleActions.class.php
      templates/
        indexSuccess.php
  web/
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png

Para la creación de los plugins, no es importante la localización del directorio del plugin (sfSamplePlugin/ en el caso del listado 17-23), ya que puede encontrarse en cualquier sitio del sistema de archivos.

Truco Se aconseja ver la estructura de archivos de los plugins existentes antes de crear plugins propios, de forma que se puedan utilizar las mismas convenciones para el nombrado de archivos y la misma estructura de archivos.

17.4.4.2. Creando el archivo package.xml

El siguiente paso en la creación del plugin es añadir un archivo llamado package.xml en el directorio raíz del plugin. El archivo package.xml sigue la misma sintaxis de PEAR. El listado 17-24 muestra el aspecto típico de un archivo package.xml de un plugin.

Listado 17-24 - Ejemplo de archivo package.xml de un plugin de Symfony

<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.4.6" version="2.0"
	xmlns="http://pear.php.net/dtd/package-2.0"
	xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
  <name>sfSamplePlugin</name>
  <channel>plugins.symfony-project.org</channel>
  <summary>symfony sample plugin</summary>
  <description>Just a sample plugin to illustrate PEAR packaging</description>
  <lead>
    <name>Fabien POTENCIER</name>
    <user>fabpot</user>
    <email>[email protected]</email>
    <active>yes</active>
  </lead>
  <date>2006-01-18</date>
  <time>15:54:35</time>
  <version>
    <release>1.0.0</release>
    <api>1.0.0</api>
  </version>
  <stability>
    <release>stable</release>
    <api>stable</api>
  </stability>
  <license uri="http://www.symfony-project.org/license">MIT license</license>
  <notes>-</notes>
  <contents>
    <dir name="/">
      <file role="data" name="README" />
      <file role="data" name="LICENSE" />
      <dir name="config">
        <!-- model -->
        <file role="data" name="schema.yml" />
      </dir>
      <dir name="data">
        <dir name="fixtures">
          <!-- fixtures -->
          <file role="data" name="fixtures.yml" />
        </dir>
      </dir>
      <dir name="lib">
        <dir name="model">
          <!-- model classes -->
          <file role="data" name="sfSampleFooBar.php" />
          <file role="data" name="sfSampleFooBarPeer.php" />
        </dir>
        <dir name="tasks">
          <!-- tasks -->
          <file role="data" name="sfSampleTask.class.php" />
        </dir>
        <dir name="validator">
          <!-- validators -->
          <file role="data" name="sfSampleValidator.class.php" />
        </dir>
      </dir>
      <dir name="modules">
        <dir name="sfSampleModule">
          <file role="data" name="actions/actions.class.php" />
          <file role="data" name="config/security.yml" />
          <file role="data" name="lib/BasesfSampleModuleActions.class.php" />
          <file role="data" name="templates/indexSuccess.php" />
        </dir>
      </dir>
      <dir name="web">
        <dir name="css">
          <!-- stylesheets -->
          <file role="data" name="sfSampleStyle.css" />
        </dir>
        <dir name="images">
          <!-- images -->
          <file role="data" name="sfSampleImage.png" />
        </dir>
      </dir>
    </dir>
  </contents>
  <dependencies>
    <required>
      <php>
        <min>5.1.0</min>
      </php>
      <pearinstaller>
        <min>1.4.1</min>
      </pearinstaller>
      <package>
        <name>symfony</name>
        <channel>pear.symfony-project.com</channel>
        <min>1.1.0</min>
        <max>1.2.0</max>
        <exclude>1.2.0</exclude>
      </package>
    </required>
  </dependencies>
  <phprelease />
  <changelog />
</package>

Las partes más interesates del archivo anterior son las etiquetas <contents> y <dependencies>, que se describen a continuación. Como el resto de etiquetas no son específicas de Symfony, se puede consultar la documentación de PEAR (http://pear.php.net/manual/en/) para obtener más información sobre el formato de package.xml.

17.4.4.3. Contenidos

La etiqueta <contents> se utiliza para describir la estructura de archivos de los plugins. Mediante esta etiqueta se dice a PEAR los archivos que debe copiar y el lugar en el que los debe copiar. La estructura de archivos se define mediante etiquetas <dir> y <file>. Todas las etiquetas de tipo <file> deben contener un atributo role="data". La sección <contents> del listado 17-24 describe la estructura de directorios exacta del listado 17-23.

Nota El uso de etiquetas <dir> no es obligatorio, ya que se pueden utilizar rutas relativas como valor de los atributos name de las etiquetas <file>. No obstante, se recomienda utilizarlas para que el archivo package.xml sea fácil de leer.

17.4.4.4. Dependencias de los plugins

Los plugins están diseñados para funcionar con una serie de versiones de PHP, PEAR, Symfony, paquetes PEAR y otros plugins. La etiqueta <dependencies> declara todas estas dependencias y ayuda a PEAR a comprobar si se encuentran instalados todos los paquetes requeridos, lanzando una excepción si alguno no está disponible.

Siempre se deberían declarar las dependencias de PHP, PEAR y Symfony; al menos se deberían declarar las correspondientes a la instalación propia del autor del plugin, como requerimiento mínimo de instalación. Si no se sabe qué requerimientos establecer, se pueden indicar como requisitos PHP 5.1, PEAR 1.4 y Symfony 1.1.

También es recomendable añadir un número correspondiente a la versión más avanzada de Symfony para la que el plugin funciona correctamente. De esta forma, se producirá un error al intentar utilizar un plugin con una versión muy avanzada de Symfony. Así, el autor del plugin se ve obligado a asegurar que el plugin funciona con las nuevas versiones de Symfony antes de lanzar una nueva versión del plugin. Siempre es mejor que se muestre un mensaje de error y se obligue a actualizar el plugin, que no simplemente dejar que el plugin no funcione y no avise de ninguna manera.

Si especificas los plugins como dependencias, los usuarios pueden instalar tu plugin y todas sus dependencias con un solo comando:

> php symfony plugin:install --install-deps sfSamplePlugin

17.4.4.5. Construyendo el plugin

PEAR dispone de un comando (pear package) que construye un archivo comprimido de tipo .tgz con los contenidos del paquete, siempre que se ejecute el comando desde un directorio que contiene un archivo package.xml, tal y como muestra el listado 17-25:

Listado 17-25 - Creando un paquete PEAR para el plugin

> cd sfSamplePlugin
> pear package

Package sfSamplePlugin-1.0.0.tgz done

Una vez construido el plugin, se puede comprobar que funciona correctamente instalandolo en el propio sistema, como se muestra en el listado 17-26.

Listado 17-26 - Instalando el plugin

> cp sfSamplePlugin-1.0.0.tgz /home/production/miproyecto/
> cd /home/production/miproyecto/
> php symfony plugin:install sfSamplePlugin-1.0.0.tgz

Según la descripción de la etiqueta <contents>, los archivos del plugin se instalarán en diferentes directorios del proyecto. El listado 17-27 muestra donde acaban los archivos del plugin sfSamplePlugin después de su instalación.

Listado 17-27 - Los archivos del plugin se instalan en los directorios plugins/ y web/

plugins/
  sfSamplePlugin/
    README
    LICENSE
    config/
      schema.yml
    data/
      fixtures/
        fixtures.yml
    lib/
      model/
        sfSampleFooBar.php
        sfSampleFooBarPeer.php
      tasks/
        sfSampleTask.class.php
      validator/
        sfSampleValidator.class.php
    modules/
      sfSampleModule/
        actions/
          actions.class.php
        config/
          security.yml
        lib/
          BasesfSampleModuleActions.class.php
        templates/
          indexSuccess.php
web/
  sfSamplePlugin/   ## Copia o enlace simbólico, dependiendo del sistema operativo
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png

Posteriormente, se comprueba si el plugin funciona correctamente dentro de la aplicación. Si todo funciona bien, el plugin ya está listo para ser utilizado en otros proyectos y para compartirlo con el resto de la comunidad de Symfony.

17.4.4.6. Distribuir un plugin desde el sitio web del proyecto Symfony

La mejor forma de publicitar un plugin es distribuirlo desde el sitio web symfony-project.org. Cualquier plugin desarrollado por cualquier programador se puede distribuir desde este sitio web, siempre que se sigan los siguientes pasos:

  1. El archivo README del plugin debe describir la instalación y uso del plugin y el archivo LICENSE debe indicar el tipo de licencia de uso del plugin. El formato de README debe seguir la sintaxis de Markdown.
  2. Entra en el sitio web oficial de Symfony, crea una cuenta de usuario y después crea tu nuevo plugin.
  3. Crea un paquete PEAR para el plugin mediante el comando pear package y comprueba que se ha creado correctamente. El nombre del paquete PEAR debe seguir la notación sfSamplePlugin-1.0.0.tgz (1.0.0 es la versión del plugin).
  4. Sube el paquete PEAR (por ejemplo sfSamplePlugin-1.0.0.tgz) desde la sección de administración de la página de tu plugin en el sitio web oficial de Symfony.
  5. Ahora tu plugin ya debería aparecer en el listado oficial de plugins en http://www.symfony-project.org/plugins/

Si se siguen todos estos pasos, cualquier usuario puede instalar el plugin ejecutando el siguiente comando en el directorio de un proyecto Symfony:

> php symfony plugin:install sfSamplePlugin

17.4.4.7. Convenciones sobre el nombre de los plugins

Para mantener el directorio plugins/ limpio, todos los nombres de los plugins deberían seguir la notación camelCase y deben contener el sufijo Plugin, como por ejemplo carritoCompraPlugin, feedPlugin, etc. Antes de elegir el nombre de un plugin, se debe comprobar que no exista otro plugin con el mismo nombre.

Nota Los plugins relacionados con Propel deberían contener la palabra Propel en su nombre. Un plugin que por ejemplo se encargue de la autenticación mediante el uso de objetos Propel, podría llamarse sfPropelAuth.

Los plugins siempre deberían incluir un archivo LICENSE que desriba las condiciones de uso del plugin y la licencia seleccionada por su autor. También se debería incluir en el archivo README información sobre los cambios producidos en cada versión, lo que realiza el plugin, las instrucciones sobre su intalación y configuración, etc.