Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
symfony-bundle-core
symkit/redirect-bundle
claude-code
symfony-bundle
dependency-injection
solid
contracts
scaffolding
code-style
abstract-bundle

Use this skill whenever the user wants to create, scaffold, architect, or configure a Symfony bundle (reusable PHP package for Symfony 7+/8+). Triggers include: 'create a bundle', 'symfony bundle', 'AbstractBundle', 'bundle structure', 'bundle skeleton', 'reusable symfony component', 'bundle configuration', 'DI extension', 'service container for bundle', 'configure()', 'loadExtension()', 'prependExtension()', 'bundle best practices', 'composer.json for bundle', 'SOLID bundle'. Also trigger when the user is building any reusable Symfony package, even if they don't explicitly say 'bundle'. Do NOT trigger for application-level Symfony development that doesn't involve creating a distributable package.

Install
php artisan boost:add-skill symkit/redirect-bundle

Save this content to: .claude/skills/symfony-bundle-core/SKILL.md

# Symfony Bundle Core — Architecture & Scaffolding (Symfony 7+/8+)

This skill covers the full lifecycle of creating a modern Symfony bundle: project scaffolding, AbstractBundle configuration, dependency injection, service registration, routing, translations, testing, and distribution.

For frontend assets (AssetMapper, Stimulus, Twig Components, Live Components), see the `symfony-bundle-ux` skill.
For Doctrine entity modeling in bundles, see the `symfony-bundle-doctrine` skill.
For Flex recipes and distribution, see the `symfony-bundle-flex` skill.
For quality tooling (Makefile, PHPStan, GrumPHP, Infection, CI), see the `symfony-bundle-quality` skill.

---

## 1. SOLID Principles for Bundles

A bundle is code reusable and modifiable by the entire community. SOLID is not optional.

- **S (Single Responsibility)**: One class, one responsibility. Services finely decomposed. No "god service" doing 5 things.
- **O (Open/Closed)**: Extensible without modification via events, interfaces, compiler passes, decorators. `final readonly class` on concrete classes forces extension by composition, not inheritance.
- **L (Liskov Substitution)**: Bundle interfaces are substitutable. No `instanceof` checks on concrete implementations internally.
- **I (Interface Segregation)**: One interface = one precise contract. No "god interface" with 10 methods.
- **D (Dependency Inversion)**: Inject interfaces, never concrete implementations. The host app can decorate or replace any service.

---

## 2. Contract Pattern

- `src/Contract/`: public interfaces of the bundle (BC-safe API)
- Internal services implement these interfaces
- Host app type-hints on interfaces, not concrete classes
- Only interfaces, events, and DTOs from `Contract/` are part of the public API (semver)
- Changes to `Contract/` require a major version bump

---

## 3. Code Style

- `declare(strict_types=1)` at the top of every PHP file
- **`final readonly class`** for: services, handlers, processors, voters, listeners, DTOs
- Exceptions to `final readonly`: Doctrine entities (not `readonly`), bundle class (not `final`), classes explicitly designed for inheritance (documented)
- PHP 8 attributes for routing/validation: `#[Route]`, `#[Assert\...]`. Never `@annotations`.
- **No attributes for service/tag registration** (`#[AsEventListener]`, `#[AutoconfigureTag]`, `#[AsTwigComponent]`, etc.) — in a bundle, all service registration goes through `loadExtension()` or XML config.
- Native function prefix: `\count()`, `\sprintf()`, `\in_array()`, `\array_map()`
- `match` over `switch` when applicable
- Early return to reduce nesting
- Constructor injection only (promoted properties: `private readonly`)
- Never `new Service()`: always dependency injection
- No business logic in controllers, entities, or events
- No hardcoded parameters: use `configure()` + env vars

---

## 4. Scaffolding a New Bundle

### Directory Structure

