Los instaladores propios de Composer

17 de junio de 2013

En ocasiones, los paquetes instalados mediante Composer necesitan realizar algunas tareas durante su instalación, como por ejemplo instalar elementos fuera del directorio vendor/ por defecto.

Composer permite crear instaladores propios para definir toda esta lógica específica de cada paquete.

Ejecutando un instalador propio

Suponiendo que ya hayas definido un instalador propio tal y como se explica en la siguiente sección, utilizarlo es tan sencillo como definir el valor adecuado en la propiedad type del archivo composer.json del paquete.

Como cada instalador propio define el tipo de paquete que es capaz de instalar, en cuanto Composer encuentra un instalador capaz de instalar un tipo concreto de paquete, se descarta el instalador normal y se ejecuta el instalador propio.

Un ejemplo práctico de la utilidad de los instaladores propios es la librería phpDocumentor, que contiene varias plantillas que deben instalarse fuera del tradicional directorio vendor/. Para ello, sus creadores han decidido definir un tipo especial de paquete llamado phpdocumentor-template y crear un instalador propio que instale las plantillas en el directorio correcto.

El siguiente ejemplo muestra el archivo composer.json de un paquete de este tipo que contenga plantillas:

{
    "name": "phpdocumentor/template-responsive",
    "type": "phpdocumentor-template",
    "require": {
        "phpdocumentor/template-installer": "*"
    }
}

Para asegurarte de que el instalador propio está disponible al instalar un paquete de ese tipo, es muy importante que el paquete añada a su instalador como dependencia mediante la propiedad require.

Creando un instalador propio

Los instaladores propios son simplemente clases que implementan la interfaz Composer\Installer\InstallerInterface y se encuentran dentro de algún paquete de Composer de tipo composer-installer.

Por lo tanto, un instalador sencillo está compuesto por dos archivos:

  1. El archivo composer.json que define el paquete.
  2. La clase del instalador, como por ejemplo My\Project\Composer\Installer.php (y que implementa la interfaz Composer\Installer\InstallerInterface).

El archivo composer.json

El archivo composer.json es igual que el de cualquier otro paquete normal de Composer, salvo por las dos siguientes restricciones:

  1. La propiedad type debe ser composer-installer.
  2. La propiedad extra debe contener una propiedad llamada class y cuyo valor define el nombre completo de la clase del instalador (también es necesario incluir el namespace de la clase). Si el paquete define varios instaladores propios, este valor debe ser un array de nombres de clases.

Ejemplo:

{
    "name": "phpdocumentor/template-installer",
    "type": "composer-installer",
    "license": "MIT",
    "autoload": {
        "psr-0": {"phpDocumentor\\Composer": "src/"}
    },
    "extra": {
        "class": "phpDocumentor\\Composer\\TemplateInstaller"
    }
}

La clase del instalador

La clase que define el instalador propio debe implementar la interfaz Composer\Installer\InstallerInterface o extender de algún otro instalador que implemente esa interfaz.

Puedes utilizar cualquier nombre para esta clase y puedes colocarla en cualquier directorio, siempre que se pueda cargar de forma automática y su namespace sea el que se indica en la propiedad extra.class del archivo composer.json.

En esta clase también se define el tipo concreto de paquete (en este caso, tipo de instalador) mediante la comprobación que se realiza en el método supports().

Ten mucho cuidado al elegir el valor de la propiedad type, ya que podría entrar en conflicto con los tipos definidos por otros paquetes ajenos a tí. Para evitar posibles colisiones, se recomienda que su valor siga el formato vendor-type (primero el nombre de la persona o empresa que hace el paquete y después, el tipo de paquete concreto), como por ejemplo: phpdocumentor-template.

La clase InstallerInterface define los siguientes métodos (no olvides consultar su código fuente para ver en detalle los parámetros de cada método):

  • supports(), permite comprobar si el valor de la propiedad type coincide con el tipo de paquete definido en el archivo composer.json (observa cómo se realiza esta comprobación en el ejemplo siguiente).
  • isInstalled(), indica si el paquete ya se encuentra instalado o no.
  • install(), este es el método más importante, ya que incluye toda la lógica propia que se debe ejecutar al instalar el paquete.
  • update(), este es el segundo método más importante, ya que incluye la lógica propia que se ejecuta cuando Composer se invoca con el comando update en vez de install.
  • uninstall(), define la lógica que se ejecuta cuando se desinstala el paquete. Asegúrate de borrar y eliminar cualquier archivo o directorio propio de este paquete para dejar el proyecto tal y como estaba antes de instalar este paquete.
  • getInstallPath(), devuelve la ruta donde se va a instalar el paquete. Esta ruta es relativa respecto del directorio donde se encuentr el archivo composer.json.

Ejemplo:

namespace phpDocumentor\Composer;

use Composer\Package\PackageInterface;
use Composer\Installer\LibraryInstaller;

class TemplateInstaller extends LibraryInstaller
{
    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        $prefix = substr($package->getPrettyName(), 0, 23);
        if ('phpdocumentor/template-' !== $prefix) {
            throw new \InvalidArgumentException(
                'Unable to install template, phpdocumentor templates '
                .'should always start their package name with '
                .'"phpdocumentor/template-"'
            );
        }

        return 'data/templates/'.substr($package->getPrettyName(), 23);
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return 'phpdocumentor-template' === $packageType;
    }
}

Este ejemplo muestra lo sencillo que es extender el instalador LibraryInstaller para ajustarse a las necesidades particulares de una librería. En este caso, la librería elimina el prefijo phpdocumentor/template- para instalar el paquete en un directorio completamente distinto al original.

El resultado es que en vez de instalarse en el directorio por defecto /vendor, cualquier paquete de este tipo se instalará en el directorio /data/templates/<nombre_corto>.