Skip to main content
Machine-readable rules for AI assistants and agents working in sevendays_backend (Symfony, PHP 8.3). These are the enforceable form of the backend coding standards and the shared AI manifesto. Each rule has a stable ID so it can be cited in review.
ruleset-id: 7days-backend
applies-to: sevendays_backend
language: PHP 8.3
framework: Symfony
id-scheme: BE-<AREA>-NN
derived-from: /development/backend/coding-standards

Rule IDs

Rules use category-scoped IDs: BE-<AREA>-NN, where <AREA> is the (stable, uppercase) section code below and NN is a zero-padded number within that area — for example BE-ASYNC-01.
  • To add a rule, take the next free number in the relevant area (a second async rule is BE-ASYNC-02), so each section grows independently and the ID always tells you the category.
  • IDs are stable. Never renumber an existing rule or reuse the ID of a retired one — mark a dropped rule as retired instead.

Language & tooling

  • BE-LANG-01 — Add declare(strict_types=1); at the top of every new PHP file. The codebase is mid-migration, so new code always opts in.
  • BE-LANG-02 — Target PHP 8.3. Use modern features: backed enums, readonly properties, constructor property promotion, first-class attributes.
  • BE-LANG-03 — Before proposing a change as done, run the per-file diff gates: bin/phpstan-diff.sh, bin/phpcs-diff.sh, bin/static.sh. Never claim a change passes CI without running them.

Coding style

  • BE-STYLE-01 — Follow PSR-12 plus the Symfony (risky) ruleset. Do not hand-format; let the fixer decide. Run vendor/bin/ecs check src --fix before finishing.
  • BE-STYLE-02 — Respect the baked-in house conventions: short array syntax [], strict comparison ===, Yoda style for equality checks, ordered class elements, and no arrow functions. Do not emit fn() => closures.

Static analysis

  • BE-STATIC-01 — New files must pass PHPStan level 4 as a hard floor; aim as high as possible toward the level 8 project goal.
  • BE-STATIC-02Never lower the PHPStan level on a file that already passes higher, and never suppress errors with baseline entries or @phpstan-ignore to make a check pass. Fix the underlying issue instead.

Structure & modules

  • BE-MOD-01 — Keep controllers thin. Business logic lives in services, not controllers.
  • BE-MOD-02 — Put a bigger feature in its own module directory under src/ (e.g. src/Raffle), owning its own config/services.yaml and config/packages.yaml. Follow src/IGaming as the reference DDD layout (Domain / Application / Infrastructure).
  • BE-MOD-03 — Communication between modules is minimal and through a facade. Do not depend on another module’s entities, repositories, or internal services directly.

Multi-project & configuration

  • BE-CONFIG-01 — Keep application code project-agnostic. Never branch on the brand: no if ($projectCode === '7days'), no match (ProjectCodeEnum::UKCC), no per-brand conditionals that fork behavior. A brand check buried in logic is a bug waiting for the other brand.
  • BE-CONFIG-02 — Drive per-brand differences through config or data, not control flow. Values belong in config/project/{project_code}_parameters.yaml (over default_parameters.yaml); differing implementations are swapped as services in config/project/{project_code}_config.yaml; operational differences are DB-driven. Inject the parameter/binding and let config select it — adding or changing a brand should be a config/data change, not a logic edit. See Projects: 7days & UKCC.

Services & DI

  • BE-DI-01 — Use constructor injection and autowiring. Do not pull services from the container manually or add service-locator patterns without reason.

Doctrine, entities & migrations

  • BE-DB-01 — There are four separate databases (draw, user, ticket, event), each with its own connection and entity manager. Put a new entity in the directory matching its database/mapping prefix; the draw EM is the default.
  • BE-DB-02Never query or join across connections/databases in a single query. Coordinate at the service layer or through a module facade.
  • BE-DB-03 — Mapping is attribute-based with the underscore naming strategy.
  • BE-DB-04 — Generate and apply migrations with bin/db_diff and bin/db_migrate (all four namespaces). Do not run bare d:m:migrate.
  • BE-DB-05 — After bin/db_diff, review the generated migration: confirm it landed in the expected namespace and touches only that DB. Clean up auto-generated boilerplate comments, leftover @todos, and empty down() scaffolding.
  • BE-DB-06Never edit a committed migration — create a new one. Always provide a meaningful getDescription().

API conventions

  • BE-API-01 — Use plain Symfony controllers (no API Platform). API controllers extend App\Controller\Api\ApiController.
  • BE-API-02 — Declare routes with #[Route] and document every endpoint with accurate Nelmio / OpenAPI attributes (#[OA\Get], #[OA\Parameter], #[OA\Response], #[Security]) — they are the published API docs.
  • BE-API-03 — In new code use the Symfony Serializer with #[Groups]. JMS Serializer is legacy — only touch it in endpoints that already depend on it; never introduce it in new code.
  • BE-API-04 — Validate input with the Symfony Validator (constraints on request DTOs/models in src/App/Model) or Form types for complex requests.
  • BE-API-05 — Throw from the App\Exception hierarchy; let ApiExceptionSubscriber produce the standard JSON shape. Do not build error responses ad hoc.
  • BE-API-06 — Guard endpoints with #[IsGranted] / voters. Session auth is the default; JWT is used by the iGaming module.
  • BE-API-07 — Mark cacheable endpoints with the custom #[CachedEndpoint] attribute.

Sensitive data

  • BE-SEC-01 — Store PII / secrets with the custom encrypted_data Doctrine type (App\Security\Crypto). Never store sensitive columns as plaintext, and never place secrets or customer data in a prompt or agent context.

Testing

BE-TEST-01 — Any code that calculates something or contains non-trivial logic (pricing, scoring, date/time math, business rules, state machines, parsers) ships with PHPUnit tests. Do not mark such a change complete without them.
  • BE-TEST-02 — Cover meaningful branches and edge cases (zero, negative, empty, boundary), not just the happy path. Keep unit tests fast and free of I/O; use functional tests for HTTP/DB integration. We do not target 100% coverage — skip trivial glue.
  • BE-TEST-03 — Build test state with Zenstruck Foundry factories (tests/Factory) and Stories (tests/Story), not hand-rolled fixtures. Name test classes *Test.php, data providers provider*, and tag with @group unit / @group integration.

Async & messaging

  • BE-ASYNC-01 — Async currently runs on RabbitMQ (OldSoundRabbitMqBundle); Symfony Messenger is not in use yet. Keep consumer/handler classes thin and framework-agnostic — put real logic in a service the consumer calls — so the planned move to Messenger is an adapter swap, not a rewrite.

Design principles & hygiene

  • BE-DESIGN-01 — Aim to comply with SOLID and the relevant PHP-FIG PSRs; follow HTTP semantics (RFC 9110), RFC 9457 Problem Details for errors, and ISO 8601 / UTC for dates. Prefer composition over inheritance; avoid premature abstraction.
  • BE-DESIGN-02Remove unnecessary comments — comment the why, not the what; delete commented-out code and generated placeholders.
  • BE-DESIGN-03 — Use early returns / guard clauses over deep nesting.
  • BE-DESIGN-04Leave it nicer than you found it: clear names, small methods, sensible shape. If a change makes a file harder to read, rethink it.
When any of these rules changes, update it here and in the backend coding standards in the same pull request, so the AI ruleset never drifts from the human docs.