Comment zanabler.com est construit avec Zana
Huit modules, deux langues, un back-office construit à partir de types de contenu, des formulaires qui résistent à une panne de messagerie, et une commande pour déployer sur un hébergement mutualisé. Visite guidée du site que vous lisez.

Nous disons à nos clients que Zana est prêt pour de vrais sites web. Le test honnête était donc de construire le nôtre avec. Zanabler.com est un site bilingue avec un back-office, des études de cas, des articles, un formulaire de demande de projet et une newsletter. Il tourne sur un hébergement mutualisé cPanel. Voici comment il est construit.
Huit modules, un par partie du site
L’application est une liste de modules dans config/modules.php :
return [
Modules\Site\SiteModule::class,
Modules\Insights\InsightsModule::class,
Modules\Admin\AdminModule::class,
Modules\Services\ServicesModule::class,
Modules\Work\WorkModule::class,
Modules\Products\ProductsModule::class,
Modules\Pages\PagesModule::class,
Modules\Contact\ContactModule::class,
];
Chaque module est un dossier avec ses routes, ses contrôleurs, ses templates, ses traductions, ses migrations de base de données et ses écrans de back-office. Insights contient les articles, dont celui-ci. Work contient les études de cas, et Contact le formulaire de projet et la newsletter. Pour changer le fonctionnement des articles, on ouvre un seul dossier.
Les modules ne communiquent qu’à travers de petits contrats. La page d’accueil affiche les derniers articles, mais elle n’interroge pas la table des articles. Elle demande au module Insights un ArticleCatalog, et c’est Insights qui décide comment répondre.
Deux langues, dans l’URL
Chaque page publique existe en anglais et en français, avec sa propre adresse dans chaque langue :
$router->localized(['en' => '/insights', 'fr' => '/articles'], [InsightsController::class, 'index'])->name('insights');
$router->localized(['en' => '/insights/{slug}', 'fr' => '/articles/{slug}'], [InsightsController::class, 'show'])->name('insights.show');
Le contenu est stocké de la même façon. Un article est un enregistrement, avec une traduction par langue, et chaque traduction a son titre, son résumé, son texte et son slug. Le sélecteur de langue et les liens hreflang viennent de ces slugs. Si un article n’a pas encore de version anglaise, le sélecteur mène à la liste des articles en anglais plutôt qu’à une page introuvable.
Un back-office construit à partir de types de contenu
La plupart des contenus du site se modifient dans le back-office : services, études de cas, articles, membres de l’équipe, pages et newsletters. Nous n’avons pas écrit une série d’écrans pour chacun. Un type de contenu décrit les champs une seule fois :
return new ContentType(
name: 'articles',
table: 'articles',
key: 'article_id',
fields: [
Field::select('category', self::CATEGORIES),
Field::image('cover_image'),
Field::text('author'),
Field::checkbox('featured'),
Field::text('title', translated: true, required: true),
Field::text('slug', translated: true),
Field::textarea('summary', translated: true),
Field::markdown('body'),
],
order: 'published_at DESC',
);
À partir de cette description, le back-office construit la liste, les formulaires (avec une section par langue), la validation, les brouillons, les liens d’aperçu signés et la publication. Chaque modification est enregistrée dans un historique : on sait qui a changé quoi, et quand.
Les articles sont rédigés en Markdown. Le moteur Markdown de Zana échappe le HTML brut et supprime les liens dangereux. Un script collé dans un article reste du texte et n’atteint jamais le navigateur d’un visiteur.
Des formulaires qui tiennent bon
Le formulaire de demande de projet est souvent le premier contact d’un nouveau client : il ne doit perdre aucun message. Les demandes sont enregistrées en base de données avant l’envoi du moindre e-mail. Si le serveur de messagerie est en panne, le visiteur voit quand même la confirmation, la demande attend dans le back-office, et l’échec est inscrit dans le journal des erreurs. Le formulaire est protégé par des jetons CSRF et une limitation du nombre d’envois.
La newsletter respecte les limites d’un hébergement mutualisé. Une tâche planifiée envoie les newsletters en attente par petits lots toutes les cinq minutes, sans jamais tourner deux fois en même temps :
$schedule->call([NewsletterDispatcher::class, 'sendBatch'])->everyFiveMinutes()->name('newsletter batch')->withoutOverlapping();
Sur le serveur, une seule tâche cron cPanel lance le planificateur de Zana, qui s’occupe du reste.
Testé comme un visiteur
Près de soixante tests ouvrent le site comme le ferait un navigateur. Ils suivent les liens, remplissent les formulaires et vérifient ce qui s’affiche :
$this->browser()->get('/fr/services/applications-mobiles')
->assertOk()
->assertSelector('h1', 'Applications mobiles')
->assertSelector('link[rel=alternate][hreflang=en][href="http://localhost:8000/en/services/mobile-apps"]');
Les tests tournent en mémoire, avec leur propre fichier de réglages. La configuration locale d’un développeur ne s’y glisse jamais, et aucun vrai e-mail n’est envoyé. Un test peut quand même vérifier que le bon message serait parti.
Déployé sur un hébergement mutualisé
Beaucoup de nos clients sont hébergés sur cPanel, souvent sans accès SSH. Nous voulions que ce chemin soit tout aussi simple. Une seule commande prépare l’envoi :
php bin/console release:build
Elle produit deux archives à partir du dernier commit, avec les dépendances de production déjà installées. La première contient l’application. Elle se place à côté de public_html, pour que les réglages et le code ne soient jamais accessibles depuis le web. La seconde va dans public_html. En production, le conteneur est compilé et la configuration mise en cache. L’application refuse de démarrer si le mode debug est actif ou si un secret est encore une valeur d’exemple.
Le site envoie aussi des en-têtes de sécurité stricts, dont une politique de sécurité du contenu (CSP). Les images sont servies en tailles adaptées à l’écran, et chaque page a ses données structurées et sa place dans le sitemap.
Ce que nous en avons retenu
Construire notre propre site avec Zana a amélioré le framework. Ce site devait enregistrer les erreurs du serveur et garder ses formulaires fonctionnels malgré une panne de messagerie. Ce code n’est pas resté ici : il est entré dans Zana 2.6, où tous les projets peuvent en profiter.
C’est tout l’intérêt d’utiliser notre propre outil. Ce qui est pénible pour nous l’est aussi pour nos clients, et nous le corrigeons dans le framework plutôt que de le contourner dans un seul projet.
Si vous avez besoin d’un site ou d’une application métier construits de cette façon, parlez-nous de votre projet. Si vous préférez le construire vous-même, Zana est open source.
