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

6

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.

Aplanospore answered 3/4, 2012 at 18:45 Comment(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 :)Soucy
A
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' %}
Amadou answered 3/4, 2012 at 20:38 Comment(12)
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.Aplanospore
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.Amadou
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.Remittance
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.Dumortierite
This should be put into a bundle :)Damek
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?Lichen
@ElnurAbdurrakhimov is there any way I could keep a services.xml outside of the bundle structure for non-bundle services?Aplanospore
@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.Amadou
what about translation files like messages.[lang].yml for example?Godfry
@KarolFiturski, nothing special about them. Just put them into the app/Resources/translations folder.Amadou
@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.Witting
In Symfony 4, it's gonna be in different way. More simplier.Optimize
L
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).

Lawana answered 3/4, 2012 at 18:48 Comment(1)
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 symfony.com/doc/current/cookbook/workflow/… Then just create one (or more) application-bundle(s) (php app/console generate:bundle) for the symfony-specific stuff.Lawana
H
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.

Harmonics answered 15/6, 2012 at 13:37 Comment(0)
I
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!

Icefall answered 23/3, 2017 at 14:43 Comment(0)
A
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

Alow answered 3/4, 2012 at 19:10 Comment(6)
But then I would be coupling the application to the KnpRadBundle... Isn't there any easier approach on this matter?Aplanospore
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.Alow
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?Aplanospore
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.Alow
You should read the symfony bookAlow
indeed, controllers and views will be tied to some symfony components. Not more or less in knpRad than any other distribution.Harmonics
Y
-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.

Yippee answered 27/12, 2015 at 14:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.