Composer y Drush en el contexto de Drupal

Como gestionar dependencias en proyectos Drupal 8

@davidjguru
Drupal & /me
31 min readMay 13, 2018

--

Photo by Paul Gilmore on Unsplash

Varias personas me han escrito últimamente con fines parecidos: bien para hacerme alguna pregunta sobre la instalación de Drupal 8 a través de Composer, bien para consultarme que hacer ahora para tener Drush asociado al proyecto, bueno y alguna también para maldecirlo todo.

Es curioso porque observando bien, veo que normalmente las consultas suelen venir de compañeros y compañeras que se encuentran en una doble transición: de Drupal 7 a Drupal 8 y del site building con Drupal a desarrollo de código. Creo que es ahí donde se produce mucha confusión conceptual y es justo en ese punto acumulativo de tensiones en el que me gustaría intervenir con este artículo.

Me he animado a recopilar algunas notas y experiencias y dejarlas estructuradas a modo de artículo consultable con la idea de compartir un poco de conocimiento, dándole una cierta articulación al contenido y siempre con el objetitvo de ayudar a esas personas que se encuentran en esa transición (como yo también lo estuve) y poder hacer un RTFM! cuando llegue el momento :-P.

Lo importante es - como siempre - que todo esto le resulte útil a alguien.

Nota: Este artículo tiene más de 6500 palabras, unas cuatro iteraciones hasta el momento (sucesivas ediciones posteriores, hasta mayo de 2019) y Medium dice que hay que invertir al menos 29 minutos en leerlo.

Índice: en este artículo
1- Primera aproximación a Composer
1.1- ¿Qué es Composer?
1.2- ¿Para que sirve?
1.3- Uso y funcionamiento
2- Instalando y probando Composer
2.1- Instalación global / Instalación local
2.2- Los comandos de Composer más usuales
2.3- Ejemplo de uso con PHPUnit
3- Tips para usar Composer
3.1- Especificando versiones
3.2- Haciendo las cosas más ligeras
3.3- Cambiando el repositorio de paquetes a Drupal.org
4- Gestionando proyectos Drupal con Composer
4.1- Mecánica de trabajo
4.2- Instalación de Drush vía Composer
4.3- Uso combinado de Drush y Composer con Drupal 8
5- Enlaces, referencias y lecturas de interés
5.1- Sobre Composer y otras experiencias cruzadas
5.2- Sobre Packagist como repositorio privado
5.3- Otros
Otros artículos relacionados que podrían interesarte:
Form API(I): Comprender, crear y modificar formularios en Drupal 8
Form API(II): Modificando formularios en Drupal8 mediante form_alter
Form API(III): Caso práctico de modificación de formulario Drupal 8
Otros artículos no relacionados que podrían interesarte:
Single Page Application: Un viaje a las SPA a través de Angular y Javascript(I)
Single Page Application: Un viaje a las SPA a través de Angular y JavaScript(II)

1- Primera aproximación a Composer

En primer lugar y a modo de introducción necesaria vamos a dar un paseo por los alrededores de Composer para conocer mejor de que estamos hablando o que es lo que estamos maldiciendo.

1.1- ¿Qué es Composer?

