Skip to content

Getting Started

Installation

Install Greph via Composer:

Shell
composer require greph/greph

This installs the library plus four executables in vendor/bin/:

  • greph: native text search, AST search, and AST rewrite
  • greph-index: warmed text index, AST fact index, and cached AST search
  • rg: ripgrep-style compatibility wrapper
  • sg: ast-grep-style compatibility wrapper

These executables work from your project's vendor/bin/ since Greph 0.2.3. Earlier releases only found the Composer autoloader inside a clone of the Greph repository, so require at least that version:

Shell
composer require greph/greph:^0.2.3

Requirements

  • PHP 8.2+ for readonly classes, enums, first-class callable syntax, and constructor promotion
  • ext-json for JSON output and on-disk index payloads
  • ext-ctype for pattern and option parsing
  • ext-tokenizer for PHP-Parser and the AST prefilters
  • ext-zlib for the cached AST store (greph-index ast-cache)
  • nikic/php-parser (installed automatically as a Composer dependency)

ext-json, ext-ctype and ext-tokenizer are enabled in almost every PHP build. composer.json declares only ext-json at the moment (#5), so Composer does not warn when one of the others is missing.

Optional:

  • ext-pcntl for parallel scans. Greph degrades gracefully to single-process execution when it is missing.
  • External rg, grep, and sg binaries for benchmark comparisons. They are never required at runtime.

Searching, rewriting and indexing never call proc_open() or shell_exec(); only parallel scans fork, and they fall back to a single process without pcntl_fork(). See Process-Restricted Hosts for hosts that disable proc_open(), shell_exec() or pcntl_fork().

Greph's default mode is grep-compatible text search:

Shell
vendor/bin/greph -F "function" src

The output uses the canonical file:line:content format that grep, ripgrep, and most editor integrations expect.

Text
src/Greph.php:41:    public static function walk(string|array $paths, ?WalkOptions $options = null): FileList
src/Greph.php:50:    public static function searchText(string $pattern, string|array $paths, ?TextSearchOptions $options = null): array
src/Greph.php:81:    public static function buildTextIndex(

Add -i for case-insensitive matching, -w for whole-word matching, -c for counts, -l for file paths only, -C 2 for two lines of context, and so on. The full list lives in the CLI reference.

Pass -p and a PHP-shaped pattern to switch to structural search:

Shell
vendor/bin/greph -p 'new $CLASS()' src

$CLASS is a metavariable that captures any class name. The pattern new $CLASS() matches every new expression without arguments, regardless of class, formatting, or comments. Use new $CLASS($$$ARGS) to match calls with any arguments.

Each AST match is printed as the file, the start line, and the matched code:

Text
src/Greph.php:43:new FileWalker()
src/Greph.php:52:new TextSearchOptions()
src/Greph.php:54:new TextSearcher()

Other useful patterns:

Shell
# Any method call with any arguments
vendor/bin/greph -p '$obj->$method($$$ARGS)' src

# Any function declaration with a void return type
vendor/bin/greph -p 'function $name($$$PARAMS): void {}' src

# Old-style array calls
vendor/bin/greph -p 'array($$$ITEMS)' src

See Modes / AST Search for the full pattern syntax.

Your first rewrite

Add -r to turn the AST search into a rewrite:

Shell
# Preview only
vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --dry-run src

# Apply
vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' src

# Confirm each file before writing
vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --interactive src

Rewrites are format-preserving outside the match: only the matched node is replaced, and the surrounding whitespace, indentation, and comments stay intact. The matched node itself is printed fresh from the template, so its own layout is normalized and a comment inside it can be lost. See Modes / Rewrite for details.

PHP API

The static Greph\Greph facade exposes the same operations as the CLI:

PHP
use Greph\Greph;
use Greph\Ast\AstSearchOptions;
use Greph\Text\TextSearchOptions;

$results = Greph::searchText(
    'function',
    'src',
    new TextSearchOptions(fixedString: true, caseInsensitive: true),
);

foreach ($results as $file) {
    foreach ($file->matches as $match) {
        echo "{$file->file}:{$match->line}:{$match->content}\n";
    }
}

$matches = Greph::searchAst(
    'new $CLASS()',
    'src',
    new AstSearchOptions(),
);

foreach ($matches as $match) {
    echo "{$match->file}:{$match->startLine}:{$match->code}\n";
}

The full surface is covered in API.

Repeated workloads benefit from warmed indexes. Build once, then query many times:

Shell
# Text index
vendor/bin/greph-index build .
vendor/bin/greph-index search -F "function" .

# AST fact index
vendor/bin/greph-index ast-index build .
vendor/bin/greph-index ast-index search 'new $CLASS()' src

# Cached parsed-tree index
vendor/bin/greph-index ast-cache build .
vendor/bin/greph-index ast-cache search 'array($$$ITEMS)' src

Indexes live in .greph-index, .greph-ast-index, and .greph-ast-cache at the project root by default.

For actively changing trees, choose a lifecycle profile instead of treating every warmed index the same:

Shell
vendor/bin/greph-index build . --lifecycle static
vendor/bin/greph-index build . \
  --lifecycle opportunistic-refresh \
  --auto-refresh-max-files 32 \
  --auto-refresh-max-bytes 1048576
vendor/bin/greph-index stats . --dry-refresh

The common pattern is:

  • keep a stable baseline as static
  • keep actively changing overlays as opportunistic-refresh
  • inspect stale behavior with stats --dry-refresh

You can search several warmed indexes in one command:

Shell
vendor/bin/greph-index search -F "apply_filters" . \
  --index-dir wordpress/.greph-index \
  --index-dir wp-content/plugins/my-plugin/.greph-index \
  --show-index-origin

vendor/bin/greph-index ast-index search 'new $CLASS()' . \
  --index-dir wordpress/.greph-ast-index \
  --index-dir wp-content/plugins/my-plugin/.greph-ast-index \
  --show-index-origin

Named index sets

If you do that repeatedly, put the layout into .greph-index-set.json and use the set subcommands instead of repeating flags:

Shell
vendor/bin/greph-index set build
vendor/bin/greph-index set stats --dry-refresh
vendor/bin/greph-index set search --show-index-origin -F "apply_filters" .
vendor/bin/greph-index set search --mode ast-index 'new $CLASS()' .
vendor/bin/greph-index set search --mode ast-cache 'array($$$ITEMS)' .

Planner trace

To see how warmed search is narrowing candidates, add --trace-plan:

Shell
vendor/bin/greph-index search --trace-plan -F "function" .
vendor/bin/greph-index ast-index search --trace-plan 'new $CLASS()' .

See Indexed / Text and Indexed / AST for the full warmed workflow.

Compatibility wrappers

If you have tools or agents that already speak ripgrep or ast-grep, drop in the wrappers:

Shell
vendor/bin/rg --json -F "function" src
vendor/bin/sg --pattern 'new $CLASS()' src --lang php

Both wrappers reuse the same engine and are probe-verified against the upstream binaries. The verified surface is documented in Compatibility / rg and Compatibility / sg and reproduced in the generated feature matrix.

Configuration

Greph picks up two on-disk conventions:

  • .gitignore: respected by default. Pass --no-ignore to disable.
  • .grephignore: same syntax as .gitignore, applied on top. Useful for tool-specific exclusions you do not want in version control.

The default index directories are .greph-index, .greph-ast-index, and .greph-ast-cache at the chosen root. Override with --index-dir on any greph-index subcommand, or use .greph-index-set.json for repeatable multi-index layouts.