Programming

Should everything really be a bundle in Symfony 2x

27 September 2026 · 10 min read

Should everything really be a bundle in Symfony 2x

When Symfony 2.x first emerged, it brought a revolutionary approach to PHP development, heavily emphasizing modularity through its “bundle” system. The initial mantra, often repeated in workshops and early documentation, seemed to suggest that virtually every piece of functionality, even application-specific code, should reside within a bundle. This philosophy aimed to promote reusability and clear separation of concerns, echoing principles of frameworks like Ruby on Rails’ engines. However, as the framework evolved and developers gained more experience with large-scale Symfony applications, the question naturally arose: should everything really be a bundle in Symfony 2.x? This discussion explores the original intent, the practical implications, and the modern best practices that have emerged regarding application structure and the judicious use of Symfony bundles.

The Philosophy Behind Symfony Bundles

At its core, a Symfony bundle is a directory that contains a set of functionalities, including controllers, entities, templates, services, and configuration. They are designed to be self-contained and easily pluggable components, promoting the idea of building applications from reusable blocks. The original vision for Symfony 2.x bundles was deeply rooted in the concept of reusability, allowing developers to package common features, such as user management or a blog system, and drop them into different projects with minimal effort. This modularity was a significant selling point, promising faster development cycles and maintainable codebases.

The framework’s AppBundle was initially presented as the default container for application-specific code. However, the prevailing wisdom often pushed developers to create separate bundles for distinct features within their own application, even if those features weren’t intended for reuse across multiple projects. This approach, while seemingly adhering to the principle of “everything is a bundle,” sometimes led to an explosion of small bundles, each containing only a handful of classes. The idea was that even application-specific logic could benefit from the clear boundaries and configuration isolation that bundles provided, leveraging Symfony’s powerful Dependency Injection Container and routing system effectively.

The Case for “Everything is a Bundle”

Proponents of the “everything is a bundle” approach in Symfony 2.x highlighted several advantages. Firstly, it enforced a strict separation of concerns. Each bundle would theoretically encapsulate a single feature or domain, making it easier for developers to understand and maintain specific parts of a large application. This modularity could simplify debugging and feature development, as changes were often confined to a single bundle.

Secondly, bundles provided a consistent structure for all code, whether it was a third-party library or custom application logic. This uniformity could lead to a predictable codebase, reducing the learning curve for new team members. Furthermore, the bundle system fully integrated with Symfony’s console commands, enabling developers to easily generate boilerplate code for new functionalities within a specific bundle. This integration streamlined the initial setup for new features, ensuring they adhered to the framework’s conventions.

Finally, for those building genuinely reusable components or libraries, packaging them as a Symfony bundle was, and still is, the standard and most efficient way to distribute them. Popular examples like FOSUserBundle or KnpMenuBundle demonstrate the power of bundles for sharing complex functionalities across the Symfony ecosystem. For an application that needed to frequently extract and reuse parts of its codebase, a bundle-centric architecture made that process more straightforward.

When Bundles Excel

Symfony bundles truly excel when the functionality they encapsulate is:

  • Reusable: If you intend to use the same feature (e.g., a custom authentication system, a specific API client, or a reporting module) across multiple Symfony projects, a bundle is the ideal packaging.
  • Distributable: For open-source contributions or internal company libraries, bundles provide the necessary structure for Composer integration and versioning.
  • Highly Configurable: Bundles offer robust ways to expose configuration options, allowing users to tailor their behavior without modifying the core code.

According to Symfony’s official documentation on bundles, their primary purpose is to group a set of functionalities in a structured and reusable way. This inherent design makes them perfect for common, shareable components that extend the framework’s capabilities. The Argument Against Excessive Bundles

While the benefits of bundles are clear for reusable components, the practice of making everything a bundle within a single application began to show its limitations. One significant concern was the overhead. Each bundle, no matter how small, adds to the application’s configuration complexity. This includes registering the bundle in AppKernel.php, managing its routing, and potentially defining its own services and configuration files. For a large application with dozens of small, application-specific bundles, this could lead to a sprawling and cumbersome directory structure, making navigation and debugging more challenging.

