Blog

Composer: come “facciamo a pezzi” un progetto Magento

Lettura 11 minuti

In Bitbull, quando partiamo con lo sviluppo di un nuovo progetto, per prima cosa ragioniamo su come “fare a pezzi” il progetto stesso, ovvero identifichiamo i componenti software che è necessario integrare per dare vita all’applicazione.

Il vantaggio principale che offre questo approccio è il riutilizzo di codice già scritto, facilmente reperibile da repository pubblici, testato e manutenuto dalla community di sviluppatori, oppure da repository privati. La conseguenza è una riduzione dei tempi di sviluppo e una sana predisposizione a pacchettizzare e rendere disponibili anche i propri moduli, seguendo uno standard condiviso. Inoltre risulta agevole l’aggiornamento dei singoli componenti a nuove versioni.

Nel mondo PHP il concetto di “aggregazione di componenti di codice riutilizzabili e disponibili su repository” non è recente: in passato era molto in voga l’utilizzo di PEAR che è un sistema di distribuzione di librerie PHP open-source, raccolte in un repository ufficiale quasi considerato un framework.

La principale differenza pratica tra PEAR e Composer è che il primo viene configurato globalmente sulla macchina in cui è installato e quindi le stesse librerie sono disponibili per tutti i progetti (con evidenti esplosioni di euforia degli sviluppatori che si trovano a gestire i conflitti di versione tra progetti diversi), mentre il secondo prevede sia delle configurazioni globali a livello di macchina, sia configurazioni locali di progetto.

Inoltre PEAR è decisamento obsoleto, come indicato già due anni fa da Fabien Potencier nel suo blog.

Scegliamo un tool in base alla flessibilità e alla propensione al rinnovamento e quindi, ad oggi, ci focalizziamo su Composer!

Cosa è Composer?

Composer è un gestore di dipendenze per progetti PHP: consente di dichiarare la lista delle dipendenze del progetto e le regole con cui si occuperà di scaricare e installare/aggiornare per noi questi pacchetti software all’interno del progetto.

