Le problème
Renommer un composant, normaliser une directive, appliquer la même retouche à
quatre cents vues : le réflexe est un sed, et il remplace toutes les
occurrences — y compris celles qui vivent dans une chaîne, un commentaire ou un
attribut qu'on ne voulait pas toucher. Blade n'est pas un langage régulier.
Forte, écrit par John Koster, analyse un .blade.php en arbre syntaxique
typé, laisse interroger cet arbre, et rend un template réécrit. Le formateur
prettier-plugin-blade v3, du même auteur, tourne dessus.
Installation
composer require fortephp/fortePHP 8.2, extension dom, Laravel 10 à 13. Le service provider
s'enregistre seul, la façade Forte est disponible immédiatement.
Lire
use Forte\Facades\Forte;
$doc = Forte::parse('<div class="mt-4">Hello, {{ $name }}!</div>');
$doc = Forte::parseFile('resources/views/welcome.blade.php');Deux propriétés font la différence à l'usage. D'abord le parseur est
tolérant : sur une balise non fermée ou un @if sans @endif, il enregistre
un diagnostic et rend un arbre partiel, qu'on peut interroger comme un autre —
deux vues cassées dans une application de quatre cents en produisent deux, les
398 restantes s'analysent normalement. Ensuite, analyser puis rendre sans
modification redonne les mêmes octets, espaces compris.
Interroger
Les méthodes de requête renvoient des lazy collections :
$forms = $doc->queryElements('form');
$conditionals = $doc->queryBlockDirectives(['if', 'unless']);
$components = $doc->queryComponents(['x-alert', 'livewire:*']);Forte construit un DOMDocument et passe l'expression à DOMXPath — c'est ce
que couvre l'exigence ext-dom. Les constructions Blade deviennent des éléments
dans un espace de noms forte : un @if est un forte:if, un écho un
forte:echo.
$divs = $doc->xpath('//div[@class]')->get();
$conditionals = $doc->xpath('//forte:if')->get();Les résultats reviennent en nœuds Forte, pas en DOMElement, donc une requête
se passe directement à une réécriture. Trouver tous les <a> sans href à
l'intérieur d'un <nav> tient en une expression au lieu d'un parcours récursif.
Réécrire
apply(), rewrite() et rewriteWith() rendent chacun un nouveau
Document et laissent l'original intact — les deux versions restent comparables.
use Forte\Rewriting\NodePath;
$newDoc = $doc->rewriteWith(function (NodePath $path) {
if ($path->isTag('a') && str_starts_with($path->getAttribute('href') ?? '', 'http')) {
$path->setAttribute('target', '_blank');
$path->setAttribute('rel', 'noopener noreferrer');
}
});
echo $newDoc->render();Le rappel reçoit un NodePath, pas le nœud : parent, frères, ancêtres,
profondeur, plus renameTag(), addClass(), replaceWith(), remove(),
insertBefore(), insertAfter(), et skipChildren() / stopTraversal() pour
sortir tôt. Les modifications sont mises en file puis appliquées d'un coup : une
passe sur un gros template produit un document, pas un par retouche.
Pour plus long qu'une closure, on écrit un Visitor avec enter() — appelé
avant les enfants — et leave(), utile quand la retouche dépend de ce qui est
arrivé aux enfants. Un RewriteBuilder offre la variante déclarative :
sélection XPath, puis mutations à appliquer.
À retenir
- Trois usages concrets : inventorier ce que contiennent les vues, appliquer une édition à des centaines de fichiers, ou faire échouer un build quand un template viole une convention d'équipe.
- La tolérance aux erreurs et le rendu à l'octet près sont ce qui rend un codemod utilisable sur une base réelle.
- Licence MIT, v1.1.0.
Source : Laravel News