Naming things
One way, written by the generators, checked by the console
TetherPHP is opinionated about names, because a name is how you find a file and how the console finds it. A route names an Action; the Action's name is the Domain's name is the Responder's name; and tether inspect can show you all three from any one of them only because the rule never bends.
The table is the whole convention. Note is the feature and Store is the operation throughout.
| Thing | Rule | Example |
|---|---|---|
| Feature | A singular noun in PascalCase. One directory of that name under Actions/, Domains/ and Responders/. | Note |
| Operation | A verb in PascalCase. One class per operation, with the same name in all three layers. | Actions\Note\StoreDomains\Note\StoreResponders\Note\Store |
| CRUD operations | The seven verbs make:resource writes, and no others. | Index Create Store Show Edit Update Destroy |
| Result | Named for its shape, never for the operation. final readonly, under Results/, shared by every operation that answers the same way. | Domains\Note\Results\Record — used by Show and Edit alike |
| The shapes | Collection many, Record one, Written a change, Invalid a refusal, Page none of those. | handle(): Written|Invalid |
| Collaborator | A noun at the root of the feature's Domain namespace. Shared by the operations, handed to them by the Action, routed to by nothing. | Domains\Note\Notes (the queries)Domains\Note\Attributes (the rules) |
| View | app/Views/pages/<feature>/<operation>.php, both lowercase. Referred to in dot notation. | pages/note/create.php$this->view('pages.note.create', …) |
| Partial | app/Views/partials/<name>.php, kebab-case. | partials/note-form.php |
| Error view | app/Views/errors/<status>.php. | errors/404.php |
| Route | Kebab-case URI, plural for a resource, {param} for a segment. make:resource --uri sets it. | /notes, /notes/{id}, /notes/{id}/edit |
| Command | app/Commands/<Name>Command.php. Invoked in kebab-case, or namespaced with a colon. | DbSchemaCommand → php tether db:schema |
| Services | One class, App\Services at app/Services.php. A class of your own that it holds goes under app/Services/. | App\Services\Mailer |
| Test | tests/Unit/ for a Domain or Result on its own, tests/Feature/ for a request through the Kernel. <Subject>Test.php. | tests/Unit/AttributesTest.phptests/Feature/NotesTest.php |
| Env key | UPPER_SNAKE_CASE in .env, read with $env->get('DB_DSN'). | APP_NAME, DB_DSN |
The three rules that carry the rest
Same name, three layers. A route names an Action; the Action's name is the Domain's name is the Responder's name. That one-to-one path is what lets php tether inspect Note\Store show you all three, and why a Domain's operations never move into a subdirectory.
Verb or noun. In Domains/Note/, a verb is an operation and a noun is a collaborator. You can tell which is which from the directory listing, and only a kind of thing that comes in numbers — Results/ — gets a subdirectory.
A Result is a shape. Seven operations do not need seven result classes; they need four. If you find yourself writing Results\Show, stop.
Enforced, not suggested
The generators write every one of these. php tether make:action Note Archive puts the file where the table says, with the namespace the table says, and make:resource writes all seven operations and the four shapes in one go. There is no option to put anything anywhere else.
php tether make:feature Blog # Blog/Index in all three layers, Results/Page, pages/blog/index.php
php tether make:action Blog Archive # Blog/Archive beside it; says which of Domain and Responder are missing
php tether make:resource Post --uri=/posts # the seven operations, four shapes, four views
php tether inspect Post\Store # what it is, what it takes, and its Domain and Responder by name
php tether context # the conventions as JSON, for tooling and agents
The demo application is the reference layout: one feature, every row of the table in use.