PhpSyntax
Bezztrátový syntaktický strom PHP bez jediné závislosti: každý token, každá mezera i každý komentář má své místo a vytištěný strom je původní soubor bajt po bajtu. Knihovna pro nástroje, které mají kód číst a měnit.
Roky jsem měl parsování PHP za vyřešenou věc: je tu nikic/PHP-Parser, stojí na něm PHPStan i Rector, co víc chtít. Pak jsem psal skript, který měl ve stovkách souborů přejmenovat jedno volání, a došlo mi, kolik práce dá nepřepsat přitom všechno ostatní. Přesně na tohle je PhpSyntax.
Strom, který nic nezahodí
Abstraktní syntaktický strom (AST) zahazuje, co pro význam programu není podstatné: mezery, prázdné řádky, komentáře uvnitř výrazů, závorky navíc. Pro analýzu kódu je to správně. Jakmile ale chcete kód změnit a zapsat zpátky, potřebujete i to, co AST zahodil, jinak přetisknete soubor podle svých představ místo podle autorových.
PhpSyntax staví konkrétní syntaktický strom (concrete syntax tree, CST): každý token zdrojáku v něm má svůj
slot (IfNode má ifKeyword, openParen, condition, closeParen a
body) a bílé znaky s komentáři visí na tokenech jako takzvaná trivia. Zaručeno je tohle:
Printer::print($parser->parse($code)) === $code
Platí bajt po bajtu pro cokoli, co PHP přijme: BOM, hashbang, konce řádků CR i CRLF, uzavírací tagy, inline HTML,
__halt_compiler(). Je to invariant, který drží testy nad velkým korpusem kódu, a zároveň nejjednodušší
test, jaký si nad vlastním kódem uděláte: naparsovat, vytisknout, porovnat.
Kdy PhpSyntax a kdy nikic/PHP-Parser
Obě knihovny mají své místo a je poctivé říct které.
nikic/PHP-Parser použijte, když potřebujete vědět, co kód dělá: typy, tok řízení, vyhodnocení konstantních výrazů, a k tomu obrovský ekosystém nástrojů kolem. A také tehdy, když potřebujete strom i z kódu se syntaktickou chybou: PHP-Parser se z chyby umí zotavit a vrátí částečný strom, kdežto PhpSyntax skončí výjimkou, protože jeho gramatika zotavení nemá.
PhpSyntax použijte, když se chcete kódu dotknout a zbytek nechat být: přejmenovat volání, doplnit argument, přesunout komentář, srovnat mezery, a dostat diff přesně tak velký jako změna. Formátovače, migrační skripty, generátory, které upravují existující soubory, nástroje na hromadné přepisy. Největší z nich je DressCode, kontrola a oprava stylu kódu, pro kterou PhpSyntax původně vznikl.
Instalace
composer require phpsyntax/phpsyntax
Knihovna běží na PHP 8.4 až 8.6 a vyžaduje rozšíření tokenizer. Jinak nemá žádnou závislost:
v composer.json je jen PHP a tokenizer, žádné mbstring ani ctype, takže se nemá o co
přetahovat s tím, co už v projektu máte. Kód psaný pro novější PHP přečte i na starším běhu, viz Parsování.
Na ukázku
use PhpSyntax\Nodes\Expression\FunctionCallNode;
use PhpSyntax\Nodes\NameNode;
use PhpSyntax\Parser;
use PhpSyntax\Printer;
$file = new Parser()->parse(file_get_contents('Order.php'));
foreach ($file->find(FunctionCallNode::class) as $call) {
if ($call->name instanceof NameNode && $call->name->text === 'sizeof') {
$call->name->text = 'count';
}
}
file_put_contents('Order.php', Printer::print($file));
Přejmenovalo se každé volání sizeof a to je také jediné, co se v souboru změnilo. Jak se strom
prochází, jak se mění a co ví o jménech, říkají další stránky: Parsování a tisk, Uzly a
sloty, Trivia, Procházení, Úpravy,
Analýzy a přehled
uzlů.
Výkon
Parser je generovaný automat LALR(1) nad gramatikou převzatou z nikic/PHP-Parser, tisk je pouhé spojení textů tokenů. Na běžném stroji
projde parsování a tisk celého adresáře vendor/ s třemi sty soubory a 1,2 MB kódu za necelou sekundu, tedy
zhruba dvě milisekundy na soubor. Index pozic tokenů se staví líně a po úpravě se neobnovuje celý, takže dotaz hned po
změně stojí tolik, jak daleko od té změny leží.