Should everything really be a bundle in Symfony 2.x?

SymfonyArchitectureBundle

Symfony Problem Overview


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.

Symfony Solutions


Solution 1 - Symfony

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' %}

Solution 2 - Symfony

Of course you can decouple your application. Just develop it as a library and integrate it into symfony vendor/-folder (either by using the deps or composer.json, depending wether you use Symfony2.0 or Symfony2.1). However, you need at least one bundle, that acts as the "frontend" of your library, where Symfony2 finds the controller (and such).

Solution 3 - Symfony

A usual symfony distribution can work without any extra (application) bundle, depending on how much functionalities you want to use from the full stack framework.

For example, your controllers can be any callable that can be put anywhere in your project structure, as soon as they are autoloaded.

In a routing definition file, you can use:

test:
    pattern:   /test
    defaults:  { _controller: Controller\Test::test }

It can be any plain old php object, only tied to the framework by the fact it has to return a Symfony\Component\HttpFoundation\Response object.

Your twig templates (or others) can be put like app/Resources/views/template.html.twig and can be rendered using the ::template.html.twig logical name.

All DI services can be defined in app/config/config.yml (or imported from app/config/services.yml for example, and all service classes can be any plain old php objects too. not tied to the framework at all.

All of this is provided by default by the symfony full stack framework.

Where you will have problems is when you will want to use translation files (like xliff), because they are discovered through bundles only.

The symfony-light distribution aims to solve these kind of problems by discovering everything that would be usually discovered only through bundles.

Solution 4 - Symfony

Since it's 5 years already passed, here are few more articles about Symfony Bundles.

  1. What are Bundles in Symfony? by Iltar van der Berg.

TLDR:

> Do you need multiple bundles in your application directly? Most likely > not. You're better off writing an AppBundle to prevent a spaghetti of > dependencies. You can simply follow the best practices and it will > work fine.

  1. Symfony: How to Bundle by Toni Uebernickel.

TLDR:

> Create only one bundle called AppBundle for your application logic. > One AppBundle - but please do not put your application logic in there!

Solution 5 - Symfony

You could use KnpRadBundle, which tries to simplify the project structure.

Another approach is to use src/Company/Bundle/FrontendBundle for example for the bundles and src/Company/Stuff/Class.php for the classes that are symfony independent and that could be reused outside of the framework

Solution 6 - Symfony

Symfony framework is very good for quickly launch a proof of concept and all the code can enter within the default bundle application in src/

In this bundle you can structure your code as you want.

After if you want to use other technology for develop your POC you can easily translate that because you don't structure all your code in bundle conception.

For all of concept you don't extremismed this. Bundle is good but bundle everything and everyday is not good.

Perhaps you can use a Silex (Symfony micro framework) for develop your Proof of Concept for reduce impact of bundle third-party.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel RibeiroView Question on Stackoverflow
Solution 1 - SymfonyElnur AbdurrakhimovView Answer on Stackoverflow
Solution 2 - SymfonyKingCrunchView Answer on Stackoverflow
Solution 3 - SymfonyFlorian KleinView Answer on Stackoverflow
Solution 4 - SymfonyReshat BelyalovView Answer on Stackoverflow
Solution 5 - Symfonymiguel_iberoView Answer on Stackoverflow
Solution 6 - SymfonydarkomenView Answer on Stackoverflow