PHP Structured Edit
Use php-ast-edit for PHP writes while this skill is active. The tool parses snippets,
changes the AST, and prints the result. After rejection, correct the cause; do not
fall back to text mutation.
First use
Resolve the executable once: repository bin/php-ast-edit, project vendor/bin/php-ast-edit,
installed command, or this skill's scripts/php-ast-edit wrapper. Use help if needed;
install missing engine dependencies before retrying.
Auto mode uses format-preserving printing unless applicable configuration enables
canonical printing. Explicit printer choices override auto mode. Normalization is
optional; doctor diagnoses canonical setup. Read the formatting reference when
configuring formatting.
Workflow
- Find the relevant code with normal search or an LSP.
- Prefer named targets:
{"select":"method:Checkout::submit"}. Other selectors:
class:, interface:, trait:, enum:, function:, property:Foo::$items,
const:Foo::LIMIT. Ambiguous names are refused. Use inspect for unnamed targets;
retain its ref and sha256.
- One edit against a named target is one call, with no payload file:
apply --file F.php --select method:Foo::bar --op rename_variable --from a --to b.
Batch related edits across files in one apply. Include sha256 when relying on a
read snapshot. After STALE_SOURCE, reread and reassess; never drop the guard.
Use "report":"agent" when a declared check's verdict is what you need, or the diff
would be large: it carries the check proof and drops the diff. Measured otherwise,
fetching that diff back costs more than it saved (benchmarks/agent-economics/
results/2026-09-10-agent-report).
- Supply compact valid snippets. Import or qualify external types in namespaced PHP,
e.g.
\\DateTimeImmutable in JSON. The printer handles indentation.
- In
agent reports read outcome, checks, checksFailed and each file's open;
checks: "none_declared" means nothing verified the edit, which is not "passed".
git diff -- <path> has the diff, from the snapshot beforeSha256 names. In full and
compact reports read effects, diff, all warnings and validation, where parsed
and legacy valid mean parser success, and follow checkIds to top-level verify.
Failed checks need repair; skipped or unrun checks remain outstanding where required.
- A passed configured command satisfies that same check on unchanged inputs. Repeat it
after relevant changes, or run additional checks required by the task.
alreadyRun
names the project checks that passed on the written files: while nothing else changed,
do not rerun those by hand. Use supplied
exact-byte evidence for its stated scope instead of rereading solely to reconfirm it.
Neither passing tests nor byte preservation proves reference completeness. Report
changed symbols and checks actually run; distinguish declarations from call sites.
An intended edit leaves a Git diff. Avoid repository-wide format for a local edit.
Minimal transaction:
{"files":[{"path":"src/Registry.php","edits":[{"target":{"select":"class:Registry"},"operation":"add_member","php":"public function register(string $name): void {}"}]}]}
php-ast-edit apply --input edits.json
"report":"full" (the default) retains per-file verification. Report mode changes
presentation only; checksPassed: null means no checks ran — agent says the same thing
as checks: "none_declared", which is harder to misread. agent is versioned by
reportVersion; full and compact are not.
Choose the narrow operation
- Empty list:
insert_into with property and position; class members: add_member.
- Replace any node:
replace_node; change a slot: replace_child.
- Change code inside a named method: keep the method as target,
replace_expression
or replace_statement with match (the code as it is) and php (the replacement).
- New file:
mode: create, full PHP including <?php, default expectAbsent guard.
Delete: mode: delete with the snapshot hash.
- Local rename:
rename_variable on the enclosing function-like scope with from/to.
Binding collisions are rejected; dynamic variables remain limited.
- Method rename:
rename_method with to on method:Class::name. Public and inherited
methods are renamed across the project through Phpactor (doctor shows its setup);
read renames.notRenamed for YAML, TypoScript and Fluid mentions. Use set_name for an
intentionally declaration-only change.
- SQL/HTML/JSON inside a PHP literal:
set_string on its Scalar_String node.
- Class import:
add_use with value and no target; already-imported is a reported
no-op, a taken name an error. Add it in the same transaction as the code that needs it.
Use contexts --operation <name> before guessing unfamiliar arguments.
Parser and host lint passes do not prove application behavior.
References, when needed
- Operations: full schema, selectors, guards,
parseAs, reports.
- Formatting: preserving layout, optional normalization.
- Enforcement: optional hook and its limits.
1---2name: php-structured-edit3description: Use when editing PHP source: renaming a method, function or variable and its call sites; adding, changing or removing a method, property, constant, parameter, return type, attribute or use import; inserting or replacing a statement, expression or call argument; changing a signature, visibility or docblock; editing a PHP string literal; creating or deleting a whole PHP file. Reach for this before Edit or sed on a .php file. Use ordinary search for discovery. Not for read-only PHP questions or edits to non-PHP files.4---56# PHP Structured Edit78Use `php-ast-edit` for PHP writes while this skill is active. The tool parses snippets,9changes the AST, and prints the result. After rejection, correct the cause; do not10fall back to text mutation.1112## First use1314Resolve the executable once: repository `bin/php-ast-edit`, project `vendor/bin/php-ast-edit`,15installed command, or this skill's `scripts/php-ast-edit` wrapper. Use `help` if needed;16install missing engine dependencies before retrying.1718Auto mode uses format-preserving printing unless applicable configuration enables19canonical printing. Explicit printer choices override auto mode. Normalization is20optional; `doctor` diagnoses canonical setup. Read the formatting reference when21configuring formatting.2223## Workflow24251. Find the relevant code with normal search or an LSP.262. Prefer named targets: `{"select":"method:Checkout::submit"}`. Other selectors:27 `class:`, `interface:`, `trait:`, `enum:`, `function:`, `property:Foo::$items`,28 `const:Foo::LIMIT`. Ambiguous names are refused. Use `inspect` for unnamed targets;29 retain its `ref` and `sha256`.303. One edit against a named target is one call, with no payload file:31 `apply --file F.php --select method:Foo::bar --op rename_variable --from a --to b`.32 Batch related edits across files in one `apply`. Include `sha256` when relying on a33 read snapshot. After `STALE_SOURCE`, reread and reassess; never drop the guard.34 Use `"report":"agent"` when a declared check's verdict is what you need, or the diff35 would be large: it carries the check proof and drops the diff. Measured otherwise,36 fetching that diff back costs more than it saved (benchmarks/agent-economics/37 results/2026-09-10-agent-report).384. Supply compact valid snippets. Import or qualify external types in namespaced PHP,39 e.g. `\\DateTimeImmutable` in JSON. The printer handles indentation.405. In `agent` reports read `outcome`, `checks`, `checksFailed` and each file's `open`;41 `checks: "none_declared"` means nothing verified the edit, which is not `"passed"`.42 `git diff -- <path>` has the diff, from the snapshot `beforeSha256` names. In `full` and43 `compact` reports read `effects`, `diff`, all `warnings` and `validation`, where `parsed`44 and legacy `valid` mean parser success, and follow `checkIds` to top-level `verify`.45 Failed checks need repair; skipped or unrun checks remain outstanding where required.466. A passed configured command satisfies that same check on unchanged inputs. Repeat it47 after relevant changes, or run additional checks required by the task. `alreadyRun`48 names the project checks that passed on the written files: while nothing else changed,49 do not rerun those by hand. Use supplied50 exact-byte evidence for its stated scope instead of rereading solely to reconfirm it.51 Neither passing tests nor byte preservation proves reference completeness. Report52 changed symbols and checks actually run; distinguish declarations from call sites.53 An intended edit leaves a Git diff. Avoid repository-wide `format` for a local edit.5455Minimal transaction:5657```json58{"files":[{"path":"src/Registry.php","edits":[{"target":{"select":"class:Registry"},"operation":"add_member","php":"public function register(string $name): void {}"}]}]}59```6061```bash62php-ast-edit apply --input edits.json63```6465`"report":"full"` (the default) retains per-file verification. Report mode changes66presentation only; `checksPassed: null` means no checks ran — `agent` says the same thing67as `checks: "none_declared"`, which is harder to misread. `agent` is versioned by68`reportVersion`; `full` and `compact` are not.6970## Choose the narrow operation7172- Empty list: `insert_into` with `property` and `position`; class members: `add_member`.73- Replace any node: `replace_node`; change a slot: `replace_child`.74- Change code inside a named method: keep the method as target, `replace_expression`75 or `replace_statement` with `match` (the code as it is) and `php` (the replacement).76- New file: `mode: create`, full PHP including `<?php`, default `expectAbsent` guard.77 Delete: `mode: delete` with the snapshot hash.78- Local rename: `rename_variable` on the enclosing function-like scope with `from`/`to`.79 Binding collisions are rejected; dynamic variables remain limited.80- Method rename: `rename_method` with `to` on `method:Class::name`. Public and inherited81 methods are renamed across the project through Phpactor (`doctor` shows its setup);82 read `renames.notRenamed` for YAML, TypoScript and Fluid mentions. Use `set_name` for an83 intentionally declaration-only change.84- SQL/HTML/JSON inside a PHP literal: `set_string` on its `Scalar_String` node.85- Class import: `add_use` with `value` and no `target`; already-imported is a reported86 no-op, a taken name an error. Add it in the same transaction as the code that needs it.8788Use `contexts --operation <name>` before guessing unfamiliar arguments.89Parser and host lint passes do not prove application behavior.9091## References, when needed9293- [Operations](references/operations.md): full schema, selectors, guards, `parseAs`, reports.94- [Formatting](references/formatting-contract.md): preserving layout, optional normalization.95- [Enforcement](references/enforcement.md): optional hook and its limits.