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.

ThingRuleExample
FeatureA singular noun in PascalCase. One directory of that name under Actions/, Domains/ and Responders/.Note
OperationA verb in PascalCase. One class per operation, with the same name in all three layers.Actions\Note\Store
Domains\Note\Store
Responders\Note\Store
CRUD operationsThe seven verbs make:resource writes, and no others.Index Create Store Show Edit Update Destroy
ResultNamed 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 shapesCollection many, Record one, Written a change, Invalid a refusal, Page none of those.handle(): Written|Invalid
CollaboratorA 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)
Viewapp/Views/pages/<feature>/<operation>.php, both lowercase. Referred to in dot notation.pages/note/create.php
$this->view('pages.note.create', …)
Partialapp/Views/partials/<name>.php, kebab-case.partials/note-form.php
Error viewapp/Views/errors/<status>.php.errors/404.php
RouteKebab-case URI, plural for a resource, {param} for a segment. make:resource --uri sets it./notes, /notes/{id}, /notes/{id}/edit
Commandapp/Commands/<Name>Command.php. Invoked in kebab-case, or namespaced with a colon.DbSchemaCommandphp tether db:schema
ServicesOne class, App\Services at app/Services.php. A class of your own that it holds goes under app/Services/.App\Services\Mailer
Testtests/Unit/ for a Domain or Result on its own, tests/Feature/ for a request through the Kernel. <Subject>Test.php.tests/Unit/AttributesTest.php
tests/Feature/NotesTest.php
Env keyUPPER_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.