Namespaces, Composer et autoloading
PHP nâa pas de systĂšme de modules, mais trois mĂ©canismes plus modestes, require, les espaces de noms et un crochet dâautoloading, que Composer assemble en un gestionnaire de paquets comparable Ă npm, pip, Maven ou Cargo. Vous nâĂ©crirez plus jamais de require pour lâune de vos propres classes, et il reste utile de savoir ce que Composer fait Ă votre place.
Les trois primitives
require 'file.php'; lit et exĂ©cute un fichier, une fois par appel. Câest ainsi que le code se partageait en 2005, et câest encore ainsi que tout sâamorce aujourdâhui, avec un unique require de vendor/autoload.php en tĂȘte de votre point dâentrĂ©e.
Un espace de noms est un prĂ©fixe sur un nom de classe. namespace App\Billing; en tĂȘte dâun fichier fait de chaque classe qui y est dĂ©clarĂ©e une App\Billing\Something, et nâapporte rien dâautre, aucune hiĂ©rarchie, aucune visibilitĂ©, aucun lien avec un dossier. App\Billing nâest pas « dans » App, ce sont simplement deux chaĂźnes qui partagent un prĂ©fixe.
Le crochet dâautoloading est la piĂšce qui rend les deux premiĂšres utiles. Quand PHP rencontre une classe quâil nâa jamais vue, il appelle une fonction que vous avez enregistrĂ©e, en lui passant le nom de la classe, et cette fonction est censĂ©e faire un require du bon fichier, aprĂšs quoi PHP rĂ©essaie.
<?php
declare(strict_types=1);
spl_autoload_register(function (string $class): void {
$file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
if (is_file($file)) {
require $file;
}
});
$invoice = new App\Billing\Invoice(); // loads src/App/Billing/Invoice.php
Le mĂ©canisme se rĂ©sume Ă cette closure, et Composer en Ă©crit une meilleure version, avec un cache, quâil vous remet.
PSR-4 : du nom au chemin
La rĂšgle que suit la closure ci-dessus porte un nom, PSR-4, et Composer lâapplique Ă partir dâun bloc de composer.json :
{
"name": "acme/shop",
"type": "project",
"require": {
"php": "^8.4",
"ext-intl": "*"
},
"require-dev": {
"phpunit/phpunit": "^12.0"
},
"autoload": {
"psr-4": { "App\\": "src/" }
},
"autoload-dev": {
"psr-4": { "App\\Tests\\": "tests/" }
}
}
App\Billing\Invoice vit dans src/Billing/Invoice.php : une classe par fichier, un nom de fichier Ă©gal au nom de la classe, et des dossiers qui reprennent les segments de lâespace de noms aprĂšs le prĂ©fixe. Lâarborescence qui en rĂ©sulte est la mĂȘme sur tous les projets PHP modernes que vous ouvrirez :
shop/
âââ composer.json
âââ composer.lock
âââ public/
â âââ index.php # require __DIR__ . '/../vendor/autoload.php';
âââ src/
â âââ Billing/
â âââ Invoice.php # namespace App\Billing;
âââ tests/
â âââ Billing/
â âââ InvoiceTest.php
âââ vendor/ # generated, not committed
Ajoutez une classe sous src/, et elle est trouvĂ©e Ă la requĂȘte suivante, sans commande Ă lancer, puisque PSR-4 rĂ©sout par chemin au moment de lâappel. La stratĂ©gie plus ancienne classmap parcourt les dossiers pour en faire un tableau gĂ©nĂ©rĂ©, et rĂ©clame un composer dump-autoload aprĂšs chaque nouveau fichier ; vous la rencontrerez dans les projets anciens.
Composer, dans le vocabulaire que vous connaissez
composer.json joue le rÎle de votre package.json, composer.lock celui du fichier de verrouillage, vendor/ celui de node_modules, généré et ignoré par Git, et Packagist celui du registre public.
composer init # interactive composer.json
composer require monolog/monolog # add and install, updates the lock
composer require --dev phpstan/phpstan # development-only dependency
composer install # reproduce exactly what the lock says
composer update # resolve anew, rewrite the lock
composer update monolog/monolog # ... for one package only
composer show # what is installed, with versions
composer outdated # what has a newer release
composer audit # known vulnerabilities in the lock
composer dump-autoload -o # regenerate the autoloader, optimised
install obĂ©it Ă composer.lock, tandis quâupdate le réécrit. En CI et en production vous lancez install, et vous obtenez exactement les versions que votre collĂšgue a testĂ©es. Committez composer.lock pour une application ; pour une bibliothĂšque, la plupart des auteurs ne le font pas, afin quâelle soit testĂ©e avec ce que ses utilisateurs rĂ©solvent, et câest le mĂȘme dĂ©bat que dans tous les autres Ă©cosystĂšmes.
Les contraintes de version suivent semver, et ^8.4 signifie « 8.4 ou toute 8.x ultĂ©rieure ». LâentrĂ©e php de require est une contrainte elle aussi ; avec config.platform.php vous figez la version contre laquelle Composer rĂ©sout, pour quâun dĂ©veloppeur sous PHP 8.5 ne tire pas un paquet que votre production en 8.4 ne peut pas exĂ©cuter.
Les scripts vivent sous une clĂ© scripts et se lancent avec composer run nom ou comme crochets de cycle de vie (post-install-cmd), Ă la maniĂšre des scripts npm. Les extensions ne sont pas des paquets : ext-intl dans require vĂ©rifie seulement que lâextension est prĂ©sente, et lâinstaller est le travail de votre gestionnaire de paquets systĂšme, de PECL ou de PIE. Les outils que vous installeriez globalement ailleurs (linters, analyseurs) vont dans require-dev, pour que chaque dĂ©veloppeur et la CI utilisent la mĂȘme version ; composer global require existe et il vaut mieux le laisser de cĂŽtĂ©. Un monorepo dĂ©clare ses paquets internes comme dĂ©pĂŽts path, et Composer crĂ©e des liens symboliques.
Vivre avec les espaces de noms
Dans un fichier, une instruction use importe un nom pour que vous puissiez lâĂ©crire court. Les alias rĂ©solvent les collisions, et les fonctions comme les constantes peuvent aussi ĂȘtre importĂ©es :
<?php
declare(strict_types=1);
namespace App\Billing;
use App\Customer\Customer;
use DateTimeImmutable as Date;
use function App\Support\money;
use const App\Support\CURRENCY;
final class Invoice
{
public function __construct(
public readonly Customer $customer,
public readonly Date $issuedOn,
) {
}
public function total(): string
{
return money(1999, CURRENCY);
}
}
echo Invoice::class, PHP_EOL; // App\Billing\Invoice
Invoice::class donne le nom pleinement qualifiĂ© sous forme de chaĂźne, ce qui est ce que vous passez Ă un conteneur, Ă un mock ou aux vĂ©rifications instanceof qui prennent une chaĂźne. Un antislash initial, comme dans \DateTimeImmutable, nomme une classe de lâespace de noms global sans passer par use.
Les fonctions se rabattent sur lâespace de noms global, alors que les classes ne le font pas. Appeler strlen() dans App\Billing cherche dâabord App\Billing\strlen, puis strlen, tandis quâappeler new DateTimeImmutable() sans use ni antislash initial Ă©choue. Vous verrez \strlen() dans certaines bibliothĂšques, parce que lâantislash saute la recherche et laisse OPcache inliner quelques fonctions intĂ©grĂ©es ; câest une micro-optimisation, pas une convention que vous ayez Ă adopter.
Les standards Ă connaĂźtre
Le PHP-FIG est le groupe oĂč les auteurs de frameworks et de bibliothĂšques sâaccordent sur des interfaces, publiĂ©es sous forme de PSR. Une PSR est un contrat plutĂŽt quâune bibliothĂšque : les implĂ©mentations viennent de nombreux Ă©diteurs, et vous pouvez en remplacer une par une autre parce que votre code ne voit que lâinterface. Voici celles que vous croiserez dĂšs la premiĂšre semaine :
- PSR-4, lâautoloading, ci-dessus.
- PER Coding Style, successeur de PSR-12 : placement des accolades, indentation, nommage. PHP-CS-Fixer ou PHP_CodeSniffer le font respecter.
- PSR-3,
LoggerInterface. Toutes les bibliothĂšques journalisent Ă travers elle ; vous branchez le logger de votre choix. - PSR-7, PSR-15 et PSR-17 : objets requĂȘte et rĂ©ponse HTTP, middlewares, et leurs fabriques. Les
$_GETetheader()du langage sont traitĂ©s dans Une requĂȘte web, sans framework ; PSR-7 est le modĂšle objet que les bibliothĂšques partagent par-dessus. - PSR-11,
ContainerInterface, pour quâune bibliothĂšque puisse demander un service Ă nâimporte quel conteneur. - PSR-14, la distribution dâĂ©vĂ©nements, PSR-6 et PSR-16, le cache.
Une bibliothĂšque qui type son constructeur contre Psr\Log\LoggerInterface fonctionne dans tous les frameworks citĂ©s dans ce livre. Cette interopĂ©rabilitĂ© explique pourquoi lâĂ©cosystĂšme compte moins de forks et de réécritures que sa taille ne le laisserait penser.
Le piĂšge
Ne modifiez jamais un fichier de vendor/. Le prochain composer install sur nâimporte quelle machine efface la modification, et personne ne saura pourquoi la production diffĂšre de votre portable. Forkez le paquet, ou surchargez la classe par votre propre entrĂ©e dâautoloading, ou envoyez le correctif en amont.
Le second piĂšge est plus discret : vous ajoutez une classe, PHP dit quâelle nâexiste pas, et vous passez vingt minutes Ă chercher une faute de frappe qui nâexiste pas. Comparez lâespace de noms au dossier : App\Billing\Invoice doit ĂȘtre src/Billing/Invoice.php, lettre pour lettre, casse comprise sous Linux. Si le projet utilise classmap plutĂŽt que psr-4, lancez composer dump-autoload et passez Ă autre chose.
Composer nâest pas une Ă©tape de build, puisquâil nây a rien Ă compiler, Ă empaqueter ni Ă transpiler : aprĂšs
composer install, le code sâexĂ©cute tel quel.
Une fois les classes trouvées et les paquets installés, il reste la bibliothÚque standard sur laquelle vous vous appuierez chaque jour, et Chaßnes, nombres, dates et JSON en fait le tour.