PHP 5.3 : namespace : les espaces de noms (partie 2)

25 novembre 2008namespace, php, php-5.3
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

Les exemples correspondant à ce point se trouvent dans le répertoire "namespace".

Voici la seconde partie de cet article traitant des namespaces, une des grosses nouveautés de PHP 5.3 -- la première partie ayant été publiée hier, lundi 24 novembre 2008.

Nous avons vu hier comment déclarer des espaces de noms, et les utiliser pour regrouper des fonctions, des classes, et des constantes... Voici venu le moment de passer à la suite :

Sommaire de cette seconde partie :


Espaces de noms et autoload

Que ce soit pour éviter d'écrire de multiples require/include ou pour des raisons de performances, nous utilisons souvent, depuis PHP 5, une fonctionnalité nommée << autoload >>, pour charger automatiquement un fichier PHP contenant une définition de classe au moment où celle-ci devient utilisée par notre application.

Les avantages sont, principalement, les suivants :

  • Vous avez, bien entendu, moins d'inclusions à écrire,
  • Vous avez moins de cas particuliers à tester : à partir du moment où votre autoloading se fait correctement et que vous utilisez des classes existantes, vous limitez les risques d'inclusions multiples, ou d'oublis d'inclusions,
  • Et, enfin, seul le code nécessaire est inclus ; PHP a donc moins de code à interpréter -- ce qui ne peut être que positif pour les performances de votre application, et la charge serveur qu'elle entrainera.

L'autoloading se fait en déclarant une fonction qui reçoit en paramètre le nom de la classe à charger, et qui est responsable de l'inclusion du fichier contenant son code source.

Deux possibilités :

  • Soit cette fonction est nommée __autoload, et elle sera automatiquement considérée comme étant la fonction de chargement automatique,
  • Soit vous utilisez spl_autoload_register (PHP >= 5.1.2) pour définir une ou plusieurs fonctions ou méthodes d'autoloading, qui s'empileront les unes au-dessus des autres.

Imaginons par exemple l'arborescence de fichiers suivante :

namespace-autoload-arborescence-fichiers.png

Nous enregistrerons comme méthode d'autoload la méthode statique Bootstrap::autoload, définie comme ceci :

<?php
class Bootstrap
{
    public static function autoload($className)
    {
        echo '<pre>Autoload : ' . $className;
        $tab = explode('\\', $className);
        $path = __DIR__ . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $tab) . '.php';
        echo "\n    =&gt; $path</pre>";
        require $path;
    }
}

Je disais plus haut qu'une fonction d'auto-chargement recevait en paramètre le nom de la classe qu'elle devait tenter de charger...

Et bien, cela ne change pas pour les classes se trouvant dans un espace de noms : en interne, leur nom est toujours << absolu >>, et contient l'intégralité du nom de l'espace de noms au sein duquel elles sont définies.

A partir de là, pour peu que l'arborescence des fichiers soit organisée de la même façon que vous espaces de noms et classes, il n'est pas difficile de déterminer quel fichier sera à inclure pour charger une classe : il suffit globalement d'exploser le nom complètement qualifié (Incluant le nom entier de l'espace de noms) de la classe sur le caractère séparateur de namespaces, l'antislash \ .

C'est précisément ce que fait la méthode définie un peu plus haut ^^


Maintenant, considérons que le fichier principal de notre application contienne le code suivant :

<?php
require_once(__DIR__ . '/libs/autoload.php');
spl_autoload_register(array('Bootstrap', 'autoload'));

$a = new LibA\SubLibA1\A1ClassGlop();
$a->method();

$a = new LibA\SubLibA2\A2ClassGlop();
$a->method();

echo '<hr />';

LibB\BMain::method();

echo '<hr />';

$a = new LibA\SubLibA1\X();
$a->method();

$b = new LibB\X();
$b->method();

Si l'on essaye de découper étapes par étapes, voyons ce qu'il se passera :

Tout d'abord, nous essayons d'instancier la classe A1ClassGlop, de l'espace de noms LibA\SubLibA1.
Cela entrainera l'auto-chargement du fichier libs/LibA/SubLibA1/A1ClassGlop.php[1], dont le contenu est le suivant :

