# CLAUDE.md: Symfony 8

## Commands
- `symfony server:start` (or `php -S`) · `bin/console cache:clear` · `bin/phpunit` · `vendor/bin/phpstan analyse` · `vendor/bin/php-cs-fixer fix --dry-run`
- Makers: `bin/console make:entity`, `make:controller`, `make:migration`; then `bin/console doctrine:migrations:migrate` (local only).

## Conventions
- Attributes everywhere: `#[Route('/orders/{id}', methods: ['GET'])]`, Doctrine mapping attributes, `#[AsMessageHandler]`, `#[AsCommand]`.
- Services are autowired and autoconfigured; inject via constructor with `readonly` promoted properties. Don't fetch services from the container.
- Controllers are thin. Map input to DTOs with `#[MapRequestPayload]` / `#[MapQueryString]` and Validator constraints on the DTO; return `JsonResponse` or render Twig.
- Load entities with `#[MapEntity]` or repositories, and check access with voters (`#[IsGranted('EDIT', subject: 'order')]`).
- Doctrine: repositories with typed query methods; avoid N+1 with joins in DQL/QueryBuilder; write through the `EntityManager` in services, with one `flush()` per use case.
- Every schema change is a migration; review the generated SQL.
- Async work through Messenger messages (simple DTOs) and handlers; configure transports and retries in `messenger.yaml`.
- Config and secrets via env vars and `bin/console secrets:set`; never commit real `.env.local` values.
- Twig auto-escaping stays on; CSRF protection on forms.

## Tests
`WebTestCase` for HTTP, `KernelTestCase` for services, a test database reset per run. PHPStan level from the project config must stay green.