```
acme-blog-bundle/
├── assets/                    # Frontend source (JS, CSS, Stimulus controllers)
│   ├── controllers/           # Stimulus controllers (*_controller.js)
│   ├── dist/                  # Pre-compiled assets
│   ├── styles/                # CSS/SCSS source files
│   └── package.json           # UX metadata for Stimulus discovery
├── config/
│   ├── definition.php         # Complex config tree (optional)
│   ├── doctrine/              # XML mapping files (Entity.orm.xml)
│   ├── routes.yaml            # Bundle routes
│   └── services.xml           # Service definitions (XML preferred)
├── docs/
│   └── index.md
├── src/
│   ├── AcmeBlogBundle.php     # Main bundle class (extends AbstractBundle)
│   ├── Contract/              # Public interfaces (BC-safe API)
│   ├── Controller/
│   ├── DependencyInjection/
│   │   └── CompilerPass/
│   ├── Entity/
│   ├── Event/                 # Bundle events for extensibility
│   ├── EventSubscriber/
│   ├── Repository/
│   ├── Service/
│   └── Twig/
│       ├── Components/
│       └── Extension/
├── templates/
│   └── components/
├── tests/
│   ├── Functional/
│   ├── Integration/
│   └── Unit/
├── translations/
│   └── AcmeBlogBundle.en.xlf
├── .editorconfig
├── .gitignore
├── .php-cs-fixer.dist.php
├── CHANGELOG.md
├── composer.json
├── deptrac.yaml
├── grumphp.yml
├── infection.json5
├── LICENSE
├── Makefile
├── phpstan.neon.dist
├── phpunit.xml.dist
└── README.md
```

### Naming Conventions

Enforced by the framework — breaking them causes resolution failures:

- **Bundle class**: `{Vendor}{Name}Bundle` → `AcmeBlogBundle`
- **Namespace**: `{Vendor}\{Name}Bundle` → `Acme\BlogBundle`
- **Config alias** (auto-derived, snake_case): `acme_blog`
- **Service prefix**: `acme_blog.` for all service IDs
- **Route name prefix**: `acme_blog_` for all route names
- **Translation domain**: `AcmeBlogBundle`
- **Twig namespace**: `@AcmeBlog` (suffix `Bundle` stripped)

### composer.json

```json
{
    "name": "acme/blog-bundle",
    "type": "symfony-bundle",
    "description": "A concise description",
    "license": "MIT",
    "require": {
        "php": ">=8.2",
        "symfony/config": "^7.0 || ^8.0",
        "symfony/dependency-injection": "^7.0 || ^8.0",
        "symfony/framework-bundle": "^7.0 || ^8.0",
        "symfony/http-kernel": "^7.0 || ^8.0"
    },
    "require-dev": {
        "deptrac/deptrac-src": "^2.0",
        "friendsofphp/php-cs-fixer": "^3.0",
        "infection/infection": "^0.29",
        "nyholm/symfony-bundle-test": "^3.0",
        "phpro/grumphp": "^2.0",
        "phpstan/phpstan": "^2.0",
        "phpunit/phpunit": "^10.0 || ^11.0",
        "symfony/phpunit-bridge": "^7.0 || ^8.0"
    },
    "autoload": { "psr-4": { "Acme\\BlogBundle\\": "src/" } },
    "autoload-dev": { "psr-4": { "Acme\\BlogBundle\\Tests\\": "tests/" } },
    "extra": { "symfony": { "require": "^7.0 || ^8.0" } }
}
```

Rules: only declare actual dependencies, `"type": "symfony-bundle"`, every PHP extension in `require`, never embed vendor code in `src/`, always `^7.0 || ^8.0` for Symfony deps.

---

## 5. The AbstractBundle Class

Always use `AbstractBundle` for new bundles (Symfony 6.1+). Method execution order:

1. `prependExtension()` — FIRST, before config resolution
2. `configure()` — defines the schema tree
3. `loadExtension()` — AFTER config merged+validated
4. `build()` — registers compiler passes

See the complete template in the `scaffold_bundle.php` script.

---

## 6. Service Definitions (XML)

XML preferred (no Yaml component dependency). Defaults: `autowire="true" autoconfigure="true" public="false"`.

Rules:
- Never public by default
- Always prefix service IDs: `acme_blog.service_name`
- Use interface aliases for type-hinting
- `registerForAutoconfiguration()` for custom interfaces

---

## 7. Controllers in Bundles

Never extend `AbstractController`. Inject specific services via constructor.

---

## 8. Routing

Use YAML/XML files in `config/routes.yaml`, not PHP attributes. Prefix all route names: `acme_blog_`.

---

## 9. Translations (XLIFF)

