close

Les namespaces ou espaces de noms en PHP 8. L'autoloading.

#1 Les namespaces ou espaces de noms : Pour quoi faire ? open

Souvent, lorsque l'on utilise des librairies, que l'on n'a pas développées soi-même, on a des télescopages au niveau des noms. Par télescopage, je veux dire que l'on se retrouve par exemple avec deux classes qui portent le même nom. Ca peut être des noms de classes, des noms de fonctions ou bien des noms de constantes. Et quand je dis classes, eh bien, c'est classes au sens large, c'est-à-dire classes, traits ou interfaces.

Les namespaces apportent une solution à ce problème, en mettant un préfixe devant le nom de la classe, de la fonction ou bien de la constante 😃.

Ensuite qu'est-ce qui s'est passé ? Eh bien, on a décidé d'utiliser ce préfixe pour lui faire porter une information supplémentaire. En fait, on a décidé de choisir ce préfixe de manière à pouvoir retrouver le chemin qui permet d'atteindre l'emplacement de la classe dans notre application. Et du coup, on s'est servi des espaces de noms pour automatiser les require. C'est ce que l'on appelle l'autoloading.

Ci-dessous, on fait un exemple pour illustrer ce que je viens de dire.

  1. Je crée un répertoire Test sur le bureau. Ce sera mon espace de travail VSCode.
  2. Je crée un fichier index.php, je mets dedans un echo de Bonjour.
  3. Je crée maintenant un répertoire App.
  4. Dans App, je crée deux autres répertoires Client et Produit.
  5. Dans chacun de ces deux répertoires, je crée un fichier Afficher.php.
  6. Dans chaque fichier PHP, je crée une classe Afficher.
  7. Maintenant, je retourne dans index.php et je fais un require de chaque classe.
  8. J'exécute et je vois un message d'erreur, car deux classes portent le même nom.
Arborescence de l'exemple
Message d'erreur car deux classes portent le même nom
Message d'erreur si deux classes ont le même nom

Ci-dessous, pour éviter la collision, je vais déclarer l'espace de nom Produit dans le fichier qui contient la classe Afficher et qui est dans le répertoire Produit. Je ne suis absolument pas obligé de prendre le nom du répertoire en tant que nom de l'espace de nom. Mais on verra que ça va nous arranger.

Par symétrie, je vais déclarer l'espace de nom Client dans la classe Afficher qui est dans le répertoire Client. En fait, c'est comme ça qu'on travaille. On met une classe par fichier et on met un espace de nom en haut de ce fichier.

Maintenant, je peux instancier mes classes. Pour ça, je vais mettre l'espace de nom devant le nom de la classe. Ca va donner un new Produit\Afficher et un new Client\Afficher

Alors, lorsque l'on met comme cela, le nom de l'espace de nom devant le nom de la classe, on dit que l'on utilise un FQCN pour Full Qualified Class Name.

Je vais également ajouter un constructeur dans chaque classe Afficher. Dans ce constructeur, je vais mettre un echo de la constante magique __NAMESPACE__. Ceci va me permettre d'afficher, au moment du new, l'espace de nom de chaque classe.

Résultat de l'exécution

#2 L'autoloading open

Maintenant, regardez ! Il ne manque pas grand-chose pour pouvoir reconstituer le chemin nécessaire pour le require en utilisant le FQCN de la classe. Il suffit d'ajouter App devant, puis de changer les backslashes en slashes et enfin d'ajouter .php à la fin.

Alors App, c'est moi qui vais l'ajouter. Je vais faire coïncider les espaces de noms avec les répertoires. Et le reste va être fait par l'appel à la fonction spl_autoload_register(). Je ne rentre pas dans les détails.

Dans ces conditions, à chaque new, un require_once du fichier qui contient la classe est exécuté et la classe est autoloadée. Le chemin du require est retrouvé au moyen du FQCN de la classe. De cette manière, les require ne sont plus faits tous en même temps en haut du fichier.

Résultat de l'exécution

