Skip to content

Commit b734d9e

Browse files
ajthinkingclaude
andcommitted
Keep the CLI out of the PHP API
The previous commit widened five endpoints from `class` to `classLike`, changed the PSR-2 printer's return-type spacing, added enum() and enumCase() to the query builder, and put --json on archetype:errors. None of that belongs in a change whose job is to add a command line, and one of them was an outright regression: on a file declaring both an interface and a class, className() started answering about the interface and property() started writing into it, which is not valid PHP. All of it is reverted. `src/` is now the new `src/Console` tree plus two lines in the service provider, and nothing an existing user calls behaves differently. The console pays for that in reach, and says so rather than pretending otherwise. The endpoints it drives address `class` declarations, so the property, constant, interface, trait, parent-class and rename operations refuse an enum, interface or trait outright: ERR app/Enums/Status.php archetype:add-implements only works on classes, and this is an enum Refusing up front rather than after the fact matters for the three operations that import a name before using it. add-implements on an enum used to write the import, fail to add the interface, and report OK — a half-done change is indistinguishable from a whole one once the file has changed, so the render-and-compare check cannot catch it. inspect, show, find, the method operations, add-case and set-array-key have no such limit; they read and write the declaration through the console's own code whatever it is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjUMv5rFTVJMr6F1J7bx7x
1 parent fab6632 commit b734d9e

29 files changed

Lines changed: 153 additions & 113 deletions

CHANGELOG.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

99
## [2.1.0] - 2026-08-29
1010

11-
Adds a command line to Archetype. Every existing PHP API is untouched; a handful
12-
of endpoints now reach constructs they previously matched but silently ignored.
11+
Adds a command line to Archetype. The PHP API is untouched — no endpoint, no
12+
printer and no query builder changes. Everything new lives in `src/Console`.
1313

1414
### Added
1515

@@ -28,13 +28,17 @@ of endpoints now reach constructs they previously matched but silently ignored.
2828
- Every operation takes a single target, which is a path, a class name, or a
2929
directory — where a directory means every class beneath it, narrowed with
3030
`--extends`, `--implements`, `--uses-trait` or `--matching`.
31-
- Every operation takes `--json`.
31+
- Every operation but `errors` takes `--json`.
3232
- Every mutation re-renders the file and compares before reporting. One that
3333
matched nothing exits non-zero rather than reporting a success that wrote
3434
nothing; one whose change is already present reports `SKIP`, which makes the
3535
operations safe to repeat.
3636
- Every mutation answers with a diff of what it changed, and takes `--dry-run`
3737
to show that diff without writing.
38+
- An operation that cannot act on the construct it was pointed at refuses before
39+
writing anything, rather than writing the part it can. `add-implements`,
40+
`add-trait` and `set-extends` import a name before using it, so a half-done
41+
change would otherwise look like a whole one.
3842
- `archetype apply` runs a script of operations in one invocation, reading a file
3943
or standard input.
4044
- `set-casts` writes to whichever casting mechanism a model already uses — the
@@ -44,18 +48,14 @@ of endpoints now reach constructs they previously matched but silently ignored.
4448
`toArray()`, `casts()` and `definition()` keep their contents.
4549
- `add-relation` covers all eleven Eloquent relation types, with pivot tables,
4650
explicit keys, `withPivot`, `withTimestamps` and a custom pivot model.
47-
- `enum()` and `enumCase()` query methods on the `ASTQueryBuilder`.
48-
- `php artisan archetype:errors` takes `--json`.
4951

50-
### Changed
52+
### Known limits
5153

52-
- `className()`, `classConstant()`, `useTrait()` and `property()` now match any
53-
class-like declaration rather than only `class`, so they work on enums,
54-
interfaces and traits. `implements()` matches classes and enums. Previously
55-
these silently did nothing on anything but a class.
56-
- The PSR-2 pretty printer prints `function name(): Type` rather than
57-
`function name() : Type`. Only newly printed declarations are affected;
58-
untouched code keeps its own formatting.
54+
- The property, constant, interface, trait, parent-class and rename operations
55+
work on classes only, because the endpoints they drive address `class`
56+
declarations. On an enum, interface or trait they refuse and write nothing.
57+
`inspect`, `show`, `find`, the method operations, `add-case` and
58+
`set-array-key` have no such limit.
5959

6060
## [2.0.1] - 2026-08-25
6161

docs.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ These options are rejected on a single-file target rather than ignored.
200200
|---|---|
201201
| `--json` | Emit JSON instead of the compact line format |
202202

203+
`errors` predates this console and does not take it.
204+
203205
### Options every mutation takes
204206