Composer (https://getcomposer.org+)es una aplicación para línea de comandos construida con PHP, lanzada en marzo de 2012 y enfocada a la gestión de dependencias en proyectos basados en el lenguaje PHP. Se basa en otros gestores de dependencias anteriores que ya estaban disponibles para NodeJS (npm+ ) o Ruby (bundler+ ) e intenta llevar el mismo concepto a entornos basados en el lenguaje PHP.

Básicamente, los gestores de dependencias existen para hacernos la vida más fácil en torno a dos conceptos: automatización (lanzar procesos que funcionen por si mismos) y estandarización (que dichos procesos operen de la misma manera unificada), especialmente en el contexto de las librerías de terceros que solemos usar en nuestros proyectos.

1.2- ¿Para qué sirve?

Básicamente, como anotaba antes, Composer es un gestor de dependencias. Ojo, de dependencias (que podrán ser paquetes u otros formatos). Composer trata con paquetes, librerías y recursos de diferentes tipos. Pero además, es posible interpretar una instalación inicial como una dependencia inicial por resolver, por lo que también puede usarse como herramienta de instalación inicial de Drupal. Más adelante veremos cómo.

Podemos decir (de momento), que Composer consiste en dos piezas clave: por un lado una herramienta de línea de comandos para gestionar estas dependencias e interactuar desde la consola del terminal y por otro lado un repositorio donde se almacenan los paquetes llamado “Packagist.org”

En medio, hay un elemento que los suele conectar a ambos: un fichero composer.json que registra las necesidades de cada proyecto para responder como un registro con herramienta de comandos, que se basa en estas anotaciones para solicitar paquetes y dependencias al repositorio. Como complemento a ese fichero .json anterior existe otro llamado composer.lock. ¿En que consiste este último? Es un archivo de registro, como un cuaderno donde Composer anota la versión exacta que se ha instalado de cada librería. Es la forma que tiene Composer de estabilizar un proyecto en una serie de versiones específicas de sus dependencias. Así, cualquier persona o sistema que se descargue el proyecto tendrá la misma versión que el resto.

Eh, pero ¿No me habías dicho que para notificar versiones y registrarlas ya estaba el composer.json? DEVUÉLVEME MI DINERO HDF.

Sí y no. A Composer se le pueden pedir (por ejemplo), intervalos de versiones y que elija la que quiera (lo veremos en un apartado siguiente). En ese caso, solo tenemos en el .json la indicación de dicha horquilla de valores, pero en el composer.lock tendremos la versión específica que al final se ha instalado para el proyecto. ¿Lo ideal? que composer.json y composer.lock viajen unidos al repositorio git del proyecto para que se complementen.

1.3- Uso y funcionamiento

Hasta el momento, Composer está disponible para las siguientes plataformas y versiones:

Composer quiere resolver la necesidad de tener que averiguar que dependencias de librerías de terceros tiene tu proyecto, sabiendo que es bastante normal hoy día que tus dependencias directas tengan a su vez varias dependencias indirectas que no tienes porque conocer (ni sus versiones), así que se lanza a resolver el árbol de dependencias del proyecto en cuestión, realizando una instalación -local- de librerías (nada de instalar a nivel global) desde un directorio vendor/ en cada proyecto en el que lo uses.

¿Esto cómo va?

Una vez instalado en un sistema, Composer permite a través del uso de sus comandos localizar, descargar e instalar las dependencias necesarias con su versiones requeridas a través de una conexión a un repositorio externo para recursos PHP llamado Packagist (https://packagist.org+) que ya comentamos anteriormente y de donde extrae todos los recursos que necesita. Y para todo ello se apoya en el fichero composer.json, cuya unidad de registro mínima es la siguiente:

{
"require": {
"vendor/project": "1.0.*"
}
}

Con la keyword “require”: anotamos que el proyecto necesita una librería proporcionada por un usuario en una versión determinada. (Y que al ser required, si no está disponible Composer no realizará la instalación). Vendor sería algo así como “proporcionador” (¿vendedor? ¿proveedor?) y proyecto el nombre de la dependencia a resolver. Veamos la apariencia de un fichero composer.json “más real”:

{
"name": "davidjguru/proyecto",
"description": "Mi Nuevo Proyecto",
"authors": [
{
"name": "David Rodríguez",
"email": "davidjguru@gmail.com"
}
],
"require": {
"phpunit/phpunit": "7.1.5"
}
}

2- Instalando y probando Composer

Habrás notado que cuando te descargas y abres un Drupal 8 hay un fichero llamado composer.json dentro del directorio principal. Bien, hablaremos de ese fichero más adelante pero lo principal ahora es saber que es un fichero para poder jugar con Composer. De un lado ese fichero y del otro la propia aplicación, que debes instalar en tu máquina.

Existen dos maneras de instalar Composer: de manera local para cada proyecto y de manera global mediante un acceso general desde cualquier lado de tu máquina y ejecutándolo desde el directorio de cada proyecto o subproyecto. Veamos como discurren estas dos maneras de instalarlo.

2.1- Instalación global / Instalación local

Composer se maneja inicialmente a través de un instalador construido en código PHP que puedes descargar aquí +, consultar su código aquí+ y seguir las instrucciones de instalación aquí+. Ok. Básicamente, el plan es que teniendo los recursos y librerías iniciales de PHP ya en tu sistema, te descargues el instalador, lo renombres como un fichero de extensión .php y lances su ejecución (es lo que ocurre en el set de instrucciones siguientes que vienen en las instrucciones de descarga e instalación).

Debes tener php listo para usar en consola:sudo apt-get install php7.0-cli

Desde terminal:

// Descarga el instalador y lo renombra
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
// Comprueba la integridad del instalador descargado
php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
// Arranca el instalador y se lo cepilla al terminar
php composer-setup.php
php -r "unlink('composer-setup.php');"

Estas instrucciones anteriores te traen a tu sistema un composer.phar, una especie de ejecutable PHP que en base a donde lo ubiques tendrá un comportamiento global o local. También puedes descargarte directamente el .phar en tu sistema: https://getcomposer.org/composer.phar+

También puede instalarse siguiendo las siguientes instrucciones:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
chmod +x /usr/local/bin/composer
composer

Localmente:

Para instalarlo localmente, debes lanzar el instalador dentro del directorio del proyecto. En cuanto se haya descargado el .phar estará disponible para ejecutarse mediante php composer.phar en la carpeta.

Globalmente:

Para instalarlo de una manera global en tu sistema debes chutarlo en un directorio que sea parte del PATH . En los sistemas Unix-based suele andar por el /bin y desde ahí puede llamarse por línea de comandos. En la siguiente instrucción el decimos al instalador de Composer que ubique el composer.phar en /bin y que se renombre como “composer”, para ser invocado desde el prompt:

$ php composer-setup.php --install-dir=bin --filename=composer

Nota: en ciertos entornos hay que explicitar bin como “/bin” en la instrucción anterior. Esto me ha ocurrido hace poco (julio de 2018) administrando un RedHat 6.6 con más años que una playa (2014):

Lo que se puede resumir en dos instrucciones:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php --install-dir=/bin --filename=composer

O también puedes descargarlo en cualquier parte y luego moverlo al /bin con el comando mv: $ mv composer.phar /usr/local/bin/composer

A nivel de uso, la diferencia estará en que si lo has instalado a nivel local para un proyecto tendrás que usar: $ php composer.phar uncomandodecomposer

Y si lo has instalado a nivel global, podrás manejarte desde cualquier sitio mediante: $ composer unbonicocomandodecomposer

Y al final si todo ha ido bien, al chutar el comando “composer” por consola, recibirás esta bonita pieza de ASCII-Art y el manual de la herramienta:

2.2- Los comandos de Composer más usuales

De cara a trabajar a diario con Composer, existen unos comandos básicos que conviene tener claros. Pueden ser los comandos de Composer más usuales y cuanto antes nos familiaricemos con ellos, más experiencia podremos seguir acumulando.

require

El pequeño comando elemental para decirle a Composer que tu proyecto requiere tal o cual dependencia.

// Declaramos una dependencia con proveedor/paquete:versión
$
php composer.phar require vendor/paquete:2.*
// Versión para instalación global
$ composer require vendor/paquete
// Sin petición de confirmación
$ composer require -n vendor/paquete
$ composer require --no-interaction vendor/paquete

El comando require activa la búsqueda, descarga e instalación de dependencias. Veamos que ocurre al solicitarle al Composer que instale Drush mediante el comando require:

Y tras el proceso, al abrir el fichero composer.lock, vemos que Drush ha sido instalado y con él, todas sus dependencias (incluye el Drupal Code Generator):

y si consultamos, vemos que ya tenemos disponible la dependencia:

install

El comando install va al encuentro del fichero composer.json que esté disponible en el mismo directorio donde es ejecutado y se lanza a traer dependencias y a ubicarlas (normalmente) en el directorio /vendor

create-project

Este comando es como la suma de ejecutar git clone + composer install: se encarga de clonar un repositorio y luego realizar todo el proceso de instalación de dependencias.

El comando create-project te permite crear un nuevo proyecto mediante Composer y prepararte toda la mandanga.

$ composer create-project doctrine/orm /ruta/hasta/proyecto 2.2.0
comando vendor/paquete ruta de carpeta versión

El primer argumento del comando es vendor/paquete, el segundo argumento es el directorio donde quieres crear el proyecto y también puedes añadir un tercer argumento para indicar la versión que quieres instalar de la dependencia. Si no lo indicas, se utilizará la versión más reciente disponible. Además, si el directorio donde quieres crear el proyecto no existe, se creará automáticamente. Ejemplo:

// Creará la carpeta drupalvendor y descargará ahí 8.2.8
$ composer create-project drupal/drupal drupalvendor 8.2.8
// Descargará la última versión de Drupal disponible
$ composer create-proyect drupal/drupal

show

Este comando sirve para listar lo que tienes instalado y en que versión lo tienes. De todas sus opciones, una de las más interesantes en el día a día es la variante show -lo, que muestra de manera comparada en dos columnas paralelas tus versiones instaladas y las versiones disponibles, por si quieres poner al día algunos de tus depedencias. Una manera rápida de obtener una visual de tus recursos.

diagnose

Este comando es una instrucción para ejecutar una revisión rápida del entorno y comprobar en que estado se encuentran los requisitos básicos y habituales de la instalación de Composer, tras lo que devuelve por pantalla un pequeño informe del resumen de estado, anotando si falta algo o existen problemas con algún recurso:

update

Ok, hace varios apartados explicábamos rápidamente en que consistía el fichero composer.lock. Este fichero registraba la versión específica de una dependencia que el proyecto tenía instalado. Esto en cierta manera marca que, aunque una dependencia publique un nuevo paquete con una versión actualizada, composer.lock se hará el longuis y seguirá con la versión anotada instalada en el proyecto, que queda anclada por este fichero de bloque PERO es posible actualizar a una versión nueva con este comando update, que lanza a Composer a la busca y captura de nuevas versiones (dentro de las restricciones anotadas en el composer.json, claro), las instala y realiza la modificación oportuna del registro de esa versión en el composer.lock del proyecto.

// Actualizará todas las dependencias, update composer.lock
$
composer update
// Actualizará solo una dependencia o set de ellas + composer.lock
$ composer update phpunit/phpunit vendor2/dependencia2
// Admite comodines para actualizar todo lo de un proveedor (vendor)
$ composer update vendor/*

self-update

Composer también puede actualizarse a si mismo buscando una versión más moderna. El comando self-update cambiará el archivo composer.phar y lo pondrá en su versión más reciente. Si lo tienes montando de manera global es probable que tengas que usar sudo para lanzar la actualización.

$ composer self-update

search

El comando search search busca el paquete indicado en los repositorios de paquetes que estén declarados en el proyecto. Normalmente sale a buscar la palabra clave introducida por el nombre y la descripción del paquete:

$ composer search monolog

Podemos acotar la búsqueda al nombre del paquete, dejando a un lado las descripciones de los mismos para afinar mejor usando el parámetro

--only-name

Y si además, lanzamos la consulta (como ocurre con otros comandos en realidad xD) desde un directorio donde no existe un fichero de referencia composer.json, Composer localizará y nos preguntará si queremos usar el más cercano:

No me queda composer.json en este directorio, ninio (AKA Composer que bonico es)

remove

El anti-require por si quieres eliminar una dependencia del proyecto o bien te has equivocado al lanzar un require. Válido para limpiar y como require-undo:

$ composer remove drupal/paragraphs

clearcache

Ocurre de vez en cuando que tras realizar pruebas e instalaciones, al ejecutar:

$ composer require -n vendor/package:version 

Empezamos a encontrar errores de instalación. Para curarnos en salud, procedemos a limpiar la caché interna de Composer lanzando la instrucción:

$ composer clearcache

2.3- Ejemplo de uso con PHPUnit

He querido añadir un ejemplito de uso Drupal-non-related para completar un poco estos primeros apartados conceptuales, para darle algo más de sustento y facilitar el que se realicen ejercicios en tu máquina con Composer. He elegido una secuencia de hace poco, instalando PHPUnit+ (un framework para realizar test de código en PHP) en una máquina. Me ha parecido una forma didáctica de jugar un poco con Composer.

No pasa nada si rompemos algo: Hemos venido a este programa a jugar.

1- Creamos directorio del proyecto

$ cd /var/www/html/
$ mkdir phptesting
$ cd phptesting

2- Creamos el fichero de composer para el proyecto:

$ vim composer.json

3- Creamos un fichero composer.json

{     
"require": {
"phpunit/phpunit": "7.1.5"
}
}

4- Instalamos dependencias

$ composer install

5- ¿Qué hace Composer?

Como ya hemos dicho, sale a Packagist.org+ a buscar la dependencia, la encuentra en la URL https://packagist.org/packages/phpunit/phpunit+ y se la trae a la carpeta destino. Por cierto, en origen, un paquete en Packagist puede ser un vínculo a un repo de Github, como en este caso de PHPUnit. Esto implica que podemos vincular un repositorio nuestro a Packagist para proveer algún recurso como dependencia por parte de Composer.

De hecho la cruda realidad es que Packagist no en sí mismo un almacén de paquetes, es solo una especie de “proxy” que registra, filtra y conecta los verdaderos repositorios del software que necesitamos. Como se ve en la imagen de arriba, en el caso de PHPUnit en realidad Packagist solo está manejando los metadatos asociados a partir de un recurso externo alojado en Github.

Pero como hemos dicho que íbamos a jugar, ¿Qué tiene esto de divertido? vamos a retorcer un poco a Composer. ¿Y si le mentimos un poco? (bueno o nos equivocando declarando una dependencia)…De hecho si le marcamos una versión INVENT:

El pobre Composer se quedará un poco majara y con mucha cortesía, nos aclarará que nuestros requisitos no pueden ser resueltos, pero que tenemos opciones:

Igualmente, también tiene la cortesía de informarnos a la hora de encontrarse con problemas de compatibilidad en versiones de PHP:

Y una vez seleccionada una versión compatible, lanzando composer update:

Todo bien, instalado y actualizado en nuestro directorio de pruebas. Ahora a testear (pero eso ya es otra película). Volvamos al hilo argumental que nos ocupa.

3- Tips para usar Composer

Vamos a dar un paseo por algunas anotaciones del día a día que quisiera trasladar en forma de “consejos” para trabajar con Composer. Repasaremos principalmente como realizar anotaciones respecto a las versiones de las dependencias, también como hacer las cosas un poco más ligeras con Composer y por último, veremos un pequeño conector de Composer con Drupal.

3.1- Especificando versiones

Una de las claves funcionales de la mecánica de trabajo con Composer es el hecho de poder indicarle que dependencia queremos, de que proveedor lo necesitamos y en versión lo pedimos (la terna elemental del uso de un buen gestor de dependencias). Ok. Pero en el caso de Composer podemos darle un poco más de juego a la hora de establecer las versiones que solicitamos. Veamos unos ejemplos:

Versiones explícitas

Puedes pedirle a Composer una versión exacta de una dependencia. Esto pedirá que se instale esta y solo esta versión, lo cual puede ser un poco arriesgado: Si otra dependencia que haya por ahí necesitase una versión distinta del mismo recurso a instalar en el mismo directorio (/vendor), el “resolvedor” (solver) dará un casque y detendrá todo el proceso de actualización o instalación.

// Solicita una versión exacta de la dependencia
"require": {
"vendor/paquete": "1.4.3", // Instala exactamente la 1.4.3
}

Por esto de la posibilidad de conflictos, no es muy común en proyectos de cierta dimensión ver peticiones de versiones exactas. Lo habitual suele ser trabajar con rangos y solicitudes más abiertas:

Versiones con comodines

Podemos establecer búsquedas usando comodines (wildcards) en el formato:

// Solicita entre un rango seleccionable de 2.7
"require": {
"vendor/dependencia": "2.7.*", // Instala versiones >=2.7.0 <2.8.0
}

Versiones marcadas por un commit específico

Es arriesgado y se recomienda cambiarlo cuanto antes se pueda a un tag de lanzamiento (en cuanto esté disponible), pero es posible con Composer solicitar una dependencia que hemos probado, sabemos que funciona y que a la vez se encuentra en un desarrollo continuo en el momento de solicitarla. Pero en el momento en el que se encuentra a nivel de repositorio nos viene genial, así que la queremos instalar hasta un commit específico enviado al repositorio de dicha dependencia. Con Composer es posible añadiendo el hash de un commit específico.

Imaginemos que queremos PHPUnit versión 6.5 y dentro de esta tras un commit que hemos comprobado que nos chuta bien en el proyecto:

// Solicita la versión de la rama 6.5 en el momento de ese commit
"require": {
"phpunit/phpunit": "6.5#30218e30c0763cebbd626ba22770493b400cc8db"
}

Versiones con rango ( con más y menos severidad)

En Composer pueden especificarse rangos de versiones útiles para la descarga, evitando romper las dependencias del proyecto con versiones no compatibles y sobre todo, facilitando las posibles actualizaciones que vayamos acometiendo. Es posible usar operadores para establecer rangos, del tipo: >=1.0 <1.1 || >=1.2 aunque lo más seguro es usar los caracteres alternativos para expresar rangos. Estos están vinculados al uso de versionado semántico de las dependencias (formatos x.y.z sabiendo que z son parches aplicados e issues menores, y cambios importantes que mantienen compatibilidad y x es una nueva versión completa que incluye incompatibilidades hacia atrás).

Para comprender bien esta cuestión, hay que tener claro que el versionado semántico representa mediante x.y.z una estructura mayor.minor.patch, donde en un mundo ideal (8) no habría trastornos de compatibilidad hasta llegar a una nueva versión de tipo mayor. Bien.

Veamos algunos ejemplos.

// El operador ~ marca un intervalo de versiones donde anotar ~8.1 // sería equivalente a declarar que queremos una versión que sea
// >= 8.1 y a la vez <9.0. Esto permite evolucionar el dígito final
// de la versión menor sin entrar a incompatibilidades
"require": {
"drush/drush": "~8.1" // Admite hasta 8.1.15, no admitiría 8.2
}
"require": {
phpunit/phpunit": "~6.5.5" // Admite hasta 6.5.7 (no hay 6.5.8)
}
// El operador ^ también sirve para especificar rangos de versiones
// con la diferencia de que permite que los números de versión se
// incrementen sin importar su posición hasta que lleguen a una
// posible incompatibilidad por cambio de la versión mayor.
"require": {
"drupal/console": "^1.0.2" // Admitiría todas hasta la futura 2.0.0
}
"require": {
"drush/drush": "^8.1" // Admite todas hasta 9.0
}
// Ejemplo INVENT para ver la diferencia entre operadores "vendor/paquete": "~4.3.2", // Admite >=4.3.2 < 4.4.0
"vendor/paquete": "^4.3.2", // Admite >=4.3.2 < 5.0.0

3.2- Haciendo las cosas más ligeras

Una de las formas con la que podemos hacer más ágil el rendimiento de nuestro proyecto basado en PHP es manteniendo y mimando todo lo relacionado con las recomendaciones de estilo propias de la comunidad, esto es, el set de especificaciones creadas por la comunidad y recomendadas como buenas prácticas: PSR-0+, PSR-1+, PSR-2+, PSR-4+

En concreto, dentro de la PSR-4, hay un apartado para el autoloader+…¿Qué es el autoloader? básicamente una función que busca las clases PHP de manera recursiva, las agrupa y las ofrece al resto del proyecto para que estén disponibles para su uso. En este momento de estandarización del código, cada clase PHP es guardada por separado en un fichero de extensión .php donde nombre de clase y nombre de fichero coinciden. En tiempos anteriores, andábamos poniendo includes o requires al inicio de muchos ficheros para poder usar sus recursos y funciones pre-determinadas ¿verdad?. Ahora es todo mucho más sencillo, ágil y mantenible(si cumplimos las reglas).

Seguramente al trabajar con ficheros PHP te será familiar el concepto de namespace, es decir, el contexto en el que se define una clase PHP en particular para a) no colisionar con otra clase que pueda llamarse igual y b) facilitar la inclusión de recursos y dependencias.

Ejemplo de mapeo en una clase PHP usada como FieldWidget en Drupal 8

Si utilizas namespaces en tus proyectos PHP probablemente ya sabrás que están completamente separados de la estructura de ficheros, ya que son conceptualmente diferentes. Los namespaces no tienen nada que ver con las carpetas, son dos cosas distintas. Para vincular ambos conceptos, dicho estándar, el PSR-4, permite enlazar los namespaces con directorios de carpetas del sistema de archivos de nuestras máquinas, y ese mapeo es fundamental para establecer el autoloading, es decir, la carga dinámica de clases para poder usarlas desde cualquier otro sitio dentro de nuestro proyecto.

El siguiente paso es añadir una clave autoload al archivo composer.json utilizando la convención PSR-4:

"autoload": {
"psr-4": {"Acme\\": "src/"}
}

Y por último ejecutamos este comando, que hará la magia por nosotros:

$ composer dump-autoload --optimize

El comando dump-autoload actualiza la información del cargador automático de clases. Este comando es útil cuando añades nuevas clases y no quieres ejecutar el comando install o update y sirve para regenerar el cargador de clases ubicado en vendor/autoload.php Pero además, es todavía más útil usado con la opción --optimize que obliga a generar un mapa de clases. El mapa de clases es una manera de mejorar el rendimiento de nuestro proyecto, ya que en aplicaciones de cierto tamaño localizar las clases mediante el cargador resulta pesado y tedioso para los procesos del programa. El mapa de clases lo realiza de manera mucho más directa. En ese array asociativo, cada clase queda vinculada a su fichero .php de referencia.

Estructura interna de ficheros dentro de /vendor/composer en un proyecto
Vista interna del array asociativo para mapeo de clases en el fichero autoload_psr4.php

En algunos sitios hablan de mejoras de entre el 20 y el 25% del rendimiento al realizar la optimización a través del mapa de clases. Cuidadín.

3.3- Cambiando el repositorio de Paquetes a Drupal.org

Este sub-apartado tenía que servir para establecer un puente con el siguiente capítulo y empezar a relacionar ya a Composer con Drupal y Drush, que es la terna original del presente artículo, así que dentro del uso de Composer vamos a ver conectores con Drupal. Se me ha ocurrido ir desbrozando el camino con una pequeña y rápida aproximación.

Lo primero será reconocer que en Packagist no están registrados todos los módulos disponibles para Drupal, con lo cual para ir más allá del core de Drupal e instalar los recursos que necesitamos en nuestro directorio /drupal/modules/contrib (lo que viene siendo el set de módulos creados por terceros que usamos en nuestros proyectos) debemos cambiar el repositorio por defecto que pueda tener el fichero composer.json. Con este fin, desde Drupal.org se ofrecen un par de repositorios de paquetes en https://packages.drupal.org/8+ o https://packages.drupal.org/7+ para conectar vía Composer (si intentas acceder vía web serás redireccionado a drupal.org), por lo que habría que modificar el fichero y sustituir la dirección del repositorio que necesitamos, bien Drupal 7 o Drupal 8:

Para realizar esta modificación en tu composer.json puedes lanzar por consola la siguiente instrucción y Composer se encargará de modificar el json:

$ composer config repositories.drupal composer https://packages.drupal.org/8

4- Gestionando proyectos Drupal con Composer

Una vez atravesados los capítulos anteriores donde Drupal ha estado haciendo pequeños cameos, ahora toca ir relacionando todo lo visto anteriormente entre sí y darle un sentido estructurado al trabajo con Composer en el contexto de un proyecto Drupal. Repasaremos los aspectos más importantes.

4.1- Mecánica de trabajo

A continuación veremos algunas ideas útiles para establecer rutinas básicas dentro de nuestro trabajo diario con la terna Drupal — Drush — Composer.

Instalando Drupal a través de Composer

Existen muchas maneras de instalar Drupal en una máquina (y cada vez más) pero ahora nos centraremos en hacerlo mediante Composer, con la idea de fijar una mecánica básica de trabajo a modo de pequeño workflow que cualquier persona interesada en el topic pueda seguir con comodidad.

Básicamente tienes tres opciones para instalar Drupal a través de Composer. Asumiendo que ya tenemos Composer instalado en nuestra máquina y como ya indicamos en un apartado anterior, lanzamos por consola la instrucción:

  1. Plan A) Usar drupal-composer como proveedor (vendor) y drupal-project como recurso base, ya que trae algunos recursos incluidos que en el resto de casos se tienen que instalar aparte (como Drush, por ejemplo, o el caso del Plugin de Composer para manejar parches a la manera de dependencias): https://github.com/drupal-composer/drupal-project+
$ composer create-project drupal-composer/drupal-project:8.x-dev directoriodestino --stability dev --no-interaction

2. Plan B) Usar el proyecto drupal-init creado por @hussainweb+ https://github.com/hussainweb/drupal-composer-init+

$ composer global require hussainweb/drupal-composer-init:~1.0 

3. Plan C) Usar drupal/drupal desde Packagist.org: https://packagist.org/packages/drupal/drupal+

$ composer create-project drupal/drupal directoriodestino 8.5

Como puede verse en la siguiente tabla comparativa extraida de la documentación de Drupal.org, cada opción tiene sus propias características:

Tabla comparativa de las funcionalidades de cada opción para instalar Drupal mediante Composer

Y aquí están las claves a la hora de decidirse: con el uso de drupal/drupal tendrás todo el contenido en el root del proyecto en lugar de generarlo dentro de una carpeta web (con lo que tendremos que hacer alguna redirección desde Apache para solicitar nuestro Drupal), y a cambio Drush y Drupal Console tienen que ser dependencias incluidas aparte.

En mi opinión y en base al día a día, mi orden de preferencia en cuanto a plantillas de composer.json no coincide con la secuencia de la tabla anterior: drupal-project me resulta la opción más optimizada y la de hussainweb la última en orden de preferencia, quiero decir: A-C-B. La opción A es una plantilla Composer para proyectos Drupal creada inicialmente por el usuario webflo en 2015 y contribuida a día de hoy por muchas más personas de la comunidad Drupal.

Seguimiento del proyecto desde control de versiones

Inicialmente, dejar fuera de seguimiento de Git los recursos del core y los módulos contrib. Estos se gestionarán desde Composer, así que nuestro proyecto solo tendrá seguimiento por parte de Git respecto a la estructura inicial y aquello que ocurra en el directorio /contrib, donde irá todo nuestro trabajo en el día a día a partir de la triada Composer — Drush — Drupal.

En el caso del control de versiones (git), como en cualquier proyecto tenemos un fichero de seguimiento .git y un fichero de exclusiones llamado .gitignore, en el que se declaran todas las excepciones a seguir cuando se registren cambios en el proyecto. Bien.

Editando el fichero .gitignore del proyecto veremos la exclusiones anotadas

# Ignore directories generated by Composer
/drush/contrib/
/vendor/
/web/core/
/web/modules/contrib/
/web/themes/contrib/
/web/profiles/contrib/
/web/libraries/

¿Qué quiere decir esto? como existen una serie de elementos que ya están registrados en Composer como dependencias y serán elementos fijos o “anclados” del proyecto, ya no será necesario moverlos de un lado a otro a través de control de versiones. No se realizará seguimiento sobre ellos.

A partir de ahora, nuestra única preocupación será mantener el composer.json debidamente actualizado y subido al repositorio. En base a eso, cualquier usuario realizará una copia local en su máquina y tras lanzar composer install o composer update tendrá el resto de dependencias ancladas del proyecto ya resueltas e instaladas.

Se acabó subir cosas innecesarias al repositorio.

Aplicación de parches en el proyecto

Es bastante normal que mientras trabajamos desarrollando en un proyecto, descubramos que un módulo tiene un funcionamiento anómalo para el que hay que aplicar un parche específico a la espera que se integre (o no) a una versión posterior. Es lo que normalmente solemos hacer usando git apply en en el directorio donde se encuentra el módulo justo tras traernos dicho fichero de texto con cambios que es en sí un parche software. Lo hacemos en local, ¿Así que el resto de nuestro equipo de trabajo debería hacerlo también? ¿y de manera particular? ¡No! controlamos la aplicación de parches manejándolos como dependencias y dándoles registro en el composer.json del proyecto.

Para ello contamos con la extensión de Composer “composer-patches” que:
- O bien forma parte ya al elegir opción A) drupal-composer/drupal-project.
- O bien se instala en el resto de casos como una nueva dependencia mediante

$ composer require cweagans/composer-patches

Composer nos permite tratar los parches como dependencias a resolver y realizar las implantaciones de esto. Para ello usaremos la estructura de la parte “extra” del composer.json

"extra": {       "installer-paths": {
"web/core": ["type:drupal-core"],
"web/libraries/{$name}": ["type:drupal-library"],
"web/modules/contrib/{$name}": ["type:drupal-module"],
"web/profiles/contrib/{$name}": ["type:drupal-profile"],
"web/themes/contrib/{$name}": ["type:drupal-theme"],
"drush/contrib/{$name}": ["type:drupal-drush"]
},
"patches": { "drupal/core": {
"Para poder mostrar campos de usuario": "https://www.drupal.org/files/issues/user-access.2773645-62.patch",
"Para que funcione el ajax en views": "https://www.drupal.org/files/issues/views_exposed_form_ajax-2842525-23.patch"
}
}

Y en el siguiente lanzamiento de composer update, se ejecutaran las instalaciones de los parches anotados en el composer.json.

“Composerizando” un proyecto Drupal

Puede ocurrir que partiendo de un proyecto que fue instalado para pruebas en un entorno local, se hayan realizado sucesivos cambios y quiera establecerse este Drupal como base de trabajo.

Imagínemos que hemos instalado un Drupal simplemente a la manera clásica: descargando un comprimido, descomprimiendo y corriendo el instalador vía el navegador web. Ok. Con el paso del tiempo lo necesitamos, lo hemos usado para proyectos y va bien, o simplemente le hemos cogido cariño y queremos hacerlo persistir un poco en esa versión y con esas configuraciones específicas. ¿Puede “Composerizarse” un proyecto Drupal a posteriori? la respuesta es sí, con la herramienta composerize-drupal: https://github.com/grasmash/composerize-drupal+

Instalación:

// Lanzamos una orden a nivel global
$ composer global require grasmash/composerize-drupal
// Nos cambiamos a la carpeta del proyecto
$ cd path/to/drupal/project/
// Lanzamos la orden de "composerizar" el proyecto con los
// parámetros necesarios: donde está el .git del proyecto y donde
// está el Drupal (suele ser dentro de /web según la opción de
// instalación.
$ composer composerize-drupal --composer-root=[repo-root] --drupal-root=[drupal-root]

Esta instrucción composerize-drupal se encargará de construir un composer.json coherente con el estado actual del proyecto a través de diversas acciones que van desde cepillarse otros .json y .lock que existan por ahí dentro, crear un .json nuevo y darse un paseo por todo la estructura de directorios /modules/* , /themes/* con la idea de poblar todo el require con los datos de nuestra instalación Drupal, mergeando todas las dependencias que se vaya encontrando por el camino.

4.2- Instalación de Drush vía Composer

Si es la primera vez que tomas contacto con Drush y has llegado aquí por que necesitas instalarlo para un proyecto pero nunca has trabajado con él, lo primero que tienes que saber es que Drush fue considerado durante mucho tiempo como “la navaja suiza de Drupal” por ser una herramienta fundamental para trabajar en proyectos de esta plataforma.

Drupal + Consola = Drush

Con el tiempo su utilidad ha quedado algo diluida por la aparición de otras herramientas como el mismo Composer, la Drupal Console o el Drupal Code Generator. Aunque seguimos usándolo fielmente en el día a día para instrucciones que no tienen alternativa. Drush sigue siendo la conjunción de “Drupal + Shell”, el primer recurso para usar Drupal desde la línea de comandos. Aquí tienes su sitio web+ y en esta otra dirección puedes encontrar la documentación de sus comandos+.

En base a lo que venimos tratando, ya debemos intuir que instalar Drush en un proyecto Drupal a través de Composer debe ser bastante sencillo. De hecho solo sería necesario si hemos elegido el plan C de instalar Drupal mediante la opción drupal/drupal que no lo trae por defecto (En el resto de casos ya estaría dentro de la carpeta /vendor).

Si hemos elegido drupal/drupal como opción de instalación para Composer, lanzamos la instrucción para resolver Drush como nueva dependencia:

$ composer require drush/drush

Importante: para evitar posibles errores, es _muy_ recomendable registrar nuevas dependencias mediante composer require y no editando y/o manipulando directamente el composer.json a mano.

Y ya tendremos Drush instalado en nuestro proyecto. Ahora bien, ¿Cómo accedemos a él a través de sus comandos? ¿Cómo lo usaremos en nuestro proyecto? A partir de aquí se abren varias opciones. Veremos las dos tácticas más útiles.

Una parte importante del trabajo con Drush es la de tenerlo accesible desde cualquier sitio en el que estemos trabajando y si se trata de una instalación local para un proyecto específico que requiera una versión concreta, entonces que Drush responda usando esa versión específica que está instalada como dependencia (como hemos visto en el párrafo anterior).

Táctica 1: Drush mediante alias de consola

La primera opción con la que podemos trabajar es la de crear un alias de consola para que apunte siempre al ejecutable de Drush que queramos usar en una instrucción.

Al fin y al cabo, cuando “instalamos” Drush estamos manejando unos binarios ejecutables, así que crear un alías de consola para usar en instrucciones sigue el esquema clásico de declarar el alías y dónde apuntará este para ejecutar algo. Por ejemplo, si hemos llevado Drush a la ruta /usr/local/bin/ marcamos un alias que apunte a ella. Para ellos podemos usar el fichero .bashrc del sistema o uno más específico como el .bash_aliases (que tendríamos que crear). En cualquier caso, hay que introducir ese alias en el .bashrc editando con una de las siguientes instrucciones:

$ vim ~/.bash_aliases
$ vim ~/.bashrc

En el caso de abrir con tu editor de texto phavorito el fichero (En este caso el .bashrc), podrás ver diversos alias a lo largo del fichero:

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# Incorpora el nuevo alias para Drushalias drush8='/usr/local/lib/drush/drush'

¿Qué nos permite este enfoque? podemos tener tantas versiones de Drush como necesitemos para cada uno de los proyectos que usemos, siempre disponibles para usar. Para ello necesitamos tres pasos bien definidos:

  1. Descargar los recursos y ubicarlos en sus destinos.
  2. Crear los alias necesarios.
  3. Testear los alias.

Pensemos en instalar en nuestro sistema Drush en versiones 6, 7, 8 y 9.

1- Creamos directorios ad-hoc en el /home del user del sistema y descargamos los recursos necesarios vía composer:

$ mkdir ~/drush6
$ cd ~/drush6
$ composer require "drush/drush:6.*"
$ mkdir ~/drush7
$ cd ~/drush7
$ composer require "drush/drush:7.*"
$ mkdir ~/drush8
$ cd ~/drush8
$ composer require "drush/drush:8.*"
$ mkdir ~/drush9
$ cd ~/drush9
$ composer require "drush/drush:9.*"

Y como curiosidad, irás viendo el crescendo progresivo de las dependencias a instalar junto a Drush:

composer require "drush/drush:6.*" -> 1 install
composer require "drush/drush:7.*" -> 8 installs
composer require "drush/drush:8.*" -> 20 installs
composer require "drush/drush:9.*" -> 36 installs

2- Creamos alias de consola: para esto necesitamos acceder al fichero .bashrc en modo edición para añadir nuestras nuevas instrucciones. Este fichero está disponible en el /home de cada user de nuestro sistema.

## Abrimos .bashrc y pasamos a modo edición del vim (Insert)
$
vim ~/.bashrc
## Incluimos nuestros nuevos alias al final del fichero
alias drush6='~/drush6/vendor/bin/drush'
alias drush7='~/drush7/vendor/bin/drush'
alias drush8='~/drush8/vendor/bin/drush'
alias drush9='~/drush9/vendor/bin/drush'
:wq!
## Recargamos el fichero bashrc para que incluya los nuevos alias
$
source ~/.bashrc

3- Probamos si tanto los alias están apuntando bien a los recursos como si estos responden adecuadamente:

$ drush6 --version
$ drush7 --version
$ drush8 --version
$ drush9 --version

Y si todo ha ido bien, obtendremos la información sobre versiones como feedback en consola:

Drush 6, 7, 8 y 9 instalados en un entorno local y disponibles vía alias de comandos

Táctica 2: Drush mediante Drush Launcher

Si en lugar de establecer un Drush global de manera centralizada para todo el sistema (o varios) preferimos tener cada uno localizado como una dependencia específica en el /vendor de cada uno de nuestros Drupales, entonces toma fuerza la opción de usar Drush Launcher en nuestro proyecto:

https://github.com/drush-ops/drush-launcher+

Se considera una buena práctica -de cara a evitar problemas de dependencias y versiones- el hecho de tener un Drush para cada proyecto de manera compartimentada. Para eso lo instalamos mediante Composer como una dependencia más del proyecto, que lo ubica en vendor/bin/drush para ser ejecutado mediante instrucciones, ok. Pero a partir de ahí, para acceder mediante comandos a las funciones de Drush necesitamos estar constantemente indicando que la ruta es esa en cada comando.

Esto lo evitamos realizando una instalación de Drush Launcher de manera global para todo nuestro sistema. El Launcher se encargará de ir a buscar los ejecutables de Drush según el contexto de cada proyecto desde el que sea lanzado y responderá con el Drush existente en cada directorio vendor que vaya encontrando.

// Descargamos Drush Launcher en local (Ubuntu o Debian-based)
$ wget -O drush.phar https://github.com/drush-ops/drush-launcher/releases/download/0.6.0/drush.phar
// Damos permisos de ejecución al .phar (php ejecutable)
$
chmod +x drush.phar
// Movemos el .phar a un directorio en /bin creado ad-hoc para drush
$
sudo mv drush.phar /usr/local/bin/drush
// Podemos pedirle una auto-actualización al .phar de Drush Launcher
$
drush self-update

Y ahora además de tener Drush instalado en nuestro proyecto, podremos convocarlo de manera sencilla.

Confirmamos el uso directo de Drush vía Launcher con drush version :

4.3- Uso combinado de Drush y Composer con Drupal 8

Cuando ya tengamos Drush y Composer conviviendo en el mismo proyecto tendremos que aprender a usar cada cual en su sentido específico, compartimentando funcionalidades que tal vez antes estuviesen en el campo de responsabilidades del otro. Tomemos por ejemplo el caso del comando de Drush “en” (enable, para habilitar módulos ya descargados o bien si no lo están, descargarlos previamente y luego habilitarlos).

A partir de ahora, si lanzamos por ejemplo:

$ drush8 en metatag

Obtendremos un mensaje muy claro por pantalla: El proyecto ha sido ensamblado por Composer y solo será él quien se encargue de resolver dependencias:

Así que aunque nos ofrezca la posibilidad de usar pm -force como opción, lo mejor será que a partir de ahora usemos composer require para módulos.

Por último, vamos a realizar un ejercicio final combinando todas las acciones anteriores y todo el contexto que hemos ido recorriendo desde el principio del artículo. Esta secuencia está realizada en un entorno LAMP basado en Ubuntu o sistemas operativos Debian-based:

$ composer create-project drupal/drupal drupalpruebas 8.5
$ cd drupalpruebas
$ composer require drush/drush
$ drush version
$ drush site-install

Esta sucesión de comandos creará una instalación de Drupal en tu carpeta destino totalmente funcional, con un perfil de administrador ya generado con las credenciales que devuelve el drush site-install al final de su ejecución (user-admin) y el modelo de datos de Drupal ya instalado en BBD. Por lo general, un Drupal ya funcional instalado y listo en tres comandos (o en menos si hemos elegido otra opción para Composer que no sea drupal/drupal).

Un Drupal funcionando en tu máquina con tres instrucciones.

$ composer create-project drupal/drupal drupalpruebas 8.5
$ composer require drush/drush
$ drush site-install

y si usas las opciones de instalación alternativas a drupal/drupal, será suficiente con:

$ composer create-project drupal-composer/drupal-project:8.x-dev directoriodestino
$ drush site-install

No está mal.

5- Enlaces, referencias y lecturas de interés

Como complemento, incluyo algunas referencias que considero de utilidad para ampliar más conocimiento sobre algunos de los aspectos tratados en este artículo.

5.1- Sobre Composer y otras experiencias cruzadas

5.2- Sobre Packagist como repositorio privado

5.3- Otros

--

--

@davidjguru
Drupal & /me

I’m @davidjguru this is my off-broadway channel, speaking about Drupal like in https://www.therussianlullaby.com or https://davidjguru.github.io but in spanish.