Attainly/ai/backend-context.md

5.9 KiB

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.
  • 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 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:

    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.
  • 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.