Composer ha un repository ufficiale delle dipendenze, Packagist (https://packagist.org/), e un meccanismo nativo di gestione dell’autoload.

Questo il link al sito ufficiale del progetto: https://getcomposer.org/

Come installare Composer?

Per poter utilizzare Composer dobbiamo procedere all’installazione; normalmente installiamo il tool a livello globale della macchina, posizionandolo in una cartella (ad es. usr/local/bin) specificata nella variabile d’ambiente $PATH, in modo che l’eseguibile (assicuratevi che lo sia dando i permessi necessari) sia disponibile ovunque.

Facendo riferimento all’ultima versione disponibile ad oggi e all’ipotesi di posizionare l’eseguibile Composer nella cartella ‘usr/local/bin’, in ambiente Unix (Linux, Mac OSX) sono sufficienti questi comandi:

$ sudo curl https://getcomposer.org/download/1.2.0/composer.phar -o /usr/local/bin/composer
$ sudo chmod +x /usr/local/bin/composer

Come funziona Composer?

L’approccio alla scomposizione del progetto in dipendenze, con Composer, si concretizza con due principali attività:

  • dichiarazione delle dipendenze, nel file composer.json
  • installazione delle dipendenze eseguendo un semplice comando Composer

Tutto parte dal file composer.json che creiamo nella cartella di progetto e in cui elenchiamo i repository dove andare a recuperare i pacchetti e la lista di tutte le dipendenze, oltre ad alcune informazioni generali ed eventuali configurazioni avanzate sul progetto stesso. In questo file possiamo anche dichiarare degli script da eseguire al termine dell’installazione delle dipendenze.

Possiamo creare questo file manualmente oppure tramite il comando:

$ composer init

Per un approfondimento sullo schema del file composer.json fare riferimento a questo link, per la lista completa dei comandi messi a disposizione dal tool fare riferimento a questo link.

Per installare le dipendenze occorre posizionarsi nella cartella in cui è presente il composer.json e lanciare da terminale il comando:

$ composer install

Questo comando scarica tutti i pacchetti dichiarati, li salva in una cartella chiamata “vendor” e crea un file autoload.php sotto la cartella vendor. Questo file è l’unico che necessita essere incluso negli script del progetto PHP.

Al termine della prima installazione delle dipendenze Composer crea il file composer.lock in cui vengono dichiarate tutte le dipendenze con indicazione della versione scaricata e installata.

Questo file può essere distribuito agli sviluppatori che vogliono replicare il progetto: sarà per loro sufficiente eseguire un composer install per assicurarsi di utilizzare gli stessi pacchetti e versioni dichiarati nel composer.lock.

Successivamente, in caso di modifica del file composer.json o di necessità di aggiornamento delle dipendenze dichiarate, andrà eseguito da terminale il comando

$ composer update

Questo comando scarica e installa le dipendenze che richiedono aggiornamento, rimuovendo la vecchia versione, e aggiorna il file composer.lock da distriibuire.

Come utilizziamo Composer con un progetto Magento?

Ma veniamo al sodo e vediamo come fare a pezzi un progetto Magento per poi lanciare un comando e trovarci magicamente di fronte un’applicazione funzionante (in realtà non è necessario alcun potere magico).

Parlerò dell’utilizzo di Composer con Magento 1, perchè Magento 2 ufficialmente supporta e incoraggia l’installazione tramite Composer.

Nel caso di un tipico progetto Magento, i “pezzi” a cui mi riferisco sono, ad esempio:

  • core di Magento
  • moduli della community
  • moduli Bitbull
  • moduli sviluppati ad hoc per esigenze legate al cliente/progetto
  • tema grafico
  • script per operazioni di deploy

Come vedremo, il nostro progetto diventa a tutti gli effetti un meta-progetto in quanto non conterrà codice PHP ma solo la definizione delle dipendenze di codice necessarie per il suo funzionamento.

Ecco il composer.json di esempio per un ipotetico progetto “magento_composer_example”:

{
 "name": "bitbull/magento_composer_example",
 "description": "Magento Composer Example Project",
 "minimum-stability": "dev",
 "authors": [
   {"name": "Bitbull", "email": "devel@bitbull.it"}
 ],
 "repositories":[
     {"type": "composer", "url": "http://packages.firegento.com"},
     {"type": "composer", "url": "http://packages.bitbull.it"},
     {"type": "vcs", "url": "git@github.com:bitbull-team/mageploy.git"},
     {"type": "vcs", "url": "git@bitbucket.org:bitbull/magento-translations-it.git"},
     {"type": "vcs", "url": "git@bitbucket.org:bitbull/custom-example-modules.git"},
     {"type": "vcs", "url": "git@bitbucket.org:bitbull/custom-example-theme.git"},
     {"type": "vcs", "url": "git@bitbucket.org:bitbull/magento-deploy.git"},
     {"type": "vcs", "url": "https://github.com/netz98/n98-magerun.git"},
     {"type": "package",
      "package": {
        "type": "magento-module",
        "name": "Acme_Module",
        "version": "1.7.9",
        "dist": {
          "url": "https://bitbucket.org/bitbull/acme_module/get/v1.7.9.zip",
          "type": "zip"
        }
      }
    }
 ],
 "require":{
   "magento-hackathon/magento-composer-installer": "3.0.6",
   "bitbull/magento-core": "1.9.2.4",
   "bitbull/magento-cookienotice": "1.0.0",
   "bitbull/magento-translations-it": "1.0.0",
   "firegento/psr0autoloader": "dev-master",
   "bitbull/composer-magento-example-modules" : "dev-master",
   "bitbull/composer-magento-example-theme" : "dev-master",
   "inchoo/php7": "1.0.3",
   "bitbull/magento-deploy": "1.0.0",
   "n98/magerun": "1.97.21"
 },
 "require-dev": {
   "aleron75/webgriffe_tph-pro": "dev-master",
   "pug-more/mageploy": "1.2.0"
 },
 "config": {
   "secure-http": false
 },
 "extra":{
   "magento-root-dir": ".",
   "magento-deploystrategy": "symlink",
   "magento-deploystrategy-overwrite": {
       "bitbull/magento-core": "copy"
   },
   "magento-force": true,
   "auto-append-gitignore": true
 },
 "autoload": {
   "psr-0": {
     "Bitbull": "lib/"
   }
 },
 "scripts": {
   "post-update-cmd": [
     "Bitbull\\Deploy\\Magento::generateLocalXml",
     "Bitbull\\Deploy\\Magento::setMaintenanceMode",
     "Bitbull\\Deploy\\Magento::flushCache",
     "Bitbull\\Deploy\\Magento::runUpgradeScripts",
     "Bitbull\\Deploy\\Magento::reindexAll",
     "Bitbull\\Deploy\\Magento::unsetMaintenanceMode"
   ],
   "post-install-cmd": [
     "Bitbull\\Deploy\\Magento::generateLocalXml",
     "Bitbull\\Deploy\\Magento::setMaintenanceMode",
     "Bitbull\\Deploy\\Magento::flushCache",
     "Bitbull\\Deploy\\Magento::runUpgradeScripts",
     "Bitbull\\Deploy\\Magento::reindexAll",
     "Bitbull\\Deploy\\Magento::unsetMaintenanceMode"
   ]
 }
}

Vediamo nello specifico il significato delle principali direttive inserite nell’esempio; per la documentazione ufficiale e completa fate riferimento a questo link:

  • name: nome del progetto
  • description: descrizione sommaria del progetto
  • minimum-stability: il livello di stability minimo richiesto per la ricerca dei pacchetti nei repository. Le opzioni sono: dev, alpha, beta, RC, stable.
  • authors: lista degli autori del progetto
  • repositories: lista dei repository personalizzati, aggiuntivi rispetto a Packagist, in cui Composer dovrà andare a cercare i pacchetti richiesti, soddisfando la minimum stability. Per ogni repository vanno specificati type e url. Nel mio esempio sono presenti tre diverse tipologie di repository, quelle che utilizziamo più spesso:
    • composer: fa riferimento ad un repository privato di pacchetti (tipo Packagist,  ma privato). Si può ad esempio utilizzare Satis per creare questo tipo di repository (vedi dettagli).
    • vcs: fa riferimento ad un repository su un server di controllo versione, ad esempio git o svn
    • package: si utilizza per recuperare pacchetti che non offrono supporto per Composer, non sono quindi provvisti al loro interno di un file composer.json. Le direttive per l’installazione/aggiornamento con Composer vengono specificate nella dichiarazione del package.
  • require: lista delle dipendenze. Per ogni dipendenza va specificato il package name e la versione richiesta
  • require-dev: lista delle dipendenze per l’ambiente di sviluppo. Tramite l’opzione –require-dev del comando di installazione dipendenze di Composer è possibile escludere queste dipendenze, normalmente in ambiente di produzione.
  • extra: possibilità di specificare una lista di configurazioni extra; nell’esempio vengono specificate le direttive usate dalla libreria che consente l’installazione di Magento come dipendenza, magento-hackaton/magento-composer-installer.
  • autoload: possibilità di specificare la mappatura tra namespace e path del pacchetto per la risoluzione dell’autoloading. Composer supporta gli standard PSR-4, PSR-0, classmap e file.
  • scripts: lista degli script da eseguire al verificarsi di un particolare evento del processo di installazione delle dipendenze.

La libreria magento-hackaton/magento-composer-installer

Questa libreria è la prima che includiamo in tutti i nostri progetti Magento poiché è studiata appositamente per installare moduli Magento tramite Composer, risolvendo alcune problematiche di integrazione.

Essa è in grado di riconoscere e processare solo le dipendenze che rappresentano moduli Magento; nel file composer.json delle singole dipendenze va impostata la tipologia di pacchetto specificando “magento-module”:

{
    "name": "bitbull/magento-translations-it",
    "type": "magento-module",
}

Per noi il core Magento, il tema, tutti i moduli e la libreria che usiamo per le operazioni di deploy sono di tipo “magento-module”.

Per quanto riguarda il core di Magento, una soluzione alternativa è l’utilizzo del plugin Magento Core Composer Installer, che nasce proprio per gestire il core di Magento come dipendenza installata con Composer, semplificando il mapping e ottimizzando la gestione del .gitignore.

Di default, in fase di installazione delle dipendenze, Composer scarica nella cartella vendor i file delle dipendenze; magento-composer-install replica questi file all’interno della root Magento in modo da creare la struttura di files e cartelle tipica di una installazione Magento standard.

Il metodo con cui rende disponibili i file dipende dalle configurazioni magento-deploystrategy e magento-deploystrategy-overwrite.

{
    "extra":{
        "magento-root-dir": ".",
        "magento-deploystrategy": “symlink”,
        "magento-deploystrategy-overwrite":  {
                    "bitbull/magento-core": "copy"
         },
         "magento-force": true
     }
}

Nel nostro caso indichiamo di deployare i moduli magento come link simbolici all’interno della struttura delle cartelle Magento, ad eccezione del pacchetto relativo al core di Magento che viene copiato.

Normalmente utilizziamo Modman per la specifica della mappatura della posizione dei files da replicare (per approfondimenti su modman leggi questo tutorial). Ulteriori opzioni sono la mappatura esplicita all’interno del composer.json oppure indicare, sempre nel composer.json tra le configurazioni “extra”, il file package.xml del modulo compilato secondo lo standard utilizzato da Magento Connect.

Questa strategia di deploy ha un comodo risvolto pratico.

Per il tema grafico e per i moduli con le personalizzazioni di progetto normalmente lavoriamo su due repository git separati (customer-project-theme e customer-project-modules) e, durante la fase di sviluppo, referenziamo le dipendenze facendo riferimento al branch master (dev-master).

In questo modo, sotto la vendor, abbiamo a tutti gli effetti due repository git su cui effettuare aggiornamenti, commit, ecc… e con la strategia di deploy “symlink” qualsiasi modifica ai pacchetti viene replicata in tempo reale sotto il path della root Magento in modo da poter lavorare senza necessità di effettuare un’operazione di composer update.

Inoltre, per chi come noi utilizza repository git, questa libreria consente di aggiungere in automatico al file .gitignore i riferimenti a tutti i file dei pacchetti integrati nel progetto come moduli magento (riconosciuti grazie alla configurazione della proprietà type).

{
    "extra":{
        "magento-root-dir": ".",
        "auto-append-gitignore": true
    }
}

Questa integrazione con git è molto comoda poiché consente di avere nel repository git solo i files composer.json, composer.lock, .gitignore.

Come ulteriore feature la libreria consente di automatizzare la gestione dell’autoloading delle dipendenze, tramite l’applicazione di una patch al core Magento (file app/Mage.php) che risolve l’inclusione del file di autoloading creato da Composer sotto la cartella vendor.

Questo comportamento, attivo di default, può essere disabilitato specificando la seguente configurazione:

{
    "extra":{
        "with-bootstrap-patch": "false"
    }
}

Creazione di pacchetti

Quando creiamo una nuova estensione Magento, sviluppando ad esempio un modulo o il tema del cliente, ci adoperiamo per fare in modo che sia installabile come dipendenza tramite Composer.

Per fare questo è sufficiente includere un file composer.json che identifica il pacchetto, specificandone il nome (composto da vendor-name e package-name) ed eventuali ulteriori dipendenze.

Qui sotto ad esempio il file composer.json del modulo di traduzioni Magento di Bitbull.

{
    "name": "bitbull/magento-translations-it",
    "description": "Magento core and transactional e-mails translations in Italian",
    "license":"MIT",
    "type": "magento-module",
    "authors":[
        {
            "name":"Lorena Ramonda",
            "email":"lorena.ramonda@bitbull.it"
        }
    ],
    "require": {
        "magento-hackathon/magento-composer-installer": "*"
    }
}

Come possiamo notare abbiamo dovuto specificare la proprietà name (dove “bitbull” è la vendor name) e la dipendenza con magento-hackaton/magento-composer-installer. Non è stato necessario specificare alcun repository in quanto l’unica dipendenza da soddisfare è recuperabile dal repository Composer ufficiale, Packagist.

Per indicazioni sulla creazione di librerie fare riferimento a questo link.

Versioning

Tutte le funzionalità che potrebbero essere messe a fattor comune su diversi progetti vengono soddisfatte con moduli esistenti (community, Bitbull, altri fornitori) , distribuiti come pacchetti singoli, e quindi indipendenti dal progetto su cui è nata l’esigenza di integrazione.

Tutti questi moduli vengono referenziati nel composer.json facendo riferimento, per la versione, ad uno specifico tag.

I moduli custom del progetto (tema e personalizzazioni), come detto, vengono installati facendo riferimento al branch master dei relativi repository per poter essere aggiornati velocemente in fase di sviluppo.

Una volta pronti per il go-live creiamo una release, sia per questi moduli personalizzati sia per il meta-progetto, aggiungendo un tag alla versione corrente e specificandola nel file composer.json del pacchetto.

Scopo finale è avere, nel composer.json del meta-progetto, tutte le dipendenze con riferimento a tag specifici. In questo modo in caso di aggiornamento delle dipendenze non si rischia di integrare nell’applicazione versioni di codice non ancora testate o magari non compatibili.

Per una panoramica completa sulla specifica delle versioni il riferimento è questo link.

Conclusioni

Usare Composer non è complesso, bisogna solo iniziare, e ragionare sempre in ottica di riutilizzo del codice, di pacchettizzazione dei propri sviluppi, di versioning e di flessibilità perché, come abbiamo visto, possiamo veramente integrare tutto il necessario, dal setup al deploy di un progetto.

Inoltre in un unico file abbiamo la visione di tutto quello che è installato nel progetto, una sorta di carta di identità e possiamo automatizzare le operazioni di aggiunta e rimozione di estensioni, riducendo gli errori dovuti inevitabilmente all’intervento manuale.

Nadia Sala