* [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.
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 callingjson_decode()orSerializerInterfaceby hand. If neither package is installed yet,composer requirethem rather than falling back to manual parsing. - Use constructor property promotion, and
readonlyfor DTOs and value objects. Don't mark a servicereadonlyif it might becomelazy: true: a lazy proxy can't extend areadonlyclass. - 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 withsymfony console ...(orbin/consolewhen the Symfony CLI isn't available). - When something fails, read
var/log/dev.logand the web profiler (/_profiler) before changing code. - If
maker-bundleis installed, preferbin/console make:*with every argument passed up front and--no-interactionwhere 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, thendoctrine:migrations:migrate), neverdoctrine:schema:updateor hand-written SQL. .envis 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, pluslint:twig templates/andlint: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.jsonif it differs).