
# PHP API

## `Greph\Greph`

`Greph\Greph` 类是静态入口点。它提供所有搜索、重写和索引操作。所有方法都接受单个路径或路径列表。

```php
use Greph\Greph;
```

### `walk`

```php
public static function walk(string|array $paths, ?WalkOptions $options = null): FileList
```

将 `$paths` 解析为扁平的 `FileList` 绝对文件路径列表。支持 gitignore、`.grephignore`、文件类型过滤、glob 模式、包含隐藏文件、跟随符号链接、二进制检测和最大文件大小限制。

```php
use Greph\Walker\WalkOptions;
use Greph\Walker\FileTypeFilter;

$files = Greph::walk('src', new WalkOptions(
    fileTypeFilter: new FileTypeFilter(['php']),
));

foreach ($files->paths() as $path) {
    echo "$path\n";
}
```

---

### `searchText`

```php
public static function searchText(
    string $pattern,
    string|array $paths,
    ?TextSearchOptions $options = null,
): array
```

执行文本搜索并返回一个 `list<TextFileResult>`。每个 `TextFileResult` 都包含文件路径以及一个 `TextMatch` 对象列表（行、列、内容、捕获内容和可选上下文）。

```php
use Greph\Greph;
use Greph\Text\TextSearchOptions;

$results = Greph::searchText(
    'function',
    'src',
    new TextSearchOptions(
        fixedString: true,
        caseInsensitive: true,
        beforeContext: 1,
        afterContext: 1,
        jobs: 4,
    ),
);

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

当设置了 `jobs > 1` 且文件数量超过工作线程阈值时，`searchText` 会自动将扫描任务分发给 `WorkerPool`。否则将在进程内运行。

---

### `searchAst`

```php
public static function searchAst(
    string $pattern,
    string|array $paths,
    ?AstSearchOptions $options = null,
): array
```

执行结构化 AST 搜索并返回一个 `list<AstMatch>`。模式使用带有 `$VAR` 和 `$$$VARIADIC` 元变量的 PHP 源代码编写。每个 `AstMatch` 都包含文件、捕获的 `PhpParser\Node`、捕获内容、起止行与字节偏移量，以及匹配的源代码片段。

```php
use Greph\Greph;
use Greph\Ast\AstSearchOptions;

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

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

---

### `rewriteAst`

```php
public static function rewriteAst(
    string $searchPattern,
    string $rewritePattern,
    string|array $paths,
    ?AstSearchOptions $options = null,
): array
```

执行结构化重写并返回一个 `list<RewriteResult>`。每个 `RewriteResult` 都包含文件路径、原始内容、重写后的内容和替换次数。重写会保留格式：仅替换匹配的节点范围，其余文件内容保持不变。

```php
$rewrites = Greph::rewriteAst(
    'array($$$ITEMS)',
    '[$$$ITEMS]',
    'src',
);

foreach ($rewrites as $result) {
    if ($result->changed()) {
        file_put_contents($result->file, $result->rewrittenContents);
    }
}
```

facade 不会写入磁盘；由调用方决定是否应用更改。除非设置了 `--dry-run` 或 `--interactive`，否则 CLI 会应用这些更改。

---

### 索引文本搜索

#### `buildTextIndex`

```php
public static function buildTextIndex(
    string $rootPath = '.',
    ?string $indexPath = null,
    IndexLifecycle|IndexLifecycleProfile|string|null $lifecycle = null,
): IndexBuildResult
```

为 `$rootPath` 构建新的 trigram + 标识符倒排索引。索引存储在 `$indexPath`（默认为 `$rootPath/.greph-index`）。`$lifecycle` 可以是 `static`、`manual-refresh`、`opportunistic-refresh` 或 `strict-stale-check`。返回一个 `IndexBuildResult`，其中包含文件数、trigram 数量，以及新增/更新/删除/未更改的计数。

#### `refreshTextIndex`

```php
public static function refreshTextIndex(
    string $rootPath = '.',
    ?string $indexPath = null,
    IndexLifecycle|IndexLifecycleProfile|string|null $lifecycle = null,
): IndexBuildResult
```

增量刷新现有文本索引。文件会重新计算哈希，仅重写有更改的条目。

#### `searchTextIndexed`

```php
public static function searchTextIndexed(
    string $pattern,
    string|array $paths,
    ?TextSearchOptions $options = null,
    ?string $indexPath = null,
): array
```

针对已预热的索引执行文本搜索。如果索引不存在，则回退为运行时错误（请先调用 `buildTextIndex`）。返回一个结构与 `searchText` 完全相同的 `list<TextFileResult>`。

```php
Greph::buildTextIndex('.');
$results = Greph::searchTextIndexed('function', 'src');
```

#### `searchTextIndexedMany`

```php
public static function searchTextIndexedMany(
    string $pattern,
    string|array $paths,
    array $indexPaths,
    ?TextSearchOptions $options = null,
): array
```