Moreover, the concept of “reusability” often became an abstract goal rather than a practical reality for many internal application features. Developing a feature as a bundle with the intention of future reuse sometimes led to over-engineering. Developers would spend time making a component generic and configurable, only for it to be used exclusively within that single application. This added development time and complexity without delivering on the promise of true reusability. Essentially, the overhead of creating and maintaining a bundle for every minor feature could outweigh the benefits for tightly coupled application logic.

When to Rethink a Bundle

You should consider alternatives to creating a new Symfony 2.x bundle when:

  • The functionality is highly specific to your current application and unlikely to be reused elsewhere.
  • It represents a small, isolated piece of domain logic that doesn’t warrant its own separate configuration and service definitions outside the main application structure.
  • You are looking to simply organize code, and a namespace or directory within your src/AppBundle or a dedicated src/ directory (in newer Symfony versions) would suffice.

Instead of creating a new bundle for every feature, modern Symfony best practices advocate for organizing application-specific code within the main src/ directory using namespaces to reflect domain boundaries. This approach leverages the power of Dependency Injection without the boilerplate associated with formal bundle registration. Striking the Right Balance: Modern Symfony Best Practices

The evolution of Symfony, particularly from version 3.x onwards, subtly shifted the perspective on application structure. While bundles remain crucial for reusable components, the framework encourages a more flexible approach for application-specific code. Instead of “everything is a bundle,” the focus has moved towards organizing domain logic directly within the src/ directory, using namespaces to delineate boundaries. This approach simplifies the application structure, reduces configuration overhead, and allows developers to focus on the core business logic.

The key to achieving a clean and maintainable Symfony application lies in effectively leveraging the Service Container and Dependency Injection for managing dependencies, regardless of whether your code is in a bundle or a simple namespaced directory. You can structure your domain logic with subdirectories like src/Domain/User, src/Domain/Product, etc., and define your services within app/config/services.yml or using autowiring. This offers the same benefits of modularity and testability without the formal bundle wrapper. Many developers now prefer to put the bulk of their application’s custom logic in the main AppBundle (or simply src/ in newer versions), using sub-namespaces to organize features. For instance, AppBundle\Service\Mailer or AppBundle\Controller\Product keeps related code together without the overhead of separate bundle definitions.

