AST-Suche
Der AST-Modus von Greph durchsucht PHP-Quellcode nach Struktur statt nach Text. Muster werden als gewöhnlicher PHP-Code mit den Metavariablen $VARIABLE und $$$VARIADIC geschrieben. Das Muster wird von nikic/php-parser in dieselbe AST-Struktur wie der durchsuchte Quellcode geparst und anschließend Knoten für Knoten abgeglichen.
Dieses Modell entspricht dem von ast-grep. Der Kompatibilitäts-Wrapper sg stellt die ast-grep-Oberfläche CLI auf derselben Engine bereit.
Schnellbeispiele
# Find every constructor call
./vendor/bin/greph -p 'new $CLASS()' src
# Find every method call with any number of arguments
./vendor/bin/greph -p '$obj->$method($$$ARGS)' src
# Find old-style array() calls
./vendor/bin/greph -p 'array($$$ITEMS)' src
# Find void-returning functions
./vendor/bin/greph -p 'function $name($$$PARAMS): void {}' src
# Find isset ternaries that should be ??
./vendor/bin/greph -p 'isset($x) ? $x : $default' src
Mustersyntax
Ein Muster ist gültiger PHP-Quellcode. Überall dort, wo ein echter Ausdruck oder eine Anweisung stehen könnte, kannst du eine Metavariable einsetzen, um diesen Knoten zu erfassen.
| Syntax | Treffer | Beispiel |
|---|---|---|
$VAR |
Beliebiger AST-Knoten, erfasst als VAR |
$x + $y trifft auf foo() + bar zu |
$_ |
Beliebiger AST-Knoten, wird nicht erfasst | $_->method() trifft auf einen beliebigen Empfänger zu |
$$$ARGS |
Null oder mehr Knoten (variadisch), erfasst als ARGS |
func($$$ARGS) trifft auf func(), func(1), func(1, 2, 3) zu |
$VAR (wiederholt) |
Dieselbe $VAR muss beide Male dieselbe Struktur treffen |
$x == $x trifft auf a == a, aber nicht auf a == b zu |
Der Name der Metavariablen wird üblicherweise großgeschrieben, muss es aber nicht. $_ ist ein Platzhalter ohne Erfassung.
$$$VARIADIC funktioniert überall dort, wo PHP eine Knotenliste erlaubt: Funktionsargumente, Array-Elemente, Parameterlisten und Anweisungskörper. Die Erfassung speichert die gesamte gefundene Sequenz, auch wenn sie keine Elemente enthält.
Metavariablen für Bezeichner
Klassen-, Funktions-, Schnittstellen-, Trait- und Enum-Namen sind Bezeichner, keine Ausdrücke. Daher würde PHP nach class normalerweise kein $Name akzeptieren. Greph schreibt diese Muster vor dem Parsen um, damit die Metavariable trotzdem funktioniert:
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src
Dieselbe Umschreibung findet auch für class, interface, trait, enum und function statt.
Wiederholte Metavariablen
Wird eine Metavariable im selben Muster wiederverwendet, müssen beide Vorkommen strukturell denselben Knoten treffen. So kannst du überflüssigen Code erkennen:
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src
# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
Dieselbe Regel gilt für $$$VARIADIC: Zwei Vorkommen von $$$ARGS müssen dieselbe Sequenz treffen.
Dateien filtern
Der AST-Modus durchläuft den Dateibaum mit demselben Walker wie der Textmodus und verwendet standardmäßig den Dateitypfilter php. Überschreibe ihn mit --type, --type-not, --glob, --no-ignore und --hidden. Der Walker berücksichtigt weiterhin .gitignore und .grephignore und überspringt Binärdateien.
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
Ausgabe
Die Standardausgabe verwendet dasselbe Format file:line:content wie der Textmodus. Der gefundene Code wird dabei zu einer einzigen Zeile zusammengefasst:
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
Übergib --json, um strukturierte Treffer auszugeben:
[
{
"file": "src/Greph.php",
"start_line": 38,
"end_line": 38,
"start_file_pos": 1234,
"end_file_pos": 1252,
"code": "new FileWalker()"
}
]
Parsing-Fehler
Standardmäßig überspringt Greph Dateien, die nicht geparst werden können, ohne eine Meldung auszugeben. Das ist sinnvoll bei der Suche in großen Codebasen mit experimentellem oder generiertem PHP. Übergib --strict-parse (bei greph-index ast-index search und greph-index ast-cache search), um stattdessen den Parsing-Fehler auszulösen. Wenn du die Fassade direkt verwendest, setze skipParseErrors: false bei AstSearchOptions.
Programmatische Verwendung
use Greph\Greph;
use Greph\Ast\AstSearchOptions;
$matches = Greph::searchAst(
'$obj->$method($$$ARGS)',
'src',
new AstSearchOptions(jobs: 4),
);
foreach ($matches as $match) {
echo "{$match->file}:{$match->startLine}\n";
echo $match->code . "\n";
foreach ($match->captures as $name => $node) {
echo " {$name} = " . get_debug_type($node) . "\n";
}
}
$match->node ist der erfasste PhpParser\Node (du kannst ihn also mit PHP-Parsers NodeVisitor durchlaufen), und $match->captures ordnet jeden Namen einer Metavariablen dem erfassten Knoten oder der Liste erfasster Knoten zu.
So funktioniert es
Die Pipeline sieht so aus:
- Muster parsen:
Greph\Ast\PatternParserschreibt Metavariablen für Bezeichner um und parst dann das Muster mit PHP-Parser. - Quellcode parsen: Jede Kandidatendatei wird einmal geparst. Im Modus
ast-cachewird dieser Schritt durch das Deserialisieren aus dem Cache-Speicher ersetzt. - Kandidaten filtern:
Greph\Ast\AstPatternPrefilterextrahiert eine grobe Signatur aus dem Muster (Aufrufname, Klassenname, Anweisungstyp) und überspringt damit Dateien, die offensichtlich keinen Treffer enthalten können. Im Modusast-indexwird stattdessen ein vorberechneter Faktenspeicher abgefragt. - Abgleichen:
Greph\Ast\PatternMatcherdurchläuft den Quellcode-AST und vergleicht ihn mit dem Muster-AST. Dabei bindet es Metavariablen und stellt sicher, dass wiederholte Variablen übereinstimmen. - Ergebnisse ausgeben: Gefundene Knoten werden in
AstMatch-Objekte mit Zeilen- und Bytepositionen sowie dem Originalausschnitt aus dem Quellcode umgewandelt.
Bei wiederholten Workloads überspringen die indizierte AST-Suche und die AST-Suche mit Cache die meisten Schritte 2 und 3.