在多个文本索引目录中执行已预热的文本搜索。Greph 会搜索每个适用的索引，按绝对文件路径去重，并且仅对所有已提供索引根目录之外的路径回退到原生扫描。

#### `loadIndexSet`

```php
public static function loadIndexSet(?string $manifestPath = null): IndexSet
```

加载索引集清单，默认为当前工作目录中的 `.greph-index-set.json`。

#### `searchTextIndexedSet`

```php
public static function searchTextIndexedSet(
    string $pattern,
    string|array $paths,
    ?TextSearchOptions $options = null,
    ?string $manifestPath = null,
    array $entryNames = [],
): array
```

从索引集清单中解析选定的 `text` 条目，并运行与 `searchTextIndexedMany` 等效的操作。

---

### 索引 AST 搜索

#### `buildAstIndex` / `refreshAstIndex`

```php
public static function buildAstIndex(
    string $rootPath = '.',
    ?string $indexPath = null,
    IndexLifecycle|IndexLifecycleProfile|string|null $lifecycle = null,
): AstIndexBuildResult
public static function refreshAstIndex(
    string $rootPath = '.',
    ?string $indexPath = null,
    IndexLifecycle|IndexLifecycleProfile|string|null $lifecycle = null,
): AstIndexBuildResult
```

构建或增量刷新 AST 事实索引。默认将提取的事实存储在 `$rootPath/.greph-ast-index`。

#### `searchAstIndexed`

```php
public static function searchAstIndexed(
    string $pattern,
    string|array $paths,
    ?AstSearchOptions $options = null,
    ?string $indexPath = null,
): array
```

使用事实索引缩小候选范围，然后针对源代码重新验证匹配项。返回与 `searchAst` 形状相同的 `list<AstMatch>`。

#### `searchAstIndexedMany`

```php
public static function searchAstIndexedMany(
    string $pattern,
    string|array $paths,
    array $indexPaths,
    ?AstSearchOptions $options = null,
): array
```

在多个 AST 索引目录中执行已预热的 AST 事实搜索。

#### `searchAstIndexedSet`

```php
public static function searchAstIndexedSet(
    string $pattern,
    string|array $paths,
    ?AstSearchOptions $options = null,
    ?string $manifestPath = null,
    array $entryNames = [],
): array
```

从索引集清单中解析选定的 `ast-index` 条目，并运行与 `searchAstIndexedMany` 等效的操作。

#### `buildAstCache` / `refreshAstCache`

```php
public static function buildAstCache(
    string $rootPath = '.',
    ?string $indexPath = null,
    IndexLifecycle|IndexLifecycleProfile|string|null $lifecycle = null,
): AstCacheBuildResult
public static function refreshAstCache(
    string $rootPath = '.',
    ?string $indexPath = null,
    IndexLifecycle|IndexLifecycleProfile|string|null $lifecycle = null,
): AstCacheBuildResult
```

构建或增量刷新缓存的解析树存储。默认将序列化后的语法树存储在 `$rootPath/.greph-ast-cache`。

#### `searchAstCached`

```php
public static function searchAstCached(
    string $pattern,
    string|array $paths,
    ?AstSearchOptions $options = null,
    ?string $indexPath = null,
): array
```

针对缓存的解析树执行 AST 搜索，完全跳过解析器。返回与 `searchAst` 形状相同的 `list<AstMatch>`。

#### `searchAstCachedMany`

```php
public static function searchAstCachedMany(
    string $pattern,
    string|array $paths,
    array $indexPaths,
    ?AstSearchOptions $options = null,
): array
```

在多个缓存目录中执行已预热的缓存 AST 搜索。

#### `searchAstCachedSet`

```php
public static function searchAstCachedSet(
    string $pattern,
    string|array $paths,
    ?AstSearchOptions $options = null,
    ?string $manifestPath = null,
    array $entryNames = [],
): array
```

从索引集清单中解析选定的 `ast-cache` 条目，并运行与 `searchAstCachedMany` 等效的操作。

---

## `Greph\Text\TextSearchOptions`

用于文本搜索的只读选项对象。

```php
new TextSearchOptions(
    bool $fixedString = false,
    bool $caseInsensitive = false,
    bool $wholeWord = false,
    bool $invertMatch = false,
    ?int $maxCount = null,
    int $beforeContext = 0,
    int $afterContext = 0,
    bool $countOnly = false,
    bool $filesWithMatches = false,
    bool $filesWithoutMatches = false,
    bool $jsonOutput = false,
    bool $tracePlan = false,
    int $jobs = 1,
    bool $respectIgnore = true,
    bool $includeHidden = false,
    bool $followSymlinks = false,
    bool $skipBinaryFiles = true,
    bool $includeGitDirectory = false,
    ?FileTypeFilter $fileTypeFilter = null,
    int $maxFileSizeBytes = 10485760,
    array $globPatterns = [],
    bool $showLineNumbers = true,
    bool $showFileNames = true,
)
```

验证：