To make an informed decision on whether to create a new bundle or use a simple directory structure for your application code, consider the following:

  1. Is it reusable? If the code module is genuinely intended for use in multiple distinct Symfony projects, then a bundle is appropriate.

  2. **Is it Question & Answer :
    I’m aware of questions like this, where people tend to discuss the general Symfony 2 concept of bundle.

    The thing is, in a specific application, like, for instance, a twitter-like application, should everything really be inside a generic bundle, like the official docs say?

    The reason I’m asking this is because when we develop applications, in general, we don’t want to highly couple our code to some full-stack glue framework.

    If I develop a Symfony 2 based application and, at some point, I decide Symfony 2 is not really the best choice to keep the development going, will that be a problem for me?

    So the general question is: why is everything being a bundle a good thing?

    EDIT#1

    Almost a year now since I asked this question I wrote an article to share my knowledge on this topic.

    I’ve written a more thorough and updated blog post on this topic: http://elnur.pro/symfony-without-bundles/


    No, not everything has to be in a bundle. You could have a structure like this:

    • src/Vendor/Model — for models,
    • src/Vendor/Controller — for controllers,
    • src/Vendor/Service — for services,
    • src/Vendor/Bundle — for bundles, like src/Vendor/Bundle/AppBundle,
    • etc.

    This way, you would put in the AppBundle only that stuff that is really Symfony2 specific. If you decide to switch to another framework later, you would get rid of the Bundle namespace and replace it with the chosen framework stuff.

    Please note that what I’m suggesting here is for app specific code. For reusable bundles, I still suggest using the best practices.

    Keeping entities out of bundles

    To keep entities in src/Vendor/Model outside of any bundle, I’ve changed the doctrine section in config.yml from

    doctrine: # ... orm: # ... auto_mapping: true 
    

    to

    doctrine: # ... orm: # ... mappings: model: type: annotation dir: %kernel.root_dir%/../src/Vendor/Model prefix: Vendor\Model alias: Model is_bundle: false 
    

    Entities’s names — to access from Doctrine repositories — begin with Model in this case, for example, Model:User.

    You can use subnamespaces to group related entities together, for example, src/Vendor/User/Group.php. In this case, the entity’s name is Model:User\Group.

    Keeping controllers out of bundles

    First, you need to tell JMSDiExtraBundle to scan the src folder for services by adding this to config.yml:

    jms_di_extra: locations: directories: %kernel.root_dir%/../src 
    

    Then you define controllers as services and put them under the Controller namespace:

    <?php namespace Vendor\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use JMS\DiExtraBundle\Annotation\Service; use JMS\DiExtraBundle\Annotation\InjectParams; use JMS\SecurityExtraBundle\Annotation\Secure; use Elnur\AbstractControllerBundle\AbstractController; use Vendor\Service\UserService; use Vendor\Model\User; /** * @Service("user_controller", parent="elnur.controller.abstract") * @Route(service="user_controller") */ class UserController extends AbstractController { /** * @var UserService */ private $userService; /** * @InjectParams * * @param UserService $userService */ public function __construct(UserService $userService) { $this->userService = $userService; } /** * @Route("/user/add", name="user.add") * @Template * @Secure("ROLE_ADMIN") * * @param Request $request * @return array */ public function addAction(Request $request) { $user = new User; $form = $this->formFactory->create('user', $user); if ($request->getMethod() == 'POST') { $form->bind($request); if ($form->isValid()) { $this->userService->save($user); $request->getSession()->getFlashBag()->add('success', 'user.add.success'); return new RedirectResponse($this->router->generate('user.list')); } } return ['form' => $form->createView()]; } /** * @Route("/user/profile", name="user.profile") * @Template * @Secure("ROLE_USER") * * @param Request $request * @return array */ public function profileAction(Request $request) { $user = $this->getCurrentUser(); $form = $this->formFactory->create('user_profile', $user); if ($request->getMethod() == 'POST') { $form->bind($request); if ($form->isValid()) { $this->userService->save($user); $request->getSession()->getFlashBag()->add('success', 'user.profile.edit.success'); return new RedirectResponse($this->router->generate('user.view', [ 'username' => $user->getUsername() ])); } } return [ 'form' => $form->createView(), 'user' => $user ]; } } 
    

    Note that I’m using my ElnurAbstractControllerBundle to simplify defining controllers as services.

    The last thing left is to tell Symfony to look for templates without bundles. I do this by overriding the template guesser service, but since the approach is different between Symfony 2.0 and 2.1, I’m providing versions for both of them.

    Overriding the Symfony 2.1+ template guesser

    I’ve created a bundle that does that for you.

    Overriding the Symfony 2.0 template listener

    First, define the class:

    <?php namespace Vendor\Listener; use InvalidArgumentException; use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; use Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener as FrameworkExtraTemplateListener; use JMS\DiExtraBundle\Annotation\Service; class TemplateListener extends FrameworkExtraTemplateListener { /** * @param array $controller * @param Request $request * @param string $engine * @throws InvalidArgumentException * @return TemplateReference */ public function guessTemplateName($controller, Request $request, $engine = 'twig') { if (!preg_match('/Controller\\\(.+)Controller$/', get_class($controller[0]), $matchController)) { throw new InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (it must be in a "Controller" sub-namespace and the class name must end with "Controller")', get_class($controller[0]))); } if (!preg_match('/^(.+)Action$/', $controller[1], $matchAction)) { throw new InvalidArgumentException(sprintf('The "%s" method does not look like an action method (it does not end with Action)', $controller[1])); } $bundle = $this->getBundleForClass(get_class($controller[0])); return new TemplateReference( $bundle ? $bundle->getName() : null, $matchController[1], $matchAction[1], $request->getRequestFormat(), $engine ); } /** * @param string $class * @return Bundle */ protected function getBundleForClass($class) { try { return parent::getBundleForClass($class); } catch (InvalidArgumentException $e) { return null; } } } 
    

    And then tell Symfony to use it by adding this to config.yml:

    parameters: jms_di_extra.template_listener.class: Vendor\Listener\TemplateListener 
    

    Using templates without bundles

    Now, you can use templates out of bundles. Keep them under the app/Resources/views folder. For example, templates for those two actions from the example controller above are located in:

    • app/Resources/views/User/add.html.twig
    • app/Resources/views/User/profile.html.twig

    When referring to a template, just omit the bundle part:

    {% include ':Controller:view.html.twig' %} 
    ```**