208

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.

Daniel Ribeiro
  • 10,156
  • 12
  • 47
  • 79
  • 1
    This is just a comment, not an answer. I personally think, we should choose the framework carefully before starting the project. Every framework has its own way to do stuff, so it will provide tools to support that way the best. If we like that way, we follow. There are other choices out there. We don't want to use a knife to cut the wood instead of a saw. But it's a very interesting question you posed :) – Anh Nguyen Nov 12 '14 at 04:24

6 Answers6

221

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' %}
Community
  • 1
  • 1
Elnur Abdurrakhimov
  • 44,533
  • 10
  • 148
  • 133
  • 2
    That's actually a really interesting approach. With that, I can also develop real bundles that contain specific set of features that the community can use, without hardly coupling my application to the framework itself. – Daniel Ribeiro Apr 03 '12 at 21:05
  • 57
    To make the code you share with the community not coupled to Symfony2 as well, you could put the general stuff into a library and then create a bundle that integrates that library with Symfony2. – Elnur Abdurrakhimov Apr 03 '12 at 21:28
  • 9
    This is an interesting idea as long as you don't rely on any of the code generation commands. `generate:doctrine:crud` for instance expects for the entity (=model in elnur's case) to be inside a bundle in order to work. – geca Apr 21 '12 at 16:44
  • 2
    With this approach is there any way to regain the functionality of the CLI app/console interface? I love the idea of keeping my models in a spot outside of any bundle, but I'd like to retain access to CLI functionality. – Andy Baird Aug 24 '12 at 16:47
  • 3
    This should be put into a bundle :) – d0001 Nov 28 '12 at 12:03
  • Is this approach practical? Can I still leverage most of the good parts of Symfony2 - e.g. EventDispatcher, this, or that? The thing that confuses me is the bundle you've created - the "template guesser overrider". Anyway - If I theoretically used this approach, what "Bundle" would I actually need? I'm assuming some of the app functionality would need a bundle. I'm doing something for "admin" "static pages" and "non-static pages". Perhaps a bundle for each to define "services" and routes such? – Sam Levin Jun 10 '13 at 05:55
  • @ElnurAbdurrakhimov is there any way I could keep a services.xml outside of the bundle structure for non-bundle services? – Daniel Ribeiro Jul 29 '13 at 12:10
  • @DanielRibeiro, your question is not entirely clear to me, but you can create `services.yml` in the `app/config` folder and import it from `config.yml`. Not sure if it works across different file formats. – Elnur Abdurrakhimov Jul 29 '13 at 13:04
  • what about translation files like messages.[lang].yml for example? – Karol F Jun 04 '14 at 15:50
  • @KarolFiturski, nothing special about them. Just put them into the `app/Resources/translations` folder. – Elnur Abdurrakhimov Jun 04 '14 at 16:35
  • @ElnurAbdurrakhimov I thought that classes outside the bundle should not have dependency on any bundle stuff. Like in the above example, the Vendor\Listener has a dependency on Symfony\Bundle\FrameworkBundle\Templating\TemplateReference. Should not know anything about the bundles, otherwise it should be kept in some bundle too. – tomazahlin Jun 11 '15 at 12:44
  • In Symfony 4, it's gonna be in different way. More simplier. – Viktor Sydorenko Jul 11 '17 at 10:48
21

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).

KingCrunch
  • 128,817
  • 21
  • 151
  • 173
  • 2
    Because of the tag `symfony-2.0` I'll assume you use the current 2.0 version. In this case create a git repository whereever you like and put everything into it, what you want to develop independent from symfony. In your symfony-project update your `deps`-file like mentioned here http://symfony.com/doc/current/cookbook/workflow/new_project_git.html#cookbook-managing-vendor-libraries Then just create one (or more) application-bundle(s) (`php app/console generate:bundle`) for the symfony-specific stuff. – KingCrunch Apr 03 '12 at 20:32
11

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.

Florian Klein
  • 8,692
  • 1
  • 32
  • 42
5

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!

Reshat Belyalov
  • 963
  • 1
  • 10
  • 19
4

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

miguel_ibero
  • 1,024
  • 7
  • 9
  • But then I would be coupling the application to the KnpRadBundle... Isn't there any easier approach on this matter? – Daniel Ribeiro Apr 03 '12 at 19:16
  • 1
    The parts that depend on symfony (Controllers, Models, templates, etc...) will always be coupled to symfony, since you are using it (extending classes, using helpers, etc...). The classes that work alone will be in the Company namespace, and you can load them using the dependency container. These classes can be framework independent. – miguel_ibero Apr 03 '12 at 19:21
  • 1
    The thing is, the concept of `Bundle` goes directly on publicly sharing. When I write some application, I don't wanna share my code, except for those parts that I intentionally built as community-driven modules. Am I wrong? – Daniel Ribeiro Apr 03 '12 at 19:37
  • You don't have to share the bundles. Think of a bundle as of a group of classes with some configuration. In each project you can have different bundles. – miguel_ibero Apr 03 '12 at 19:45
  • You should read the [symfony book](http://symfony.com/doc/current/book/index.html) – miguel_ibero Apr 03 '12 at 19:47
  • indeed, controllers and views will be tied to some symfony components. Not more or less in knpRad than any other distribution. – Florian Klein Jun 15 '12 at 13:24
-2

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.

miltone
  • 4,416
  • 11
  • 42
  • 76