PHP: Utiliser l'autoloader de Composer avec Atoum

Chers amis Français, en ce temps de Forum PHP (que j'ai suivi de très loin — outre Atlantique), je suis en train de rédiger une documentation technique (et aussi un peu philosophique) sur l'implémentation de l'intégration continue au sein de ma boîte.

Comme je n'avais encore jamais utilisé Composer, ici l'occasion se présente bien et comme je souhaite apporter une French touch au boulot grâce à Atoum, j'ai décidé qu'on l'utilisera pour tester nos modules Magento :).

Le but de cet article est très simple : utiliser Atoum avec le loader de Composer en prenant compte d'une code source namespacé ou pas.

Composer

Si vous ne connaissez pas Composer, c'est pas grave mais maintenant que je vous en parle, vous aurez plus d'autre choix que de l'utiliser tellement ça envoie du poney ! :lol:

Installation

Supposons que vous ayez cette architecture de projet :

.
├── app
│   └── code
│       ├── core
│       └── local
│           ├── ProjectUnderscore
│           └── Project_NS
├── bin
├── cache
├── logs
├── tests
│   └── units
│       ├── ProjectUnderscore
│       └── Project_NS
└── web

Parmi nos deux projets, l'un utilise les namespaces et l'autre non. Pour installer Composer, rien de plus simple :

curl -s http://getcomposer.org/installer | php -- --install-dir=bin

Importation des dépendances

Ensuite, pour installer vos vendors, il suffira très simplement de créer un fichier composer.json à la racine de votre projet et d'y spécifier les dépendances souhaitées :

{
    "require": {
        "mageekguy/atoum"        : "dev-master",
        "symfony/http-foundation": "dev-master",
        "breerly/zf1"            : "1.11.*"
    }
}
composer.json

Remarque : il est clair que ce fichier JSON devra être commité sinon aucun intérêt...

Ces quelques lignes vont vous créer toute les dépendances dont vous avez besoin et pour cela, une seule commande suffit :

bin/composer.phar install

Remarque : Composer créera automatiquement le dossier vendor donc vous n'avez même plus besoin de vous préoccuper de cela.

Vous pouvez aussi valider votre fichier JSON avec la commande suivante :

bin/composer.phar validate

Sinon la documentation des commandes de Composer vous aidera beaucoup !

Autoloading

D'après notre arbre de projet, notre code sera situé dans app/code/local (convention Magento) donc nous allons inidiqué à Composer de générer les déclarations d'autoloading nécessaires afin d'éviter d'avoir cinquante autoloaders dans notre projet :

{
    "require": {
        "mageekguy/atoum"        : "dev-master",
        "symfony/http-foundation": "dev-master",
        "breerly/zf1"            : "1.11.*"
    },
    "autoload": {
        "psr-0": {
            "Project_NS"       : "app/code/local/",
            "ProjectUnderscore": "app/code/local/"
        }
    }
}
composer.json

Si vous avez ajouté ces déclarations après la première installation ou si vous changez ces paramètres, vous pouvez régénérer l'autoloader avec la commande update :

bin/composer.phar update

Le fichier qui contiendra ces déclaration sera vendor/composer/autoload_namespaces.php et notre autoloader sera vendor/autoload.php.

Code source

Voici le code commun à nos deux projets, seul la déclaration des classes/namespaces changera :

// Code qui sera dans le fichier Application.php de chaque projet
public function getZendVersion()
{
    return \Zend_Version::VERSION;
}

public function sendHttpResponse()
{
    return new Response('Hello world!');
}

Dans le projet namespacé (Project_NS), on créer le fichier Application.php :

<?php
// app/code/local/Project_NS/Application.php
 
namespace Project_NS;
 
use Symfony\Component\HttpFoundation\Response;
 
class Application
{
    // code à tester
}

Et enfin pour le projet sans namespace (utilisation d'underscores), dans un fichier de même nom :

<?php
// app/code/local/ProjectUnderscore/Application.php
 
use Symfony\Component\HttpFoundation\Response;
 
class ProjectUnderscore_Application
{
    // code à tester
}

Notre code est prêt, bon, c'est pas du TDD mais c'est pas important. Le but est de montrer un exemple qui mélange du code mixte (namespace ou non) via Atoum et Composer.

Tests unitaires

Maintenant que nous avons notre code source et Atoum, on va pouvoir tester. On recréer donc les même fichiers dans notre répertoire de tests. Le code commun de nos tests sera le suivant :

public function testGetZendVersion()
{
    $this
        ->string($this->app->getZendVersion())
        ->isEqualTo('1.11.11');
}

public function testSendHttpResponse()
{
    $rep = $this->app->sendHttpResponse();

    $this
        ->object($rep)
        ->isInstanceOf('Symfony\Component\HttpFoundation\Response');

    $this
        ->integer($rep->getStatusCode())
        ->isEqualTo(200);

    $this
        ->string($rep->getContent())
        ->isEqualTo('Hello world!');
}

Pour le projet namespacé, c'est comme expliqué dans l'exemple d'Atoum :

<?php
// tests/units/Project_NS/Application.php
 
namespace Project_NS\tests\units;
 
use Project_NS;
use mageekguy\atoum;
 
require_once __DIR__ . '/../../../vendor/autoload.php';
 
class Application extends atoum\test
{
    public function beforeTestMethod($method)
    {
        $this->app = new Project_NS\Application();
    }
 
    // code commun
}

Note : l'inclusion de l'autoloader de Composer.

Et enfin pour le projet qui utilise des underscores :

<?php
// tests/units/ProjectUnderscore/Application.php
 
namespace tests\units;
 
use mageekguy\atoum;
 
require_once __DIR__ . '/../../../vendor/autoload.php';
 
class ProjectUnderscore_Application extends atoum\test
{
    public function beforeTestMethod($method)
    {
        $this->app = new \ProjectUnderscore_Application();
    }
 
    // code commun
}

Ici, on remarque que le namespace tests\units permettant à Atoum de fonctionner correctement n'est pas préfixé par le namespace du projet.
Et enfin, pour lancer les tests :

vendor/bin/atoum -d tests/units

Pour terminer ce bref article qui sera utile plus comme un « comment commencer à architecturer son projet », depuis des années, ça a toujours été plus ou moins galère de gérer les dépendances de librairies en ayant des autoloaders à droite à gauche sans savoir à quoi ils servent. Aujourd'hui, avec Composer qui gère aussi l'autoloading, tout devient ultra-simple et c'est pas trop tôt car il y en avait marre de passer 3h en début de chaque projet parce que tel fichier ou telle classe ne trouvait pas une autre.
En avant pour la suite, le prochain article portera probablement sur la mise en place d'un système d'intégration continue ;).