
# 缓存 AST 搜索

缓存 AST 搜索是对 [事实索引](https://greph.inline0.dev/zh/docs/indexed/ast)的补充性预热缓存模式。它不会存储提取出的事实并重新解析匹配文件，而是将序列化的语法树存储在磁盘上。后续查询会反序列化这些语法树并直接运行匹配器，从而完全跳过 PHP-Parser 流程。

缓存通过 `greph-index ast-cache` CLI 子命令以及 `Greph::buildAstCache()` / `Greph::searchAstCached()` 门面方法提供。它也支持多索引和基于清单的集合搜索。

## 构建缓存

```bash
# 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
```

默认情况下，缓存存储在 `<root>/.greph-ast-cache/`。缓存构建通常是三种索引模式中最慢的，因为每个文件都必须解析一次。

构建成功后会输出一行摘要：

```text
Built AST cache for 2547 files in .greph-ast-cache (2541 cached trees, +2541 ~0 -0 =0)
```

缓存语法树的数量通常略少于文件数量，因为解析失败（并被静默跳过）的文件不会生成缓存条目。如果希望在搜索时显示解析错误，请传入 `--strict-parse`。

## 刷新缓存

```bash
./vendor/bin/greph-index ast-cache refresh .
```

`refresh`会重新遍历索引根目录，重新解析已更改的文件，并且只重写发生变化的缓存语法树。

生命周期配置与其他预热模式相同：

- `static`：从不自动检查新鲜度或修改数据
- `manual-refresh`：在 `stats` 中报告过期状态，但搜索时从不刷新
- `opportunistic-refresh`：仅当变更集低于配置的阈值时，才在搜索期间刷新
- `strict-stale-check`：拒绝过期搜索，而不执行刷新

## 查询缓存

```bash
./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
```

标志集合与 [`ast-index search`](https://greph.inline0.dev/zh/docs/indexed/ast#querying-the-index)完全相同，包括处理缓存缺失的 `--fallback scan`。

### 多索引缓存搜索

重复指定 `--index-dir`，即可在一次调用中搜索多个预热缓存：

```bash
./vendor/bin/greph-index ast-cache search \
  --index-dir ../wordpress/.greph-ast-cache \
  --index-dir ./.greph-ast-cache \
  --show-index-origin \
  'array($$$ITEMS)' .
```

### 基于清单的缓存集合

如果你已经维护了命名的预热集合，同一份清单也可以用于缓存 AST 搜索：

```bash
./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)' .
```

### 规划器诊断

`--trace-plan`会将缓存 AST 规划器的决策报告到 `stderr`，包括：

- 搜索了哪些预热缓存
- 是否运行了过期刷新逻辑
- 按文件缩小范围后的候选文件数量
- 为精确结构匹配而反序列化的缓存语法树数量

## 缓存与事实索引

两种模式都能加速 AST 搜索，但针对的工作负载不同。

| 方面 | 事实索引（`ast-index`） | 缓存（`ast-cache`） |
|---|---|---|
| 存储内容 | 提取出的事实（调用名称、类名、声明） | 序列化后的语法树 |
| 构建成本 | 较低 | 较高（解析所有内容） |
| 构建大小 | 较小 | 较大 |
| 查询流程 | 按事实筛选，然后解析并匹配候选项 | 反序列化语法树，然后匹配 |
| 最适用场景 | 具有可识别头部（调用名称、类名）的模式 | 区分特征在于结构而非名称的模式 |
| 最不适用场景 | 头部是元变量的模式 | 不再适合 OS 页面缓存的语法树上的冷查询 |

实际上，事实索引的冷查询速度最快，而缓存的热查询速度最快。选择适合你工作负载的模式，也可以同时构建两者并让工具自行选择。

## 编程方式使用

```php
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`接受与原生 AST 搜索相同的 `AstSearchOptions`，并返回相同的 `list<AstMatch>` 结构。唯一的区别是，已在缓存中的文件会跳过解析器流程。对于多索引或基于清单的流程，请使用 `searchAstCachedMany(...)` 和 `searchAstCachedSet(...)`。

## 适用场景

以下情况适合使用缓存 AST 模式：

- 你会针对同一代码库反复运行结构化查询。
- 先前运行的瓶颈是 PHP-Parser（性能分析中表现为耗时集中在词法分析器和解析器）。
- 你有足够的磁盘空间存储序列化语法树。

如果查询通常只命中代码库的一小部分，而且匹配结果集中在可识别的调用名称或类名附近，那么[事实索引](https://greph.inline0.dev/zh/docs/indexed/ast)通常更合适。如果查询覆盖代码库的范围很广，但你能接受一次性的解析成本，那么缓存更合适。