- `jobs >= 1`
- `beforeContext, afterContext >= 0`
- `maxCount === null || maxCount >= 1`

如果你想单独遍历文件树，请使用 `walkOptions()` 推导出匹配的 `WalkOptions`。

已预热的索引搜索器会使用 `tracePlan`，而 `greph-index` CLI 流程会将规划器诊断信息发送到 `stderr`。原生 `searchText` 本身不会使用它。

---

## `Greph\Ast\AstSearchOptions`

用于 AST 搜索和重写的只读选项对象。

```php
new AstSearchOptions(
    string $language = 'php',
    int $jobs = 1,
    bool $respectIgnore = true,
    bool $includeHidden = false,
    bool $followSymlinks = false,
    bool $skipBinaryFiles = true,
    bool $includeGitDirectory = false,
    ?FileTypeFilter $fileTypeFilter = null,
    int $maxFileSizeBytes = 10485760,
    array $globPatterns = [],
    bool $skipParseErrors = true,
    bool $dryRun = false,
    bool $interactive = false,
    bool $jsonOutput = false,
    bool $tracePlan = false,
)
```

AST 模式的默认文件类型过滤器为 `php`。当 `skipParseErrors` 为 `true`（默认值）时，无法解析的文件会被静默跳过；将其切换为 `false` 可使解析错误以异常形式抛出。已预热的 AST 搜索器会使用 `tracePlan`，而 `greph-index ast-index|ast-cache search` 用于输出候选筛除诊断信息。

---

## `Greph\Walker\WalkOptions`

用于文件遍历器的只读选项对象。

```php
new WalkOptions(
    bool $respectIgnore = true,
    bool $includeHidden = false,
    bool $followSymlinks = false,
    bool $skipBinaryFiles = false,
    bool $includeGitDirectory = false,
    ?FileTypeFilter $fileTypeFilter = null,
    int $maxFileSizeBytes = 10485760,
    array $globPatterns = [],
)
```

---

## `Greph\Walker\FileTypeFilter`

将命名文件类型解析为扩展名列表。内置别名：

| 别名 | 扩展名 |
|---|---|
| `css` | `css`、`sass`、`scss` |
| `html` | `htm`、`html`、`phtml` |
| `js` | `cjs`、`js`、`mjs` |
| `json` | `json` |
| `md` | `markdown`、`md` |
| `php` | `inc`、`php`、`php3`、`php4`、`php5`、`php7`、`php8`、`phpt`、`phtml` |
| `txt` | `txt` |
| `ts` | `ts`、`tsx` |
| `xml` | `xml` |
| `yaml` | `yaml`、`yml` |

```php
new FileTypeFilter(includeTypes: ['php'], excludeTypes: ['md']);
```

---

## 结果对象

### `Greph\Text\TextFileResult`

```php
final readonly class TextFileResult
{
    public string $file;
    /** @var list<TextMatch> */
    public array $matches;

    public function matchCount(): int;
    public function hasMatches(): bool;
}
```

### `Greph\Text\TextMatch`

```php
final readonly class TextMatch
{
    public string $file;
    public int $line;
    public int $column;
    public string $content;
    public string $matchedText;
    /** @var array<int|string, string> */
    public array $captures;
    /** @var list<array{line: int, content: string}> */
    public array $beforeContext;
    /** @var list<array{line: int, content: string}> */
    public array $afterContext;
}
```

### `Greph\Ast\AstMatch`

```php
final readonly class AstMatch
{
    public string $file;
    public PhpParser\Node $node;
    /** @var array<string, mixed> */
    public array $captures;
    public int $startLine;
    public int $endLine;
    public int $startFilePos;
    public int $endFilePos;
    public string $code;
}
```

### `Greph\Ast\RewriteResult`

```php
final readonly class RewriteResult
{
    public string $file;
    public string $originalContents;
    public string $rewrittenContents;
    public int $replacementCount;

    public function changed(): bool;
}
```

### `Greph\Index\IndexBuildResult`

```php
final readonly class IndexBuildResult
{
    public string $rootPath;
    public string $indexPath;
    public int $fileCount;
    public int $trigramCount;
    public int $addedFiles;
    public int $updatedFiles;
    public int $deletedFiles;
    public int $unchangedFiles;
}
```

`AstIndexBuildResult` 和 `AstCacheBuildResult` 的结构相同，分别以 `factCount` 和 `cachedTreeCount` 替换。

---

## 异常

Greph 会抛出三种类型化异常，均位于 `Greph\Exceptions` 中：

| 异常 | 触发时机 |
|---|---|
| `WalkerException` | 遍历期间发生文件系统错误（路径缺失、目录不可读） |
| `PatternException` | 正则表达式或 AST 模式无效 |
| `ParseException` | PHP 源代码无法解析（仅当 `skipParseErrors: false` 时） |

如果请求位置不存在索引，索引搜索方法会抛出 `\RuntimeException`。`greph-index` CLI 提供了一个 `--fallback scan` 选项，可以捕获这些异常并回退到非索引扫描。
