Vai al contenuto

Gestire correttamente le dipendenze PHP con Composer

Il primo passo per costruire una pipeline di CD (Continuous Delivery) adeguata è gestire le dipendenze nel modo giusto. Se sei uno sviluppatore di PHP, è necessario padroneggiare Composer riuscire a fare ciò in maniera semplice ed immediata.

Cos’è Composer?

Composer è il gestore delle dipendenze per PHP e Packagist è il repository ufficiale dei pacchetti. Il rilascio di questo progetto nel 2012 ha rivoluzionato il mondo PHP perchè consente di definire tutte le librerie dalle quali l’applicazione dipende e di scaricare automaticamente queste librerie con un semplice comando. Composer risolve molti problemi, sai se la tua applicazione dipende da quali librerie e se queste librerie dipendono da altre librerie… L’uso di Composer e di un sistema di controllo della versione è il primo passo per costruire una pipeline CI/CD per PHP.

E’ richiesta la presenza di PHP sul sistema in cui è eseguito Composer. Si installa tramite un comodo installer che ti avvisa eventuali incompatibilità di versioni PHP. Composer è disponibile per varie piattaforme.

Composer più in dettaglio

Composer utilizza due file per elencare le dipendenze e una directory per archiviare il tutto:

  • composer.json – Questo file elenca le dipendenze del progetto e di solito contiene anche altri metadati.
  • composer.lock – Questo file viene creato e/o aggiornato dopo l’installazione e/o l’aggiornamento delle dipendenze del progetto con l’elenco dei pacchetti e delle versioni installate.
  • vendor/ – La directory predefinita in cui vengono archiviati i pacchetti delle dipendenze (il nome directory può essere cambiato, solo se strettamente necessario).

La cartella vendor dovrebbe essere inserita nel file .gitignore per mantenere il repository del progetto snello e pulito.

Utilizzo di base

La prima cosa che devi specificare sono i pacchetti da cui dipende il tuo progetto e questo fatto creando il file composer.json (è un file di testo) nella directory radice del progetto in questo modo.

{
    "require": {
        "monolog/monolog": "1.2.*"
    }
}

Come puoi vedere, si specifica il nome del pacchetto e la versione all’interno di require. Per inludere altri pacchetti, puoi consultare Packagist per avere i nomi ufficiali da usare.

Il nome di ogni pacchetto è composto dal nome del fornitore e dal nome del progetto. Inoltre è possibile fornire la versione del pacchetto con una certa logica che vedremo nel paragrafo successivo.

Specifiche delle versioni

Nel nostro codice sopra abbiamo voluto essere indulgenti recuperando la versione 1.2.*, ma potrebbero esserci casi in cui vogliamo essere un po’ più restrittivi (specificando ad esempio la versione specifica 1.2.2). Esistono sei modi per specificare la versione desiderata.

Version Range

Usando gli operatori di confronto puoi specificare una versione successiva alla 1.3, inferiore alla 1.8 o seguire un set di regole ancora più complesso usando la logica AND e OR. Gli operatori utilizzati possono essere >, <, >=, <=e !=. Logica AND è rappresentato da uno spazio o una virgola, OR logico è rappresentato dal doppio pipe: ||.

Specificare >2.7 significherebbe qualsiasi versione successiva alla 2.7 mentre  >2.7 <=3.5 consentirebbe qualsiasi versione superiori alla 2.7 e inferiore alla 3.5 (compresa).

Wildcard Versions

Utilizzando un carattere jolly è possibile specificare un modello. 2.3.* comprenderebbe tutto quanto ci sta sotto, cioè 2.3.0, 2.3.1, 2.3.2 etc… ma non 2.4.0. È equivalente a >=2.3.0 <2.4

Hyphen Ranges

Gli intervalli con il trattino consentono di specificare un intervallo più facilmente, sebbene sia un po’ più facile confondersi a causa del modo in cui gestisce le versioni parziali. Una versione completa è composta da tre numeri, nel qual caso gli intervalli di trattini hanno perfettamente senso.

2.0.0 – 3.0.0 è un intervallo inclusivo, il che significa che saranno accettate tutte le versioni tra 2.0.0 (inclusa) e inferiore a 3.0.0 (inclusa).

Versioni parziali come 2.0 – 3.0 indicano qualsiasi versione da 2.0 (inclusa) fino alla versione 3.1 (esclusa).

La ragione di questo comportamento apparentemente strano è che il lato sinistro è inclusivo, il lato destro è completato con un carattere jolly. L’espressione sarebbe equivalente a >=2.1 <3.1.0

Tilde Range

