# Backend context > Read `ai/shared.md` first. This file only covers backend-specific rules. ## Project Context **Stack:** PHP 8.x, Slim 4, PHP-DI/Slim-Bridge, PHPUnit (dev), Composer. Persistence: PostgreSQL. **Architecture:** Domain-Driven Design. Code is organized by domain entity under `backend/app/` (PSR-4 `App\\`) into Entities, DTOs, Repositories, Use Cases, and Fakes (in-memory repos for tests). The entity list is intentionally omitted here - update this section as entities land. ## Code patterns - Look at similar entities for reference before writing anything new - Entities: constructor with properties, getters - DTOs: simple data containers for creation (e.g. `CreateElementDto`) - Repositories: interfaces that define data access - Do not write unit tests for concrete repository implementations (e.g. `Postgres*Repository`). They are exercised by e2e tests. Use cases are tested with fake repositories. - Repository methods that find records by a foreign key should accept the related entity, not a raw id (e.g. `findBySet(Set $set)`, not `findBySetId(int $setId)`). - Use cases: business logic with Request objects - When throwing exceptions, add `@throws` docblock - Fakes: in-memory implementations for testing - Live under `tests/Fakes/` - Find/lookup methods must return a new instance of the entity, not the stored reference - Tests: follow `tests/Unit/[Entity]/UseCases/` layout - In `setUp`, only use fake repositories for entities under test - construct dependency objects directly with `new` (e.g. `new Set(...)`) instead of creating them through their fake repositories ## PHP rules - Imports: always put `use` statements at the top of the file, never use inline imports (e.g. `\App\Foo\Bar::class`) - Closures: never use arrow functions (`fn () =>`) - always use regular anonymous functions (`function () { return ...; }`) ## Pre-commit Run `phpcs` on worked-on directories before committing (phpcs ruleset to be added to the repo; the Nix flake already provides `php-codesniffer`). Run `./vendor/bin/phpunit tests` and confirm green. ## LLM anti-patterns Constructs LLMs default to that this project forbids. Name the trap explicitly so you catch yourself before writing it. - Arrow function: - Forbidden: `fn ($node) => $node->getId()` - Required: `function ($node) { return $node->getId(); }` - Inline FQCN type: - Forbidden: `function f(): \Psr\Http\Message\ResponseInterface` - Required: import `ResponseInterface`, then return `ResponseInterface` - Inline `::class`: - Forbidden: `Container::get(\App\Foo\Bar::class)` - Required: import `Bar`, then call `Container::get(Bar::class)` - Default param: - Forbidden: `function f(int $count = 10)` - Required: `function f(int $count)` - Default in fake: - Forbidden: `public function create(Dto $dto, bool $strict = true)` - Required: no default; every caller passes a value - Lookup returns stored ref: - Forbidden: `return $this->items[$id] ?? null;` - Required: rebuild a new instance with the stored fields - FK lookup by id: - Forbidden: `findBySetId(int $setId)` - Required: `findBySet(Set $set)` - Short variable name: - Forbidden: one-letter or abbreviated names (`$t`, `$n`, `$res`) - Required: explicit names (`$text`, `$node`, `$response`) - Em dash: - Forbidden: comment or docblock containing an em dash character - Required: use `-` When generating code, scan the diff for these patterns before writing it to disk. If you catch one mid-write, rewrite that line.