205207
| Option | Effect |
@@ -367,8 +369,7 @@ archetype add-case app/Enums/ProjectStatus.php OnHold on_hold
367369
archetype add-case app/Enums/Suit.php Spades # pure enum, no backing value
368370
```
369371

370-
Constants work on classes, interfaces, enums and traits. New enum cases are
371-
added after the ones already there.
372+
New enum cases are added after the ones already there.
372373

373374
### Methods
374375

@@ -383,6 +384,23 @@ archetype remove-method app/Models/Project.php isActive
383384
Methods can be added to a class, enum, interface or trait, and are appended
384385
after the methods already there.
385386

387+
### What the console will not do
388+
389+
`set-property`, `add-to-property`, `empty-property`, `remove-property`,
390+
`set-casts`, `set-const`, `remove-const`, `add-implements`, `add-trait`,
391+
`set-extends` and `rename-class` work on classes only. On an enum, interface or
392+
trait they refuse and write nothing, rather than writing the part they can and
393+
reporting success:
394+
395+
```
396+
$ archetype add-implements app/Enums/Status.php 'App\Contracts\HasColor'
397+
ERR app/Enums/Status.php archetype:add-implements only works on classes, and this is an enum
398+
```
399+
400+
`inspect`, `show`, `find`, `add-method`, `replace-method`, `remove-method`,
401+
`add-case` and `set-array-key` have no such limit — they read or write the
402+
declaration whatever it is.
403+
386404
### Several operations in one call
387405

388406
```bash

readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ OK app/Models/User.php $fillable +1
256256
Three rules hold for every operation that writes:
257257

258258
* it re-renders the file and compares, so a change that matched nothing is an
259-
error and exits non-zero — never a success that wrote nothing;
259+
error and exits non-zero — never a success that wrote nothing, and never half
260+
a change reported as a whole one;
260261
* it answers with a diff, so you do not have to read the file back to see what
261262
happened;
262263
* a change already applied is `SKIP`, not `OK` and not an error, so operations

src/Commands/ErrorsCommand.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
class ErrorsCommand extends Command
1111
{
12-
protected $signature = 'archetype:errors {--json : Emit JSON instead of a table}';
12+
protected $signature = 'archetype:errors';
1313
protected $description = 'List dirty files';
1414
protected $result;
1515
protected $errors;
@@ -33,15 +33,6 @@ public function handle()
3333
}
3434
});
3535

36-
if ($this->option('json')) {
37-
$this->output->writeln(json_encode([
38-
'ok' => $this->errors->isEmpty(),
39-
'errors' => $this->errors->values()->all(),
40-
], JSON_UNESCAPED_SLASHES));
41-
42-
return;
43-
}
44-
4536
if ($this->errors->isEmpty()) {
4637
$this->info('No errors found!');
4738
return;

src/Console/Commands/AddImplementsCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ protected function perform(): int
1818
$interfaces = $this->argument('interfaces');
1919

2020
return $this->mutate(function (LaravelFile $file) use ($interfaces) {
21+
$this->requireKind($file, ['class']);
22+
2123
$existing = array_map(fn ($name) => class_basename($name), $file->implements());
2224

2325
$wanted = array_values(array_filter(

src/Console/Commands/AddToPropertyCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected function perform(): int
2525
$values = $this->argument('values');
2626

2727
return $this->mutate(function (LaravelFile $file) use ($name, $values) {
28-
$this->requirePropertyHolder($file);
28+
$this->requireKind($file, ['class']);
2929

3030
$visibility = $this->visibilityOf($file, $name, $this->option('visibility'));
3131
$existing = $file->property($name);

src/Console/Commands/AddTraitCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ protected function perform(): int
1818
$traits = $this->argument('traits');
1919

2020
return $this->mutate(function (LaravelFile $file) use ($traits) {
21+
$this->requireKind($file, ['class']);
22+
2123
$existing = array_map(fn ($trait) => class_basename($trait), $file->useTrait());
2224

2325
$wanted = array_values(array_filter(

src/Console/Commands/EmptyPropertyCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ protected function perform(): int
1919
$name = $this->argument('name');
2020

2121
return $this->mutate(function (LaravelFile $file) use ($name) {
22-
$this->requirePropertyHolder($file);
22+
$this->requireKind($file, ['class']);
2323

2424
if (! (new Introspector($file))->hasProperty($name)) {
2525
return $this->unchanged("no \$$name");

src/Console/Commands/RemoveConstCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ protected function perform(): int
1919
$name = $this->argument('name');
2020

2121
return $this->mutate(function (LaravelFile $file) use ($name) {
22+
$this->requireKind($file, ['class']);
23+
2224
$present = collect((new Introspector($file))->constants())
2325
->contains(fn ($constant) => $constant['name'] === $name);
2426

src/Console/Commands/RemovePropertyCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ protected function perform(): int
1919
$name = $this->argument('name');
2020

2121
return $this->mutate(function (LaravelFile $file) use ($name) {
22+
$this->requireKind($file, ['class']);
23+
2224
if (! (new Introspector($file))->hasProperty($name)) {
2325
return $this->unchanged("no \$$name");
2426
}

0 commit comments

Comments
 (0)