L’operatore ~ (tilde) è ottimo per il targeting di una versione minima richiesta e per consentire qualsiasi cosa fino alla prossima versione principale, ma non includendola. Una specifica del tipo ~3.6 significa che stai autorizzando qualsiasi versione anche superiore a 3.6, ma non 4.0. Questo metodo è equivalente a >-3.6 <4.0

Caret Range

L’operatore ^ (accento circonflesso) si comporta in modo molto simile al tilde ma si avvicina al controllo delle versioni semantico e consentirà sempre aggiornamenti continui. Ad esempio ^1.2.3 è equivalente a >=1.2.3 <2.0.0. Per le versioni precedenti alla 1.0 funziona anche con sicurezza e tratta ^0.3 come >=0.3.0 <0.4.0.

Questo è l’operatore raccomandato per la massima interoperabilità durante la scrittura del codice della libreria.

Dev-Master

Specificando dev-master significa che stai per recuperare sempre l’ultima versione disponibile (ancora in fase dev) che non è stata ancora taggata con un numero di versione stabile. Questo può andare bene durante lo sviluppo, ma dobbiamo essere consapevoli che il numero di bug in queste versioni potrebbe essere maggiore ad una versione stable.

Installare le dipendenze

Per recuperare poi le dipendenze definite, basta eseguire all’interno della stessa cartella dov’è stato definito il file composer.json, il comando install:

composer install

come risultato si avrà una cosa simile:

Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing monolog/monolog (1.2.1)
    Downloading: 100%

Writing lock file
Generating autoload files

Composer scrive le versioni esatte installate nel file composer.lock. Non daremo un’occhiata al file, anche perchè è un file creato automaticamente da Composer.

Diremo invece che il file lock è molto importante. Immagina di lavorare su un’applicazione che dipende da una libreria. Quando esegui Composer per installare queste librerie, viene scaricata l’ultima versione (diciamo 1.1) che viene scritto nel file lock. Se più avanti viene rilasciata, per questa dipendenza, la versione 1.2 e si esegue di nuovo il comando install senza mettere il file lock nella stessa directory, il Composer scaricherà l’ultima versione 1.2 e non la 1.1 ma se esiste un file lock, il Composer ignorerà il composer.json mentre continuerà a considerare il composer.lock. Quindi esegui sempre il commit delle tue applicazioni con composer.lockfile in modo che gli altri sviluppatori del team eseguano le stesse versioni.

Se non desideriamo scrivere il file composer.json, possiamo sempre utilizzare il comando require tramite riga di comando, il quale permette di creare automaticamente i file composer.json e il lock. La sintassi è molto semplice ed è la seguente:

composer require monolog/monolog

Per aggiornare alle versioni più recenti, basta utilizzare il comando update.

composer update

Se è necessario aggiornare una specifica dipendenza, anzichè aggiornare tutte le dipendenze, è possibile eseguire questo comando:

composer update monolog/monolog

Caricamento automatico

Adesso che abbiamo definito e scaricato le dipendenze necessarie al nostro progetto, dobbiamo capire come includere questi file all’intetrno dei nosstri progetti. Niente di più semplice. Composer si occupa, tra le altre cose, anche di generare un file chiamato autoload.php che ci semplifica notevolmente le operazioni di inclusione delle librerie.

Il file autoload.php viene memorizzato all’interno della cartella vendor. Per utilizzare ad esempio le dipendenze di monolog, basta scrivere all’inizio del file php:

require_once __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// create a log channel
$log = new Logger('test');
$log->pushHandler(new StreamHandler('test.log', Logger::WARNING));

// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');

Lo script semplicissimo non fa altro che richiamare le librerie Monolog (con la clausula USE) necessarie a scrivere su un file di log le operazioni. Al termine dello script, verrà prodotto un file di log con le seguenti righe:

[2014-03-09 16:48:53] test.WARNING: Foo [] []
[2014-03-09 16:48:53] test.ERROR: Bar [] []

Dipendenze e Dipendenze DEV

Le dipendenze sono di due tipi:

  • Dipendenze: pacchetti richiesti dal progetto per funzionare. Il progetto non verrà installato a meno che tali requisiti non vengano soddisfatti.
  • Dipendenze dev: pacchetti richiesti per lo sviluppo del progetto o l’esecuzione di test, ecc.

La classificazione è gestita dalle clausule require e require-dev all’interno del composer.json. Usando il comando require di composer, possiamo anche classificare le dipendenze usando o omettendo l’opzione –dev

Ad esempio, il comando:

composer require phpunit/phpunit --dev

non fa altro che scaricare e installare la dipendenza PHPUnit in vendor. Ma siccome abbiamo specificato l’opzione –dev, la dipendenza sarà disponibile solamente in fase di developing (dev) e non di produzione (prod).