Pourquoi nous avons créé Zana
Zana est notre framework PHP open source pour les applications métier. Voici les problèmes qu’il résout : des modules aux frontières vérifiées, un conteneur qui échoue au démarrage plutôt qu’en production, et une documentation testée.

En swahili, zana signifie « outil ». C’est la mission que nous avons donnée au framework : être un outil qui aide une équipe à construire des logiciels métier. Il ne doit pas être un monde à apprendre avant de pouvoir travailler. Zana est notre framework PHP open source, publié sous licence MIT. Nous l’utilisons pour les projets de nos clients, et le site que vous lisez tourne dessus.
Cet article présente les problèmes que Zana veut résoudre et les choix qui en découlent.
Le problème : un logiciel métier grandit dans tous les sens
La plupart des applications métier commencent petit : quelques écrans, une base de données et un formulaire de contact. Puis elles grandissent. Arrivent la facturation, puis les validations, les rapports, une API mobile et un back-office. Deux ans plus tard, le code qui envoie une facture connaît celui qui valide un bon de commande. Ce code connaît les tables de reporting, et plus personne n’ose modifier une partie sans craindre de casser le reste.
Les frameworks en sont rarement la cause, mais ils aident rarement. Ils rangent le code par couche technique : tous les contrôleurs ici, tous les modèles là. Les frontières métier qui comptent, comme la facturation, les patients ou le stock, sont laissées à la discipline. La discipline ne survit pas aux délais.
Zana inverse cette logique.
Les modules d’abord
Une application Zana est une liste de modules. Chacun est un dossier qui possède entièrement une capacité métier : son domaine, ses cas d’usage, son stockage et ses écrans.
// config/modules.php
return [
Modules\Patients\PatientsModule::class,
Modules\Bmi\BmiModule::class,
];
Un module déclare ses dépendances :
final class BmiModule extends Module
{
public function name(): string { return 'Bmi'; }
public function dependsOn(): array { return [PatientsModule::class]; }
public function register(ServiceCollectionInterface $services): void
{
$services->scoped(MeasurementRepository::class, PdoMeasurementRepository::class);
}
}
Les modules sont triés pour que les dépendances passent en premier. Une dépendance inconnue ou un cycle arrête l’application au démarrage. Les frontières sont aussi vérifiées, pas seulement recommandées. Un seul test applique les règles de dépendance de Zana à tous les modules, y compris ceux que vous ajouterez l’an prochain :
public function testEveryModuleFollowsTheDependencyRules(): void
{
$this->assertModuleArchitecture(ModuleRules::for(__DIR__ . '/../modules'));
}
Les règles sont simples. Le domaine ne dépend que des briques DDD de Zana. La présentation n’accède jamais au domaine ni à la base de données. Un module n’en utilise un autre qu’à travers ses Contracts publics. Si une règle est enfreinte, le test nomme la classe et l’import en cause.
Échouer au démarrage, pas en production
La deuxième idée concerne le moment où les erreurs apparaissent.
Dans beaucoup d’applications PHP, une erreur de câblage n’apparaît que lorsqu’une page a besoin du service défaillant. Ce peut être une dépendance manquante, ou un service à longue durée de vie qui garde une ressource prévue pour une seule requête. Si cette page est le rapport mensuel, vous le découvrez à la fin du mois.
Le conteneur de Zana vérifie toute l’application au moment de sa construction :
$services = new ServiceCollection();
$services->singleton(LoggerInterface::class, FileLogger::class);
$services->scoped(UnitOfWork::class);
$services->transient(CalculateBmiHandler::class);
$container = $services->build(); // tout est validé, puis la collection est figée
Chaque constructeur doit pouvoir être satisfait. Un singleton qui dépend d’un service scoped, même indirectement, est refusé avant la première requête. Chaque erreur indique le module d’où vient l’enregistrement, pour savoir où chercher.
La même idée s’applique au déploiement. En production, l’application refuse de démarrer si le mode debug est actif, si le debug est actif sur une adresse publique, ou si un secret est encore une valeur d’exemple. Vous pouvez ajouter vos propres contrôles. Un mauvais déploiement échoue bruyamment dans le terminal, jamais discrètement devant vos clients.
Rapide parce que compilé
Strict ne veut pas dire lent. En production, Zana compile le conteneur en PHP simple et met la configuration en cache. Le conteneur compilé résout 100 services 3,3 fois plus vite qu’à l’exécution. Un aller-retour HTTP coûte environ 70 µs dans une application démarrée. Votre temps va à votre propre code et à votre base de données, là où il doit aller.
Conçu pour la logique métier
Zana fournit les briques que les applications métier réinventent sans cesse : agrégats et entités, objets-valeurs immuables, identifiants typés, événements de domaine, et un type Result pour les échecs prévus. Il propose aussi un médiateur qui valide les commandes avant qu’elles n’atteignent leur handler, et des unités de travail qui enregistrent une modification d’un seul bloc.
final readonly class PatientId extends Identity {}
final class Patient extends AggregateRoot
{
public static function register(PatientId $id, string $name): self
{
$patient = new self($id);
$patient->recordEvent(new PatientRegistered($name));
return $patient;
}
}
Rien de tout cela n’est obligatoire. Un petit site peut l’ignorer complètement. Mais quand un projet grandit et porte de vraies règles métier, les outils sont déjà là, avec les mêmes conventions que le reste du framework.
Des standards, et seulement ce dont vous avez besoin
Zana utilise les interfaces PSR partout : conteneur, messages HTTP, journalisation, événements et cache. Votre code et vos bibliothèques restent portables. Zana est aussi découpé en paquets, pour n’installer que ce que vous utilisez. Une API JSON n’embarque pas de templates, et un site web n’embarque pas de bus de messages. Chaque paquet est testé avec PHPUnit et analysé avec PHPStan au niveau 8.
Une documentation fiable
Une documentation périmée est pire que pas de documentation, parce qu’on la croit. Chaque exemple de code de la documentation de Zana est un fichier exécutable, vérifié par des tests contre le résultat annoncé. Quand un comportement change, la documentation change aussi, sinon le build échoue.
Pour qui est Zana ?
Zana convient aux équipes qui construisent des applications appelées à vivre des années et à grandir : outils internes, portails, plateformes, back-offices et API. Il est tout aussi à l’aise pour faire tourner un site multilingue avec back-office, comme celui-ci.
Pour l’essayer :
composer create-project mj-zana/skeleton-web mon-site
cd mon-site
composer serve
Ouvrez ensuite http://localhost:8000. Le guide de démarrage (en anglais) prend le relais.
Si vous voulez de l’aide pour adopter Zana, que ce soit une formation, une revue d’architecture ou un projet réalisé avec, parlez-nous de votre projet. Nous le construisons, et nous l’utilisons tous les jours.
