Files
Johannes WachterandGitHub 04b7d39ecd [FrameworkBundle] Add AGENTS.md for AI coding agents (#1563)
* [FrameworkBundle] Add AGENTS.md for AI coding agents

* [FrameworkBundle] Point CLAUDE.md at AGENTS.md via @-import

* [FrameworkBundle] Add heading to CLAUDE.md

* [FrameworkBundle] Apply review feedback to AGENTS.md

Split the operational bullets out of Conventions into a new Everyday
workflow section, generalise what remains around the best practices page,
and replace the stale-training-data note with a concrete list of
discovery commands.

Keeps three specifics under Conventions: MapRequestPayload over
hand-rolled json_decode(), no readonly on a service that may become
lazy, and symfony/lock for mutual exclusion. Those are the failure modes
the file exists to address, and a general rule does not change a model's
behaviour the way naming them does.

* [FrameworkBundle] Broaden the attribute and injection guidance

Attributes were only shown on controllers. They now cover properties,
commands, listeners, message handlers and service declaration.

The autowiring bullet also contradicted itself: it sent scalar arguments
to an explicit service definition, which is what #[Autowire] exists to
avoid. Injection now points at #[Autowire] and #[Target] first, with a
YAML definition as the last resort.
2026-09-04 17:44:00 +02:00

5.3 KiB

AGENTS.md

This is a Symfony project. Check composer.json for the exact Symfony/PHP version in use, and read symfony.lock to see which recipes ran. Don't assume Doctrine, Twig, API Platform, Messenger, or Lock are installed unless one of those says so.

Ask before generating

If the task doesn't specify, ask rather than guess:

  • Persistence: Doctrine ORM, Doctrine ODM, or none?
  • Interface: server-rendered (Twig), API (Serializer, maybe API Platform), or both?
  • Auth: SecurityBundle, and which authenticator?

If you can't ask (no interactive channel), state the assumption you're making and pick the smallest option (e.g. no persistence layer) rather than scaffolding a full stack nobody asked for.

Adding features: Flex, not hand-wiring

Install new capabilities with composer require <package> (e.g. symfony/lock, symfony/messenger, orm-pack) and let the Flex recipe register the bundle and generate its config. Don't hand-edit config/bundles.php or hand-write a bundle's base config; that's what the recipe is for. Don't skip a good-fit component just because it isn't installed yet; installing it is one command.

Conventions

Follow https://symfony.com/doc/current/best_practices.html to write idiomatic Symfony:

  • Use PHP attributes for framework metadata, and not only on controllers: #[Route], #[MapRequestPayload], #[IsGranted] on actions, #[Assert\...] on properties, #[AsCommand], #[AsEventListener], #[AsMessageHandler], and #[AsAlias] / #[AsTaggedItem] / #[Autoconfigure] on services. No YAML or XML routing.
  • Rely on autowiring and autoconfiguration. Type-hint constructor arguments and let the container resolve them. Where a type-hint can't express it, stay in the class with #[Autowire] (parameters, env vars, expressions) or #[Target] (one of several implementations of an interface). A YAML service definition is the last resort, not the first.
  • Controllers extend AbstractController, stay thin, and delegate to services.
  • Use the framework for what it already does: Form for server-rendered forms, Validator for validation, Serializer for JSON, Messenger for async work, Security (voters, authenticators) for access control, Twig path()/url() instead of hardcoded URLs.
  • Before hand-writing infrastructure (locks, queues, caches, HTTP clients, mailers, schedulers) or reaching for a third-party library, check whether a Symfony component covers it. It usually does.

Three specifics worth spelling out, because they are easy to get wrong:

  • Bind request data with #[MapRequestPayload] / #[MapQueryString] on action arguments, which wires up Serializer and Validator for you, instead of calling json_decode() or SerializerInterface by hand. If neither package is installed yet, composer require them rather than falling back to manual parsing.
  • Use constructor property promotion, and readonly for DTOs and value objects. Don't mark a service readonly if it might become lazy: true: a lazy proxy can't extend a readonly class.
  • Use symfony/lock (LockFactory) for mutual exclusion. A hand-built flag or lock file looks fine in review and is usually wrong under concurrency.

Everyday workflow

  • Run the app with symfony serve -d, and commands with symfony console ... (or bin/console when the Symfony CLI isn't available).
  • When something fails, read var/log/dev.log and the web profiler (/_profiler) before changing code.
  • If maker-bundle is installed, prefer bin/console make:* with every argument passed up front and --no-interaction where supported: makers prompt on a terminal by default, which hangs a non-interactive shell. If a maker still needs interactive input, hand-write the code instead.
  • If Doctrine ORM is installed, schema changes go through migrations (bin/console make:migration, then doctrine:migrations:migrate), never doctrine:schema:update or hand-written SQL.
  • .env is committed and holds defaults only. Real secrets belong in .env.local (git-ignored) or the secrets vault (bin/console secrets:set), read via %env(...)%.

Testing

Install symfony/test-pack if it isn't already. Functional/HTTP tests extend WebTestCase; service-level tests extend KernelTestCase. Run php bin/phpunit (falls back to vendor/bin/phpunit). A feature isn't done until it has a test that exercises it the way a caller would, an HTTP request for a controller or a service call for a service, not just "it didn't throw."

Code style

Symfony's coding standard, the @Symfony php-cs-fixer ruleset (a PSR-12-derived superset). Run vendor/bin/php-cs-fixer fix if friendsofphp/php-cs-fixer is installed; it isn't part of the skeleton by default.

Discover, don't guess

Framework APIs change between versions and your training data may be stale. Look things up in the project instead of relying on memory:

  • bin/console about: versions, environment, paths.
  • bin/console debug:router, debug:container, debug:autowiring <name>, debug:config <bundle>, config:dump-reference <bundle>: what exists and how it is configured.
  • bin/console lint:container, plus lint:twig templates/ and lint:yaml config/ where those packages are installed: validate before running.
  • Read the installed source and docblocks under vendor/.
  • Docs: https://symfony.com/doc/current/ (switch to the version matching composer.json if it differs).