# Backend context > Read `ai/shared.md` first. This file only covers backend-specific rules. ## Project Context **Stack:** PHP 8.5, Slim 4, PHP-DI/Slim-Bridge, PHPUnit 13, Composer. Persistence is JSON-file based (see `Json*Repository` classes); no ORM. **Architecture:** Domain-Driven Design. Code is organized by domain entity under `app/` (Auth, Node, Plan, ScheduledNode, Text, User, View, ValueObjects) into Entities, DTOs, Repositories, Use Cases, and Fakes (in-memory repos for tests). ## Code patterns - Look at similar entities (e.g. `Node`, `Text`) for reference - Entities: constructor with properties, getters - DTOs: simple data containers for creation (e.g. `CreateTextDto`) - Repositories: interfaces that define data access - Do not write unit tests for concrete repository implementations (e.g. `JsonNodeRepository`). They are exercised by e2e tests. Use cases are tested with fake repositories. - Use cases: business logic with Request objects - When throwing exceptions, add `@throws` docblock - Fakes: in-memory implementations for testing - Look at `tests/Fakes/` for examples - Find/lookup methods must return a new instance of the entity, not the stored reference - Tests: follow existing patterns in `tests/Unit/[Entity]/UseCases/` - In `setUp`, only use fake repositories for entities under test - construct dependency objects directly with `new` (e.g. `new Text(...)`) 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 `php-cs-fixer fix` on worked-on directories before committing (uses the existing `.php-cs-fixer.dist.php` config). ## LLM anti-patterns Constructs LLMs default to that this project forbids. Name the trap explicitly so you catch yourself before writing it. | Anti-pattern | Forbidden | Required | |---|---|---| | Arrow function | `fn ($x) => $x->getId()` | `function ($x) { return $x->getId(); }` | | Inline FQCN type | `function f(): \Psr\Http\Message\ResponseInterface` | `use Psr\Http\Message\ResponseInterface;` then `function f(): ResponseInterface` | | Inline `::class` | `Container::get(\App\Foo\Bar::class)` | `use App\Foo\Bar;` then `Container::get(Bar::class)` | | Default param | `function f(int $count = 10)` | `function f(int $count)` | | Default in fake | `public function create(Dto $dto, bool $strict = true)` | no default; every caller passes a value | | Lookup returns stored ref | `return $this->items[$id] ?? null;` | rebuild a new instance with the stored fields | | Short variable name | `$t`, `$n`, `$res`, `$req`, `$e` | `$text`, `$node`, `$response`, `$request`, `$exception` | | Em dash | `// fetches user — by email` | `// fetches user - by email` | When generating code, scan the diff for these patterns before writing it to disk. If you catch one mid-write, rewrite that line.