**Every user-facing string must be translated** — no hardcoded text in templates, controllers, forms, validators, or flash messages.

- XLIFF (.xlf) mandatory. Own domain (`AcmeBlogBundle`). Never overwrite other domains.
- Extract **all** strings: Twig (`{% trans %}`, `|trans`), form labels/help/placeholders, constraint messages, controller flash messages, email subjects/bodies, error pages
- Always provide at least `en` and `fr` locales
- Form labels: always explicit `'label' => 'form.field_name'`, never rely on auto-generated labels
- Validator messages: always use translation keys (`'message' => 'validation.field.constraint'`)
- Run `translation:extract` regularly to catch missing keys

---

## 10. Testing

Use `nyholm/symfony-bundle-test`. Always generate at least one integration test. Run `make quality` after every change.

---

## 11. Configurability

Every feature of a bundle must be **activatable, deactivatable, and configurable** via `configure()`:

- Enable/disable any feature (Doctrine, controllers, Twig components, listeners, etc.) via a boolean config key
- Override Doctrine entity classes and repository classes in config (see `symfony-bundle-doctrine` skill)
- Customize any default value (table prefix, translation domain, route prefix, etc.)
- The host app must never need to fork the bundle to customize behavior

## 12. Anti-Patterns Checklist

| Anti-Pattern | Impact |
|---|---|
| Extending `AbstractController` | Couples to entire FrameworkBundle |
| YAML for service definitions | Forces Yaml dependency |
| Embedding vendor code in `src/` | Breaks autoloading, security audits |
| Hardcoded parameter values | Host can't customize |
| Public services by default | Pollutes host container |
| Missing service ID prefix | Collisions |
| `new Service()` instead of DI | Untestable, unconfigurable |
| Setter/property injection | Hidden dependencies |
| Business logic in controller/entity | SRP violation |
| Service class without `final` | Fragile inheritance |
| `mixed` without justification | PHPStan level 9 violation |
| Hardcoded entity/repository classes | Host can't customize |
| Non-translatable user-facing strings | Breaks i18n |
| Features that cannot be disabled | Host can't tailor bundle |

---

## 13. Generating a Bundle

1. Ask for: vendor name, purpose, Symfony components, Doctrine?, assets?, Twig components?
2. Scaffold full directory structure (use [scaffold_bundle.php](scaffold_bundle.php))
3. Generate all config files + quality tooling
4. If Doctrine: read `symfony-bundle-doctrine` skill
5. If frontend: read `symfony-bundle-ux` skill
6. If Flex: read `symfony-bundle-flex` skill
7. Always generate integration test
8. Run `make quality` to validate

## Additional Resources

- For compiler passes, events, voters, cache warmers, and semver contracts, see [advanced-patterns.mdc](advanced-patterns.mdc)
- To scaffold a bundle from the command line, run [scaffold_bundle.php](scaffold_bundle.php)
- For quality tooling (Makefile, PHPStan 9, GrumPHP, Infection, Deptrac, CI), read the `symfony-bundle-quality` skill

Symfony Bundle Core — Architecture & Scaffolding (Symfony 7+/8+)

This skill covers the full lifecycle of creating a modern Symfony bundle: project scaffolding, AbstractBundle configuration, dependency injection, service registration, routing, translations, testing, and distribution.

For frontend assets (AssetMapper, Stimulus, Twig Components, Live Components), see the symfony-bundle-ux skill. For Doctrine entity modeling in bundles, see the symfony-bundle-doctrine skill. For Flex recipes and distribution, see the symfony-bundle-flex skill. For quality tooling (Makefile, PHPStan, GrumPHP, Infection, CI), see the symfony-bundle-quality skill.


1. SOLID Principles for Bundles

A bundle is code reusable and modifiable by the entire community. SOLID is not optional.

  • S (Single Responsibility): One class, one responsibility. Services finely decomposed. No "god service" doing 5 things.
  • O (Open/Closed): Extensible without modification via events, interfaces, compiler passes, decorators. final readonly class on concrete classes forces extension by composition, not inheritance.
  • L (Liskov Substitution): Bundle interfaces are substitutable. No instanceof checks on concrete implementations internally.
  • I (Interface Segregation): One interface = one precise contract. No "god interface" with 10 methods.
  • D (Dependency Inversion): Inject interfaces, never concrete implementations. The host app can decorate or replace any service.