#3 Comment utiliser correctement un espace de nom ? open

  1. Vous devez respecter les règles de nommage de PHP que vous utilisez pour les noms des variables, des fonctions ect.
  2. Par convention, écrivez le nom en UpperCamelCase (* une majuscule à la première lettre en plus du camelCase). Attention, car PHP n'est pas case sensitive sur le nom de l'espace de nom. Il ne vous dira rien si vous faites une erreur.
  3. L'espace de nom doit être déclaré au tout début de votre fichier. La seule instruction que vous pouvez mettre avant est une directive declare. Par exemple, declare(strict_types=1); pour activer le mode strict. Même un session_start(); il faut le mettre après. Même un espace devant la balise d'ouverture <?php ne passera pas.
  4. Vous pouvez utiliser le même espace de nom dans plusieurs fichiers différents. Ca vous permet de mettre plusieurs classes dans le même espace de nom et c'est utile. Ci-dessous, je compète mon exemple avec une nouvelle classe Tester, que je place dans l'espace de nom Produit.
    Nouvelle arborescence du projet
    Résultat de l'exécution
  5. Vous pouvez aussi mettre plusieurs espaces de noms dans le même fichier, PHP ne l'interdit pas, mais en fait, on ne fait jamais ça.
  6. Enfin, il y a une autre façon de mettre en place un espace de nom. Il est possible de mettre le contenu de l'espace de nom entre accolades comme ci-dessous. Je ne m'étendrai pas sur cette méthode, car je l'ai rarement vu utilisée.

#4 L'espace de nom global open

En fait, lorsque vous n'utilisez pas les namespaces, vous vous trouvez quand même dans un espace de nom global. Et si vous ne connaissez pas le principe des namespaces, eh bien, vous ne le savez même pas 🙃.

Ci-dessous, dans le fichier index.php, je vais déclencher une Exception('Mon Erreur'). Je suis dans global bien que je n'ai fait aucune déclaration de namespace. J'exécute et logiquement ça fonctionne correctement, car la classe Exception est distribuée en natif dans global.

Ci-dessous, je vais déclarer un espace de nom Index dans le fichier index.php. Et là, oups, je vois qu'il y a quatre erreurs (* en fait, c'est toujours à-peu-près la même). PHP ne voit plus mes classes Afficher et Tester et il ne voit plus la classe Exception.

  1. La classe Exception, il ne la trouve plus car il la cherche dans l'espace de nom Index et qu'en fait, elle se trouve dans l'espace de nom global. Notez bien que je suis obligé d'ajouter un backslashe pour que PHP aille la chercher dans global.
  2. C'est à peu près pareil pour les classes Afficher et Tester. Etant donné qu'à présent, je suis dans Index et que j'ai écrit mes FQCN en relatif, pour la classe Produit par exemple, PHP cherche dans Index\APP\Produit. Il faut passer les FQCN en absolu. Pour ça il faut ajouter un backslashe devant.

Un FQCN exprimé en relatif est toujours évalué en le préfixant avec le namespace dans lequel il se trouve.

  1. Ci-dessous, nous sommes dans global. Que vous écriviez Client\Afficher ou bien \Client\Afficher ne change rien. (* De toute façon le premier backslashe sera toujours supprimé. Il ne sera pas fourni à l'autoloader car le chemin à utiliser par require doit être relatif).
    Exemple de FQCN dans global
  2. Ci-dessous, Index est ajouté au FQCN relatif.
    Exemple de FQCN dans un espace de nom

#5 Travailler avec des fonctions ou des constantes open

Ci-dessous, PHP va chercher la fonction strlen() qui est dans global sans que j'aie besoin de le préciser. Souvenez-vous, que juste un peu plus haut dans le tuto, il ne l'a pas fait pour la classe Exception !

Ci-dessous, je déclare une fonction strlen() dans Index. PHP va prendre celle-là sauf si je précise \.

Conclusion : Quand il y a un appel à une fonction dans un espace de nom, PHP la cherche dans l'espace de nom courant, sinon il va la chercher là où on le lui dit, par exemple ici, dans global.

#6 Utiliser des use et des alias open

A un moment, utiliser les FQCN va alourdir le code, surtout s'ils sont longs. On va pouvoir éviter ça, en utilisant des use.

Ci-dessous, j'utilise un premier use pour dire à PHP que lorsqu'il doit instancier la classe Tester, eh bien, il s'agit de la classe \App\Produit\Tester.

Maintenant, je mets un use pour les classes Afficher. Mais si je fais ça, tel quel, je me retrouve à nouveau avec deux classes qui portent le même nom. La solution ici, c'est de compléter le use, par un alias. Ici encore, je vais en faire deux par symétrie.

Notez bien que le code des classes ne change pas (* ci-dessous, je ne l'ai même pas inséré car c'est le même). Et c'est bien là l'intérêt. Vous importez des classes, il y a collision, vous faites un alias et vous ne touchez pas au code que vous importez.

Si vous voulez connaître, le FQCN qui est passé à l'autoloader au moment d'un new, alors ajoutez un callback à la fonction spl_auto_register, comme ci-dessous.

Pour plus d'infos, voir les règles de résolutions dans le manuel PHP.