<?php
namespace LibA\SubLibA1;

class A1ClassGlop {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

L'appel à la méthode method entrainera donc l'affichage suivant :

string 'LibA\SubLibA1 \ LibA\SubLibA1\A1ClassGlop \ method' (length=50)

Passons maintenant à l'instanciation de la classe LibA\SubLibA1\A1ClassGlop.
Elle passera par l'auto-chargement du fichier libs/LibA/SubLibA2/A2ClassGlop.php, dont le contenu est reproduit ci-dessous :

<?php
namespace LibA\SubLibA2;

class A2ClassGlop {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Je vous laisse imaginer la suite ;-)
(au besoin, vous trouverez l'exemple complet dans le répertoire namespace/autoload-1/ de l'archive jointe à cet article -- avec tous les autres exemples que j'ai utilisé au cours de cette série !)


En somme, pour résumer, la fonctionnalité d'autoload est toujours là, et elle fonctionne et peut être utilisée exactement de la façon dont nous étions habitués lorsque nous travaillions avec la convention de nommage de classes PEAR ; la différence est que, maintenant, nos classes sont organisées en espaces de noms, et que le séparateur est \ , et plus _.


Type-hinting

Je vois de plus en plus souvent des type-hint utilisés, pour indiquer, directement au sein des prototypes de méthodes, qu'elles attendent en paramètres des objets qui doivent être des instances d'une classe ou de l'une de ses classes filles, ou qui doivent implémenter une interface...

Cette possibilité ne disparait bien évidemment pas en PHP 5.3, et il est, en toute logique, possible d'utiliser des classes << namespacées >> comme indications de types.

Par exemple, considérons le fichier lib-A.php reproduit ci-dessous :

<?php
namespace NS_A;

class ClassA {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Et le fichier lib-B.php dont voici le contenu :

<?php
namespace NS_B;

require_once(__DIR__ . '/lib-A.php');

class ClassB {
    // /!\ Il faut utiliser \NS_A\ClassA et non NS_A\ClassA,
    // puisque nous sommes actuellement dans NS_B !
    public static function method(\NS_A\ClassA $a) {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' :: ' . __FUNCTION__ );
        var_dump($a);
    }
}

Notez l'indication de type, basée sur une classe namespacée, sur le paramètre de ClassB::method !

Ces deux fichiers peuvent être utilisés depuis un fichier principal, ressemblant à ceci :

<?php
require_once(__DIR__ . '/lib-A.php');
require_once(__DIR__ . '/lib-B.php');

$a = new NS_A\ClassA();
$a->method();

NS_B\ClassB::method($a);

Ici, aucun problème : ClassB::method reçoit une instance de \NS_A\ClassA, comme attendue, et nous obtenons l'affichage suivant :

string 'NS_A \ NS_A\ClassA \ method' (length=27)
string 'NS_B \ NS_B\ClassB :: method' (length=28)
object(NS_A\ClassA)[1]

Par contre, si nous avions passé autre chose qu'une instance de \NS_A\ClassA, par exemple comme ceci lors du second appel à ClassB::method :

<?php
require_once(__DIR__ . '/lib-A.php');
require_once(__DIR__ . '/lib-B.php');

NS_B\ClassB::method('a');  // Catchable fatal error: Argument 1 passed to NS_B\ClassB::method() must be an instance of NS_A\ClassA, string given

Nous aurions alors obtenu une erreur de la forme suivante :

namespace-type-hint-1.png

En effet, une chaine de caractères n'est de toute évidence pas une instance de \NS_A\ClassA, ni d'aucune de ses classes filles !

Finalement, la fonctionnalité de type-hinting continue à fonctionner comme en PHP < 5.3, même lorsque nous l'utilisons avec des classes organisées au sein d'espaces de noms -- et, encore une fois, c'est heureux !

Pour plus d'informations sur le Type Hinting en PHP 5, n'hésitez pas à consulter la page de manuel qui lui est consacrée.


Import d'un espace de noms : use

Utiliser partout dans vos sources un nom d'espace de noms potentiellement long n'est pas forcément une bonne solution pour un développeur : plus il y a de code à saisir, plus il y a de code à maintenir, et plus il y a de risques d'erreurs...
Et cela ne résout aucunement le problème des noms de classes habituellement trop longs en PHP.

Pour répondre à ce problème, et à la demande qui va avec, PHP 5.3 introduit un nouveau mot-clef, ou, plutôt, un nouveau couple de mots-clefs : << use ... as ... >>[2].

La façon la plus simple de définir ce mot-clef est certainement de dire qu'il permet de définir un << alias >> pour un espace de noms.

use : création d'un alias d'espace de noms

Pour commencer avec un premier exemple, créons le fichier lib.php, contenant le code source reproduit ci-dessous :

<?php
namespace NAMESPACE_AU_NOM_TRES_LONG;

class ClassA {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Difficile à exploiter, comme nom de namespace, n'est-ce pas ?

Heureusement, à l'aide du mot-clef use, nous allons pouvoir, avant de travailler avec cet espace de nom, lui définir un alias :

<?php
require_once(__DIR__ . '/lib.php');

use NAMESPACE_AU_NOM_TRES_LONG as A;  // Défini "A" comme alias de "NAMESPACE_AU_NOM_TRES_LONG"

$a = new A\ClassA();  // Utilisation de A, et non de "NAMESPACE_AU_NOM_TRES_LONG"
$a->method();

$a = new NAMESPACE_AU_NOM_TRES_LONG\ClassA();  // Utilisation de NAMESPACE_AU_NOM_TRES_LONG : OK !
$a->method();

Et nous obtiendrons bien l'affichage attendu, à savoir :

string 'NAMESPACE_AU_NOM_TRES_LONG \ NAMESPACE_AU_NOM_TRES_LONG\ClassA \ method' (length=71)
string 'NAMESPACE_AU_NOM_TRES_LONG \ NAMESPACE_AU_NOM_TRES_LONG\ClassA \ method' (length=71)

A noter, donc :

  • use permet de définir un alias pour un nom d'espace de noms,
  • Utiliser use ne rend pas inaccessible le nom d'origine de l'espace de nom aliasé,
  • Et, en interne, c'est toujours le nom complètement qualifié de l'espace de noms qui est utilisé -- pas le nom défini par l'alias, donc  !


use : alias d'un espace de noms composé

Passons à un second exemple, où notre espace de noms n'aura plus un nom très long, mais un nom "composé" : définissons un fichier lib.php :

<?php
namespace NS\NOM\COMPOSE;

class ClassA {
    public function method() {
        var_dump(__METHOD__);
    }
}

Et un second fichier sub-lib.php :

<?php
namespace NS\NOM\COMPOSE\SOUS\LIBRAIRIE;

class ClassB {
    public function method() {
        var_dump(__METHOD__);
    }
}

Ces deux espaces de noms sont ensuite utilisés depuis le fichier principal de notre application :

<?php
require_once(__DIR__ . '/lib.php');
require_once(__DIR__ . '/sub-lib.php');

use NS\NOM\COMPOSE as A;  // Défini "A" comme alias de "NS\NOM\COMPOSE"

$a = new A\ClassA();  // Utilisation de A, et non de "NS\NOM\COMPOSE"
$a->method();

$b = new A\SOUS\LIBRAIRIE\ClassB();  // Même chose
$b->method();

Et nous obtenons en sortie l'affichage suivant :

string 'NS\NOM\COMPOSE\ClassA::method' (length=29)
string 'NS\NOM\COMPOSE\SOUS\LIBRAIRIE\ClassB::method' (length=44)

Pas de différence par rapport à ce que nous faisions plus haut : nous continuons à créer des alias sur des noms d'espaces de noms, même s'ils contiennent des \ .

Par contre, notez qu'il n'est pas possible d'utiliser la syntaxe suivante :

<?php
require_once(__DIR__ . '/lib.php');
require_once(__DIR__ . '/sub-lib.php');

use NS\NOM\COMPOSE as A;  // Défini "A" comme alias de "NS\NOM\COMPOSE"
use A\SOUS as B;

$b = new B\LIBRAIRIE\ClassB();  // Fatal error: Class 'A\SOUS\LIBRAIRIE\ClassB' not found
$b->method();

Nous obtiendrons l'erreur suivante :

namespace-alias-not-alias-of-alias.png

Il n'est pas possible de définir un alias lui-même basé sur un alias : PHP n'effectue l'interprétation d'aliases qu'une seule fois !


Définition d'un alias de classe

Jusqu'à présent, nous avons utilisé use pour définir des alias d'espaces de noms...

... Mais on peut aussi utiliser ce mot-clef pour définir directement un alias de classe !

Prenons par exemple le fichier lib.php suivant :

<?php
namespace NS\NOM\COMPOSE;

class ClassA {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Il est alors possible de définir un alias pour la classe ClassA :

<?php
require_once(__DIR__ . '/lib.php');

use NS\NOM\COMPOSE\ClassA as MyClass;  // Défini "MyClass" comme alias de "NS\NOM\COMPOSE\ClassA"

$a = new MyClass();  // Utilisation de MyClass, et non de "NS\NOM\COMPOSE\ClassA"
$a->method();

Et le résultat obtenu lors de l'exécution de ce script sera le suivant :

string 'NS\NOM\COMPOSE \ NS\NOM\COMPOSE\ClassA \ method' (length=47)

Je vous laisse imaginer l'utilité, lorsque vous travaillez très souvent avec une classe donnée, ou lorsque vous devez rendre namespaçable du code qui ne l'était pas...

... Et je tremble déjà à l'idée des difficultés que cela risque d'engendrer en termes de facilité de compréhension et de (re-)prise en main de code existant -- du moins si ce n'est pas utilisé avec sagesse... :-(

Une des difficultés sera de trouver une norme valide pour ces noms d'aliases ; cela viendra avec le temps et la pratique, je suppose...


use sans as

Avant de terminer cette partie consacrée à use et à la définition d'alias, un petit dernier exemple ^^

Lorsque l'on travaille avec des espaces de noms en plusieurs parties, comme ceci :

<?php
namespace NS\NOM\COMPOSE;

class ClassA {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Il est possible d'utiliser le mot-clef use sans utiliser as : le nom de l'alias sera alors automatiquement construit à partir de la dernière composante du nom de l'espace de noms :

<?php
require_once(__DIR__ . '/lib.php');

use NS\NOM\COMPOSE;  // Défini "COMPOSE" comme alias de "NS\NOM\COMPOSE"

$a = new COMPOSE\ClassA();  // Utilisation de COMPOSE, et non de "NS\NOM\COMPOSE"
$a->method();

Qui donnerait la sortie suivante :

string 'NS\NOM\COMPOSE \ NS\NOM\COMPOSE\ClassA \ method' (length=47)

Un alias nommé COMPOSE a automatiquement été créé, à partir de la dernière composante de NS\NOM\COMPOSE.


Par contre, si notre espace de noms ne portait pas un nom << composé >>, cela revient à aliaser sa dernière -- et unique -- composante avec elle-même... Ce qui ne fait absolument rien...

Par exemple, si nous définissons un espace de noms comme suit :

<?php
namespace NAMESPACE_AU_NOM_TRES_LONG;

class ClassA {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Et que nous tentons de l'aliaser sans utiliser as :

<?php
require_once(__DIR__ . '/lib.php');

use NAMESPACE_AU_NOM_TRES_LONG;  //

$a = new NAMESPACE_AU_NOM_TRES_LONG\ClassA();  // Utilisation de NAMESPACE_AU_NOM_TRES_LONG => Warning Warning: The use statement with non-compound name 'NAMESPACE_AU_NOM_TRES_LONG' has no effect
$a->method();

Nous obtenons un avertissement :

namespace-alias-not-alias-of-alias.png

Le code s'exécutera tout de même... Mais notre instruction use sera... useless[3].


Quelques mots sur l'ordre de résolution

Je n'ai pas encore eu suffisament l'occasion de m'amuser avec l'implémentation quasi-finale des espaces de noms en PHP 5.3, qui n'est arrivée qu'il y a quelques jours, mais, en termes d'ordre de résolution, voici ce que j'ai pu noter :

L'ordre de résolution est le suivant :

  1. Chargement depuis l'espace de noms courant,
  2. Pour les classes : appel à la (ou aux) fonction(s) d'autoload, s'il en existe,
  3. Pour les fonctions et les constantes : chargement depuis l'espace de noms global,
  4. Si non trouvé : échec.

Quelques points me semblent tout particulièrement intéressants :

  • L'implémentation cherche en premier lieu dans l'espace de noms courant. Cela garanti que lorsque vous placez du code dans un espace de noms, ce sont les fonctions/constantes/classes définies dans cet espace de noms qui seront appelées par le code en question.
  • Pour les classes, il n'y a pas de chargement par défaut depuis l'espace de noms global : si vous voulez utiliser une classe globale depuis du code situé dans un espace de noms, vous devez précéder le nom de classe par \ .

Pour ce qui est des raisons pour lesquelles il n'y a pas de fallback vers l'espace de noms global pour les classes :

  • PHP fourni beaucoup moins de classes globales que de fonctions globales, et il est probable qu'il en aille de même pour les applications tirant parti de la fonctionnalité d'espaces de noms,
  • Implémenter un fallback sur l'espace de noms global pour les classes entrainerait des problèmes avec le type-hinting, en forçant, dans certains cas, des auto-chargements.

Et pour ce qui est des raisons pour lesquelles un fallback vers l'espace de noms global a été implémenté pour les fonctions :

  • Il y aura théoriquement souvent utilisation de classes, et non de fonctions, au sein d'espaces de noms -- ceux-ci étant une fonctionnalité << objet >>
  • Mais il est certain que les développeurs voudront pouvoir accéder facilement à l'immense quantité de fonctions que PHP défini !

Et pour la source, la plus récente que j'aie pu trouver, qui explique clairement les choses, est un post de Lukas sur internals@ le 5 novembre -- en espérant que l'implémentation finale ne s'éloigne pas trop de ces propositions...


Si vous souhaitez expérimenter un peu par vous-même, n'hésitez pas à jeter un coup d'oeils aux exemples se trouvant dans les répertoires namespace/resolution-1 et namespace/resolution-2 de l'archive jointe à cet article ;-)

(Ils sont probablement un peu long pour être reproduits ici :-( -- mais montrent quelques cas qui pourraient nous pièger !)


Notez que la documentation à propos des namespaces sur php.net n'est aujourd'hui absolument pas à jour... Mais considérant l'importance des espaces de noms pour PHP 5.3, il ne fait aucun doute qu'elle sera actualisée d'ici quelques temps -- jetez un oeil de temps en temps, vous apprendrez sans aucun doute des choses utiles !

Quoi qu'il en soit, il y a toujours du travail en cours à ce niveau... Et la version alpha3 devrait sortir dans les prochains jours, pour nous permettre de tester tout ça !


Faites attention !

Bien évidemment, rien ni nul n'est parfait... L'implémentation des espaces de noms non plus, donc ^^

Le choix de l'antislash comme séparateur de namespaces a probablement suffisament remué les esprits sur la blogosphère pour que je n'en rajoute pas une couche, mais il peut être bon de montrer quelques exemples de situations dans lesquelles il convient de faire attention à ce que vous écrivez, afin de limiter les risques d'erreurs et de bugs...

Premier exemple

Pour notre premier exemple, créons un fichier, lib.php, contenant la définition suivante :

<?php
namespace MyNamespace;

function test() {
    var_dump(__NAMESPACE__ . ' \ ' . 'test()');
}

Une fois ce fichier créé, nous pouvons appeler la fonction qu'il contient en dynamique, de la manière suivante :

<?php
require_once(__DIR__ . '/lib.php');

$nom = 'MyNameSpace\test';
var_dump($nom);
$nom();

Cette syntaxe fonctionnera aussi :

<?php
require_once(__DIR__ . '/lib.php');

$nom = "MyNameSpace\\test";
var_dump($nom);
$nom();

Par contre, cette troisième possibilité ne fonctionnera pas :

<?php
require_once(__DIR__ . '/lib.php');

$nom = "MyNameSpace\test";
var_dump($nom);
$nom();

Et nous obtiendrons l'erreur suivante :

namespace-antislash-caractere-echappement-1.png

L'antislash est le caractère d'échappement dans les chaînes de caractères, et, dans une chaîne délimitée par des doubles-quotes, "\n" est interprété comme une tabulation... Ce qui entraine un nom de fonction n'existant pas !


Second exemple

Pour la route, voici un second exemple, en utilisant un espace de noms dont le nom est composé, et une classe :

<?php
namespace my\ns\de\test;

class MyGreatClass
{
    public function glop()
    {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

Ici encore, la première façon d'appeler cette classe en dynamique est correcte :

<?php
require_once(__DIR__ . '/lib.php');

$nom = 'my\ns\de\test\MyGreatClass';
var_dump($nom);

$a = new $nom();
$a->glop();

La seconde, en échappant les antislash, l'est aussi :

<?php
require_once(__DIR__ . '/lib.php');

$nom = "my\\ns\\de\\test\\MyGreatClass";
var_dump($nom);

$a = new $nom();
$a->glop();

Par contre, encore une fois, la troisième, utilisant une chaîne de caractères délimitée par des doubles-quotes et des antislash non échappés n'est pas correcte :

<?php
require_once(__DIR__ . '/lib.php');

$nom = "my\ns\de\test\MyGreatClass";
var_dump($nom);

$a = new $nom();
$a->glop();

Elle entraine l'erreur suivante :

namespace-antislash-caractere-echappement-2.png

Certains d'entre vous se diront peut-être que le choix de \ comme séparateur de namespaces n'était pas bon... Sans entrer dans le Troll, ce choix présente à la fois des avantages et des inconvénients... Quoi qu'il en soit, c'est l'opérateur qui a été choisi, c'est ainsi et pas autrement : à nous de nous y faire -- et nous nous y ferons, je n'en doute pas !

Accessoirement, pour les curieux, vous pouvez jeter un oeil à la Request for Comments: Namespace Separators, qui donne quelques arguments sur le sujet.


Quelques points qui n'existent pas

Avant de terminer, j'aimerais mettre en évidence une paire de points auxquels on pourrait penser, ou que l'on pourrait trouver potentiellement intéressant, et qui n'existent pas en PHP 5.3 -- du moins pas en date du 22 novembre 2008 (le snapshot que j'ai utilisé pour créer et tester les exemples de cet article datant de ce jour là).

Le séparateur de namespaces est l'antislash ; pas le double-deux-points

Je me répéte par rapport à ce que je disais en tout début d'articles, mais ça ne peut pas faire de mal : le séparateur d'espaces de noms n'est pas le T_PAAMAYIM_NEKUDOTAYIM (le symbole << :: >>), mais l'antislash : << \ >>.

Oui, il a été prévu pendant longtemps que le séparateur utilisé soit << :: >>, ce qui explique qu'il soit utilisé dans grand nombre d'articles, blog-posts, et documentations qui n'ont pas été mis à jour ; mais il a finalement été décidé de retenir << \ >> à la place, entre la sortie de la version alpha2 et de la version alpha3.

Premier exemple

Si vous essayez d'exécuter l'exemple composé des deux portions de code suivantes : lib.php :

<?php
namespace LIB;

function a() {
    var_dump('LIB::a');
}

Et, comme fichier principal de l'application :

<?php
require_once(__DIR__ . '/lib.php');

LIB::a();

Vous obtiendrez une erreur :

namespace-separateur-not-paamayim_nekudotayim-1.png

En effet, puisque l'opérateur << :: >> est utilisé pour séparer les noms de classes de leurs méthodes, PHP croit que vous souhaitez appeler la méthode statique a de la classe LIB, qui n'existe pas.

Second exemple

Et pour un second exemple, considérant le fichier lib.php suivant :

<?php
namespace Mon::Espace::De::Noms;

function a() {
    var_dump('Mon::Espace::De::Noms\a');
}

Qui sera appelé par le fichier principal de notre application :

<?php
require_once(__DIR__ . '/lib.php');

Mon::Espace::De::Noms::a();

Ce qui se traduira par l'erreur suivante :

namespace-separateur-not-paamayim_nekudotayim-2.png

Ici, sans même aller jusqu'à la tentative d'appel de la fonction, c'est déjà la déclaration de l'espace de noms qui entrainera l'erreur !


Pas d'espace de noms entre accolades

L'idée de placer le contenu défini au sein d'un espace de noms entre accolades a souvent été évoqué, parfois comme solution permettant dans le futur l'implémentation de namespaces imbriquées...

... Mais cette idée n'a pas été retenue pour PHP 5.3 : si vous essayez d'exécuter le code d'un fichier construit de cette manière :

<?php
namespace LIB
{
    function a() {
        var_dump('LIB\a');
    }
    
    function b() {
        var_dump('LIB\b');
    }
}

Eventuellement appelé par celui-ci :

<?php
require_once(__DIR__ . '/lib.php');

LIB\a();
LIB\b();

Vous obtiendrez (encore une fois ^^ ) une erreur :

namespace-not-bracketed-1.png

Ce code ne << compile >> même pas !


Pas de use dynamique

Une troisième (pour terminer ? ) ?

Supposons que l'on ait déclaré un espace de noms, par exemple comme ceci :

<?php
namespace NS\NOM\COMPOSE;

class ClassA {
    public function method() {
        var_dump(__NAMESPACE__ . ' \ ' . __CLASS__ . ' \ ' . __FUNCTION__);
    }
}

On pourrait éventuellement être tenté -- je ne sais pas trop pourquoi, mais j'avais envie d'essayer :-D -- de vouloir utiliser un namespace dont le nom ne soit pas connu à l'écriture du script[4], un peu comme ceci :

<?php
require_once(__DIR__ . '/lib.php');

$namespace = 'NS\NOM\COMPOSE';

use $namespace as A;  // Parse error: syntax error, unexpected T_VARIABLE, expecting T_STRING or T_NS_SEPARATOR

Et bien, oubliez : ce n'est pas possible, et se traduira par une erreur :

namespace-not-use-dynamic.png

Tant pis ^^


Conlusion

Grand sujet de débats au sein de la communauté ces derniers mois, l'implémentation des namespaces en PHP 5.3 peut ne pas paraître parfaite... Mais elle est ainsi faite, et, quoi qu'il en soit, elle semble répondre aux objectifs qui lui avaient été fixés.

Maintenant, une seule chose nous manque : la pratique !

Ce n'est que dans quelques mois, lorsque nous aurons réellement pris l'habitude de travailler avec les espaces de noms, que nous serons à même de mettre en place des séries de bonnes pratiques, ainsi que d'identifier plus clairement certains dangers qui risquent de nuire à la maintenabilité du code de nos applications...

En attendant, l'utilisation des espaces de noms est-elle prévue ? Oui, au moins pour quelques projets !
En particulier, je pense aux Frameworks suivants :

  • Zend Framework utilisera peut-être des espaces de noms en version 2.0 -- sortie pour quelque chose comme mi-2009 ?
  • Même chose pour le Framework ORM Doctrine, aussi pour sa version 2.0
  • Et qu'en sera-t-il pour vos projet ? Peut-être pour vous simplifier les choses sur l'inclusion de certains composants externes ?


Pour ceux d'entre-vous qui seraient curieux, quelques exemples correspondant à l'ancienne implémentation des espaces de noms, jusqu'à la version alpha2 inclue, sont disponibles dans le répertoire "NOT-namespace-deuxdeux-points" de l'archive jointe -- les exemples correspondant à l'implémentation finalement retenue étant disponibles dans le répertoire "namespace".


Notes

[1] Je me placerai ici, dans cet article, en relatif par rapport à la racine montrée sur la capture d'écran reproduite plus haut ; mais, en réalité, les chemins sont absolus ; ils sont juste trop longs pour être reproduits en intégralité ; je n'ai donc conservé que la partie significative

[2] Oui, je sais, j'ai déjà présenté use comme << nouveau >> dans mon article sur les fonctions anonymes et les closures ; mais considérant que ce n'est pas le même usage...

[3] Et hop, encore un jeu de mots à deux balles... faut j'arrête :-D

[4] Je vous avais prévenu : je n'ai pas réfléchi à la question du pourquoi je pourrais vouloir faire ça -- j'avais juste envie ^^