2. Contract Pattern

  • src/Contract/: public interfaces of the bundle (BC-safe API)
  • Internal services implement these interfaces
  • Host app type-hints on interfaces, not concrete classes
  • Only interfaces, events, and DTOs from Contract/ are part of the public API (semver)
  • Changes to Contract/ require a major version bump

3. Code Style

  • declare(strict_types=1) at the top of every PHP file
  • final readonly class for: services, handlers, processors, voters, listeners, DTOs
  • Exceptions to final readonly: Doctrine entities (not readonly), bundle class (not final), classes explicitly designed for inheritance (documented)
  • PHP 8 attributes for routing/validation: #[Route], #[Assert\...]. Never @annotations.
  • No attributes for service/tag registration (#[AsEventListener], #[AutoconfigureTag], #[AsTwigComponent], etc.) — in a bundle, all service registration goes through loadExtension() or XML config.
  • Native function prefix: \count(), \sprintf(), \in_array(), \array_map()
  • match over switch when applicable
  • Early return to reduce nesting
  • Constructor injection only (promoted properties: private readonly)
  • Never new Service(): always dependency injection
  • No business logic in controllers, entities, or events
  • No hardcoded parameters: use configure() + env vars

4. Scaffolding a New Bundle

Directory Structure

acme-blog-bundle/
├── assets/                    # Frontend source (JS, CSS, Stimulus controllers)
│   ├── controllers/           # Stimulus controllers (*_controller.js)
│   ├── dist/                  # Pre-compiled assets
│   ├── styles/                # CSS/SCSS source files
│   └── package.json           # UX metadata for Stimulus discovery
├── config/
│   ├── definition.php         # Complex config tree (optional)
│   ├── doctrine/              # XML mapping files (Entity.orm.xml)
│   ├── routes.yaml            # Bundle routes
│   └── services.xml           # Service definitions (XML preferred)
├── docs/
│   └── index.md
├── src/
│   ├── AcmeBlogBundle.php     # Main bundle class (extends AbstractBundle)
│   ├── Contract/              # Public interfaces (BC-safe API)
│   ├── Controller/
│   ├── DependencyInjection/
│   │   └── CompilerPass/
│   ├── Entity/
│   ├── Event/                 # Bundle events for extensibility
│   ├── EventSubscriber/
│   ├── Repository/
│   ├── Service/
│   └── Twig/
│       ├── Components/
│       └── Extension/
├── templates/
│   └── components/
├── tests/
│   ├── Functional/
│   ├── Integration/
│   └── Unit/
├── translations/
│   └── AcmeBlogBundle.en.xlf
├── .editorconfig
├── .gitignore
├── .php-cs-fixer.dist.php
├── CHANGELOG.md
├── composer.json
├── deptrac.yaml
├── grumphp.yml
├── infection.json5
├── LICENSE
├── Makefile
├── phpstan.neon.dist
├── phpunit.xml.dist
└── README.md

Naming Conventions

Enforced by the framework — breaking them causes resolution failures:

  • Bundle class: {Vendor}{Name}Bundle → AcmeBlogBundle
  • Namespace: {Vendor}\{Name}Bundle → Acme\BlogBundle
  • Config alias (auto-derived, snake_case): acme_blog
  • Service prefix: acme_blog. for all service IDs
  • Route name prefix: acme_blog_ for all route names
  • Translation domain: AcmeBlogBundle
  • Twig namespace: @AcmeBlog (suffix Bundle stripped)

composer.json

{
    "name": "acme/blog-bundle",
    "type": "symfony-bundle",
    "description": "A concise description",
    "license": "MIT",
    "require": {
        "php": ">=8.2",
        "symfony/config": "^7.0 || ^8.0",
        "symfony/dependency-injection": "^7.0 || ^8.0",
        "symfony/framework-bundle": "^7.0 || ^8.0",
        "symfony/http-kernel": "^7.0 || ^8.0"
    },
    "require-dev": {
        "deptrac/deptrac-src": "^2.0",
        "friendsofphp/php-cs-fixer": "^3.0",
        "infection/infection": "^0.29",
        "nyholm/symfony-bundle-test": "^3.0",
        "phpro/grumphp": "^2.0",
        "phpstan/phpstan": "^2.0",
        "phpunit/phpunit": "^10.0 || ^11.0",
        "symfony/phpunit-bridge": "^7.0 || ^8.0"
    },
    "autoload": { "psr-4": { "Acme\\BlogBundle\\": "src/" } },
    "autoload-dev": { "psr-4": { "Acme\\BlogBundle\\Tests\\": "tests/" } },
    "extra": { "symfony": { "require": "^7.0 || ^8.0" } }
}

Rules: only declare actual dependencies, "type": "symfony-bundle", every PHP extension in require, never embed vendor code in src/, always ^7.0 || ^8.0 for Symfony deps.


5. The AbstractBundle Class

Always use AbstractBundle for new bundles (Symfony 6.1+). Method execution order:

  1. prependExtension() — FIRST, before config resolution
  2. configure() — defines the schema tree
  3. loadExtension() — AFTER config merged+validated
  4. build() — registers compiler passes

See the complete template in the scaffold_bundle.php script.


6. Service Definitions (XML)

XML preferred (no Yaml component dependency). Defaults: autowire="true" autoconfigure="true" public="false".

Rules:

  • Never public by default
  • Always prefix service IDs: acme_blog.service_name
  • Use interface aliases for type-hinting
  • registerForAutoconfiguration() for custom interfaces

7. Controllers in Bundles

Never extend AbstractController. Inject specific services via constructor.


8. Routing

Use YAML/XML files in config/routes.yaml, not PHP attributes. Prefix all route names: acme_blog_.


9. Translations (XLIFF)

Every user-facing string must be translated — no hardcoded text in templates, controllers, forms, validators, or flash messages.

  • XLIFF (.xlf) mandatory. Own domain (AcmeBlogBundle). Never overwrite other domains.
  • Extract all strings: Twig ({% trans %}, |trans), form labels/help/placeholders, constraint messages, controller flash messages, email subjects/bodies, error pages
  • Always provide at least en and fr locales
  • Form labels: always explicit 'label' => 'form.field_name', never rely on auto-generated labels
  • Validator messages: always use translation keys ('message' => 'validation.field.constraint')
  • Run translation:extract regularly to catch missing keys

10. Testing

Use nyholm/symfony-bundle-test. Always generate at least one integration test. Run make quality after every change.


11. Configurability

Every feature of a bundle must be activatable, deactivatable, and configurable via configure():

  • Enable/disable any feature (Doctrine, controllers, Twig components, listeners, etc.) via a boolean config key
  • Override Doctrine entity classes and repository classes in config (see symfony-bundle-doctrine skill)
  • Customize any default value (table prefix, translation domain, route prefix, etc.)
  • The host app must never need to fork the bundle to customize behavior

12. Anti-Patterns Checklist

Anti-Pattern Impact
Extending AbstractController Couples to entire FrameworkBundle
YAML for service definitions Forces Yaml dependency
Embedding vendor code in src/ Breaks autoloading, security audits
Hardcoded parameter values Host can't customize
Public services by default Pollutes host container
Missing service ID prefix Collisions
new Service() instead of DI Untestable, unconfigurable
Setter/property injection Hidden dependencies
Business logic in controller/entity SRP violation
Service class without final Fragile inheritance
mixed without justification PHPStan level 9 violation
Hardcoded entity/repository classes Host can't customize
Non-translatable user-facing strings Breaks i18n
Features that cannot be disabled Host can't tailor bundle

13. Generating a Bundle

  1. Ask for: vendor name, purpose, Symfony components, Doctrine?, assets?, Twig components?
  2. Scaffold full directory structure (use scaffold_bundle.php)
  3. Generate all config files + quality tooling
  4. If Doctrine: read symfony-bundle-doctrine skill
  5. If frontend: read symfony-bundle-ux skill
  6. If Flex: read symfony-bundle-flex skill
  7. Always generate integration test
  8. Run make quality to validate

Additional Resources

  • For compiler passes, events, voters, cache warmers, and semver contracts, see advanced-patterns.mdc
  • To scaffold a bundle from the command line, run scaffold_bundle.php
  • For quality tooling (Makefile, PHPStan 9, GrumPHP, Infection, Deptrac, CI), read the symfony-bundle-quality skill
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource