Attainly/ai/backend-context.md

140 lines
6.4 KiB
Markdown

# Backend context
Read `ai/shared.md` first. This file covers backend-specific rules.
## Project context
**Stack:** PHP 8.4, Laravel 13, PHPUnit, Larastan, Composer.
**Location:** `backend/`.
The backend follows a domain-oriented structure. Existing areas use entities,
DTOs, repository interfaces, Eloquent implementations, use cases, and test
fakes. Extend those patterns when adding behavior to an established area. Do
not add speculative layers or interfaces that the requested behavior does not
need.
The Vue application remains a separate project under `frontend/website/`.
Keep frontend source, dependencies, builds, and delivery out of the backend
unless the user explicitly asks to integrate them. The backend root route is
intentionally unclaimed; the built-in health endpoint is `/up`.
## Code patterns
- Inspect similar domain code before adding a new entity, DTO, repository,
use case, controller action, or fake.
- Entities own domain state and behavior and expose descriptive methods.
- DTOs are explicit data containers for creation or transfer between seams.
- Repository interfaces define domain-facing persistence operations. Keep
Eloquent details in their implementations and register bindings in the
application provider.
- Put reusable business behavior in a use case. Give a use case a request DTO
when it accepts a payload; call `execute()` directly when it has no input.
- Document use-case exceptions with `@throws` when callers are expected to
handle them.
- Keep controllers thin. Translate HTTP input to use-case input and domain
output to the response contract.
- Catch only documented, expected exceptions at the controller boundary. Let
unexpected exceptions reach Laravel's exception handler.
- Fake repositories are in-memory implementations used by unit tests. Return
a new entity instance from create and lookup methods rather than exposing a
stored reference.
- Use Eloquent relationships and query scopes consistently rather than
duplicating query fragments.
## Unit tests
- Follow the existing organization under `tests/Unit/<Area>/`.
- Use fake repositories and fake collaborators for the behavior under test.
Construct unrelated dependency entities directly instead of routing them
through additional repositories.
- Test use-case branches at the use-case seam. Do not repeat every branch in
controller or HTTP tests.
- Keep deletion behavior tests in the relevant deletion use-case suite. Do
not test deletion policy through entity, repository, or database-constraint
tests.
- Plain entities, value objects, use cases, middleware, and controller units
should extend `PHPUnit\Framework\TestCase` when they do not need Laravel.
- Extend `Tests\TestCase` only when a test needs Laravel's container, facades,
database, routing, or HTTP kernel.
- Reserve direct Eloquent repository feature tests for persistence mapping,
query behavior, or database constraints that cannot be proven through a
plain unit test.
## Feature tests
- Feature tests exercise the HTTP seam: routing, middleware, real Eloquent
bindings, cookies, persistence, validation, and response shape.
- Feature tests are additive to unit tests. Prefer a happy path and the
relevant authorization guard instead of duplicating all business branches.
- Place endpoint tests under `tests/Feature/<Area>/`, extend
`Tests\TestCase`, and use `RefreshDatabase` when database state is involved.
- Do not keep tests that only assert an obsolete column or other removed
schema detail is absent. Such assertions may verify a removal while it is
being developed, but discard them afterward and retain behavioral coverage.
- Development and runtime use PostgreSQL through the local Unix socket.
PHPUnit uses SQLite `:memory:` as configured in `phpunit.xml`, so feature
tests are self-contained and do not need the worktree stack.
- Never point `RefreshDatabase` tests at the development PostgreSQL database.
- Keep mail on PHPUnit's `array` transport unless a test explicitly exercises
a real mail integration.
- Authentication uses a custom database-session cookie, not a Laravel guard.
`actingAs()` does not apply. Credentialed requests use
`withCredentials()` and `withUnencryptedCookie()` because API middleware
reads the raw cookie.
- Read an unencrypted response cookie with
`$response->getCookie($name, false)`.
## PHP rules
- Put imports at the top of the file. Do not use inline fully qualified class
names when a normal `use` statement is clearer.
- Do not use arrow functions. Use regular anonymous functions.
- Do not use first-class callable syntax. Use regular anonymous functions for
callbacks so the invocation is explicit.
- Do not add default values to function or constructor parameters. Pass every
argument explicitly, including nullable arguments.
- Use descriptive names for classes, methods, parameters, and local
variables.
- Document exceptions with `@throws` when a caller is expected to handle
them.
## Seeders
- Keep `DatabaseSeeder` as the orchestrator.
- Split substantial seed data into one seeder per domain concept and invoke
them in dependency order.
- Do not add production repository or model APIs solely to make seeding
convenient.
- Use existing lookup methods for cross-seeder relationships. When a group of
records only makes sense together, keep it in one seeder and retain local
references.
## Migrations
- Attainly is not in production yet.
- While no production database exists, edit the original `create_*` migration
when changing a table instead of accumulating follow-up alter migrations.
- Keep one migration file per table during this pre-production phase.
- Rebuild the development database with:
```sh
direnv exec "$(git rev-parse --show-toplevel)" \
php artisan migrate:fresh --seed
```
- Once a production database exists, replace this policy with additive,
forward-only migrations.
## Backend workflow
- Run the focused PHPUnit test while developing.
- Use `just backend-types-check` for Larastan and `just backend-test` for the
full PHPUnit suite during iteration.
- The completion gate runs `backend-test-unit` and `backend-test-feature` as
concurrent processes. This is safe because each process owns its own
in-memory SQLite database.
- Fix failures caused by the change. Report unrelated baseline failures
precisely rather than expanding scope silently.
- The shared `just test-all` command is the required completion gate. Focused
backend recipes never replace it.