AST-Suche mit Cache
Die AST-Suche mit Cache ist ein ergänzender Modus mit aufgewärmtem Cache zum Faktenindex. Statt extrahierte Fakten zu speichern und passende Dateien erneut zu parsen, speichert der Cache serialisierte geparste Bäume auf der Festplatte. Bei späteren Abfragen werden die Bäume deserialisiert und direkt mit dem Matcher geprüft. Die PHP-Parser-Pipeline wird dabei vollständig übersprungen.
Du erreichst den Cache über den Unterbefehl greph-index ast-cache CLI und die Fassadenmethoden Greph::buildAstCache() / Greph::searchAstCached(). Auch Suchen über mehrere Indizes und manifestbasierte Gruppen werden unterstützt.
Cache erstellen
# Full build at the current directory
./vendor/bin/greph-index ast-cache build .
# Use a non-default cache directory
./vendor/bin/greph-index ast-cache build . --index-dir /tmp/greph-ast-cache
# Build a cache that may refresh automatically for small changes
./vendor/bin/greph-index ast-cache build . \
--lifecycle opportunistic-refresh \
--auto-refresh-max-files 32 \
--auto-refresh-max-bytes 1048576
Standardmäßig wird der Cache unter <root>/.greph-ast-cache/ gespeichert. Von den drei Indexmodi dauert die Erstellung dieses Caches meist am längsten, da jede Datei einmal geparst werden muss.
Nach erfolgreicher Erstellung wird eine einzeilige Zusammenfassung ausgegeben:
Built AST cache for 2547 files in .greph-ast-cache (2541 cached trees, +2541 ~0 -0 =0)
Die Anzahl gespeicherter Bäume ist üblicherweise etwas geringer als die Anzahl der Dateien, da Dateien mit Parse-Fehlern stillschweigend übersprungen werden und keinen Cache-Eintrag erhalten. Übergib --strict-parse beim Suchbefehl, wenn Parse-Fehler stattdessen angezeigt werden sollen.
Cache aktualisieren
./vendor/bin/greph-index ast-cache refresh .
refresh durchsucht den indexierten Stammordner erneut, parst geänderte Dateien neu und schreibt nur die veränderten Bäume im Cache um.
Die Lebenszyklusprofile entsprechen den anderen aufgewärmten Modi:
static: Aktualität nie prüfen oder automatisch ändernmanual-refresh: veralteten Zustand instatsmelden, aber bei der Suche nie aktualisierenopportunistic-refresh: bei der Suche nur aktualisieren, wenn die Zahl geänderter Dateien unter den konfigurierten Grenzwerten liegtstrict-stale-check: veraltete Suchen ablehnen, anstatt den Cache zu aktualisieren
Cache abfragen
./vendor/bin/greph-index ast-cache search 'new $CLASS()' src
./vendor/bin/greph-index ast-cache search '$obj->$method($$$ARGS)' src
./vendor/bin/greph-index ast-cache search --json 'array($$$ITEMS)' src
./vendor/bin/greph-index ast-cache search --fallback scan 'new $CLASS()' src
./vendor/bin/greph-index ast-cache search --trace-plan 'array($$$ITEMS)' src
Die verfügbaren Flags entsprechen denen von ast-index search, einschließlich --fallback scan für fehlende Caches.
Cache-Suche über mehrere Indizes
Wiederhole --index-dir, um mehrere aufgewärmte Caches in einem Aufruf zu durchsuchen:
./vendor/bin/greph-index ast-cache search \
--index-dir ../wordpress/.greph-ast-cache \
--index-dir ./.greph-ast-cache \
--show-index-origin \
'array($$$ITEMS)' .
Manifestbasierte Cache-Gruppen
Wenn du bereits benannte Gruppen aufgewärmter Indizes verwendest, kannst du mit demselben Manifest die AST-Suche mit Cache ausführen:
./vendor/bin/greph-index set build --mode ast-cache
./vendor/bin/greph-index set stats --mode ast-cache --dry-refresh
./vendor/bin/greph-index set search --mode ast-cache --show-index-origin 'array($$$ITEMS)' .
Planner-Diagnosen
--trace-plan gibt die Entscheidungen des AST-Cache-Planners an stderr aus. Dazu gehören:
- welche aufgewärmten Caches durchsucht wurden
- ob die Logik zur Aktualisierung veralteter Caches ausgeführt wurde
- die Anzahl der Kandidatendateien nach der Eingrenzung auf Dateiebene
- die Anzahl der deserialisierten Bäume, die für den genauen strukturellen Abgleich verwendet wurden
Cache oder Faktenindex
Beide Modi beschleunigen die AST-Suche, sind aber auf unterschiedliche Aufgaben ausgelegt.
| Aspekt | Faktenindex (ast-index) |
Cache (ast-cache) |
|---|---|---|
| Gespeicherte Daten | Extrahierte Fakten (Aufrufnamen, Klassennamen, Deklarationen) | Serialisierte geparste Bäume |
| Erstellungskosten | Niedriger | Höher (parst alles) |
| Größe beim Erstellen | Kleiner | Größer |
| Abfragepfad | Nach Fakten filtern, dann Kandidaten parsen und abgleichen | Baum deserialisieren, dann abgleichen |
| Am besten geeignet für | Muster mit einem erkennbaren Kopf (Aufrufname, Klassenname) | Muster, deren Unterscheidungsmerkmal strukturell und nicht namentlich ist |
| Am schlechtesten geeignet für | Muster, deren Kopf eine Metavariable ist | Kalte Abfragen für einen Baum, der nicht mehr in den OS-Seitencache passt |
In der Praxis bietet der Faktenindex die schnellste kalte Abfrage und der Cache die schnellste warme Abfrage. Wähle die Variante, die zu deinem Workload passt, oder erstelle beide und lass dein Tool entscheiden.
Programmatische Verwendung
use Greph\Greph;
use Greph\Ast\AstSearchOptions;
use Greph\Index\IndexLifecycleProfile;
Greph::buildAstCache('.', lifecycle: IndexLifecycleProfile::Static);
$matches = Greph::searchAstCached(
'array($$$ITEMS)',
'src',
new AstSearchOptions(jobs: 4, tracePlan: true),
);
searchAstCached akzeptiert dieselben AstSearchOptions wie die native AST-Suche und liefert dieselbe list<AstMatch>-Struktur zurück. Der einzige Unterschied: Für Dateien, die bereits im Cache liegen, wird die Parser-Pipeline umgangen. Für Abläufe mit mehreren Indizes oder Manifesten verwendest du searchAstCachedMany(...) und searchAstCachedSet(...).
Wann du ihn verwenden solltest
Der Cache-Modus für ASTs ist das richtige Werkzeug, wenn:
- Du wiederholt strukturelle Abfragen an derselben Codebasis ausführst.
- Bei deinen vorherigen Durchläufen PHP-Parser der Engpass war (in Profilen als Zeit im Lexer und Parser sichtbar).
- Du den Speicherplatz für den serialisierten Baumspeicher entbehren kannst.
Wenn deine Abfragen normalerweise nur einen kleinen Teil der Codebasis betreffen und sich die Treffer auf erkennbare Aufruf- oder Klassennamen konzentrieren, ist der Faktenindex meist besser geeignet. Wenn deine Abfragen große Teile der Codebasis abdecken und du einmalige Parsing-Kosten in Kauf nehmen kannst, ist der Cache die bessere Wahl.