From symfony-skills
Guides organizing a Symfony 7 project's src/ directory into feature modules, configuring services with autowiring, and deciding where classes belong in a modular monolith layout.
How this skill is triggered — by the user, by Claude, or both
Slash command
/symfony-skills:bundle-organizationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Since Symfony 4, application code lives in `src/` under the `App\` namespace — **not** in a custom bundle. Create a bundle only for **reusable library code shared across projects**. For an application, organize `src/` by **feature/module**, not by technical type.
Since Symfony 4, application code lives in src/ under the App\ namespace — not in a custom bundle. Create a bundle only for reusable library code shared across projects. For an application, organize src/ by feature/module, not by technical type.
src/
├── Kernel.php
├── Sales/ # bounded context / module
│ ├── Domain/
│ ├── Application/
│ ├── Infrastructure/
│ └── UI/ # controllers, console commands, CLI
├── Billing/
│ └── ...
└── Shared/ # cross-cutting kernel: base value objects, bus, ids
config/
├── packages/ # per-bundle config (doctrine.yaml, security.yaml, ...)
│ └── prod/ · dev/ · test/ # env overrides
├── routes/ # route imports
├── services.yaml # app service wiring
└── bundles.php # registered bundles
src/
├── Controller/ # ❌ for a large app, everything-in-one-folder doesn't scale
├── Entity/
├── Repository/
└── Service/
This is fine for a tiny app, but in a domain of any size it scatters one feature across five folders. Group by module first, then layer inside.
Rely on Symfony's defaults — autowiring + autoconfiguration + resource autodiscovery:
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/Kernel.php'
- '../src/**/Domain/**/{Entity,ValueObject}' # plain objects, not services
App\Sales\Domain\OrderRepository: '@App\Sales\Infrastructure\Doctrine\DoctrineOrderRepository'
#[Autowire] at the point of use — don't sprinkle %env()% reads:public function __construct(
#[Autowire('%env(int:ORDER_TTL_SECONDS)%')] private readonly int $orderTtl,
#[Autowire(service: 'app.payment.http_client')] private readonly HttpClientInterface $client,
) {}
#[AutowireIterator] / #[TaggedIterator] for strategy sets:public function __construct(
#[AutowireIterator('app.payment_gateway')] private readonly iterable $gateways,
) {}
| Class | Location |
|---|---|
| Doctrine entity / aggregate | src/<Module>/Domain/ (or Domain/Model/) |
| Repository interface (port) | src/<Module>/Domain/ |
| Repository Doctrine impl | src/<Module>/Infrastructure/Doctrine/ |
| Application service / handler | src/<Module>/Application/ |
| Controller | src/<Module>/UI/Http/ (or top-level Controller/ in small apps) |
| Console command | src/<Module>/UI/Cli/ |
| DTO (request/response) | src/<Module>/UI/Http/Dto/ |
| Value object | src/<Module>/Domain/ |
| Custom Doctrine type | src/Shared/Infrastructure/Doctrine/Type/ |
secrets:set) or .env.local — never commit real secrets.%env(int:…)%, %env(bool:…)%, %env(json:…)%.$_ENV / getenv() in app code — inject the value.Only for shareable libraries. Modern bundles use AbstractBundle:
final class AcmePaymentBundle extends AbstractBundle
{
public function configure(DefinitionConfigurator $definition): void { /* config tree */ }
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$container->import('../config/services.yaml');
}
}
AppBundle for application code — modern apps use src/ under App\, no app bundle.Controller/, Entity/, Service/ for a large domain — organize by module first.services.yaml — rely on autowiring + autodiscovery; configure only the exceptions.getenv() / reads $_ENV inside a service — inject the value via #[Autowire('%env(...)%')].Domain plain objects from autoregistration..env — use the secrets vault or .env.local.npx claudepluginhub fatonh/symfony-skills --plugin symfony-skillsProvides Symfony framework reference with architecture patterns, DDD integration, clean architecture checklists, common violations, and antipatterns for auditing PHP projects.
Structures Symfony apps with ports & adapters / clean architecture, keeping the domain framework-free. Use for complex, long-lived domains where isolating business logic from the framework pays off.
Implements Clean Architecture, Hexagonal (Ports & Adapters), and Domain-Driven Design patterns in PHP 8.3+ with Symfony 7.x. For enterprise app architecture, legacy refactoring, DDD, and testable backends.