by Elnur Abdurrakhimov

Symfony Without Bundles

One of the first things developers new to the Symfony framework learn about is bundles. Everything in Symfony is a bundle, the documentation says.

Based on that thought enthusiastic developers happily hop the bundle bandwagon and start creating a bunch of bundles in their apps:

CommonBundle

CoreBundle

UserBundle

FrontendBundle

BackendBundle

BlogBundle

ForumBundle

PageBundle

ContactBundle

AdminBundle

ThisBundle

ThatBundle

WhateverBundle

…

The list goes on and on. Sure, you don’t usually meet all these bundles in a single project, but something similar is surely happening.

Note that whatever is being discussed in this post relates to application specific code that’s not reusable and will not be shared with other projects. Bundles are still very great for reusable code that needs to be shared and integrated with Symfony, and I use them a lot myself, although infrastructural ones only. But bundles just don’t make much sense for the application code that’s not reusable. In this particular case bundles add more problems than benefits. Remember, one size doesn’t fit all and there are no silver bullets.

Problems With Multiple Bundles

When I started with Symfony, I’ve used the same approach and run into several problems with it:

Creating a lot of bundles is tedious. You have to create a bundle class and a bunch of standard folders for each new bundle and then activate it and register its routes and DI and whatnot.

Unnecessary hardcore decision making process. Sometimes you just can’t decide which bundle a particular thing belongs to because it’s used by more than one bundle. And after you spend a half of a day and finally make your hard decision on where to put it, you’ll find that in a couple of days or weeks you won’t be able to tell right away which bundle to look that thing in — because most of the times the decision wasn’t based on pure logic and you had to choose based on a coin toss or whatever means you use to bring higher powers for help.

I suggested using CommonBundle for common stuff in the past but doing that you’ll have to do a lot of unnecessary refactorings moving a thing to and from CommonBundle based on how many or few bundles will use that thing later.

App specific bundles are interdependent anyway. When people meet the idea of bundles for the first time, one of the main thought that goes through their minds is something like “Yay! I’ll have me a bunch of reusable bundles!” That idea is great and I have nothing against it. The problem is that app specific bundles are not that reusable anyway — there are interdependent. Forget about reuse in this case.

No idea where to put Behat features and step definitions. This problem is related to the previous ones: you have to repeat the same brainless motions for each bundle and then make hardcore decisions. And then you have to move things around when things change.

A Single Bundle for the Whole Application

Having run into those problems, my next evolutionary step was to give up the idea of having multiple bundles in an application and switch to a single AppBundle for the whole application. That solved all the problems listed above.

One of the reasons developers create multiple bundles is to partition things like controllers and views so that it’s easier to find stuff that’s related to the frontend or the backend. Turns out you don’t need bundles for that; PHP namespaces do the job and do it great.

As you can see, subnamespaces and subfolders combined solve the problem of partitioning an app with sections. No need to create a bunch of bundles for that.

No Bundles at All

When I got used to this one bundle approach, I had another insight: if I have everything in a single bundle, what’s the point of having the bundle at all? The bundle was adding an additional level of folder and namespace nesting for no particular benefit. So the next evolutionary step was to move everything out of the bundle and get rid of the bundle itself.

I did that step-by-step, first moving out services, repositories, models, controllers, and views, but eventually I’ve managed to move out everything.

I’ve been using this approach for more than two years now and it works great for me.

Now let’s get to specifics.

Services Out of Bundles

Moving services out of bundles is easy. Actually, you didn’t need bundles for services in the first place. But services out of bundles is a prerequisite for controllers discussed next.

There’s nothing special to services here. You can define them in config.yml or create separate file services.yml and import it from config.yml.

Let’s say you want to define a user manager service. Here’s how the class would look:

To make the routes work, you need to do two things. First, note the @Route annotation on the class: @Route("/user", service="user_controller"). The trick is that service property that refers to the service name defined in the annotation above: @Service("user_controller").

Second, you need to refer to this controller from routing.yml:

1
2
3

site:resource:Example\Controller\UserControllertype:annotation

Templates Out of Bundles

Since templates are not “real” code, instead of keeping them in the src/ folder, they go to app/Resources/views. That folder is already used by Symfony, but it’s mostly used for overriding templates of third-party bundles. You can still override templates of other bundles by placing them into app/Resources/views, but since we have no bundles for the application code, our templates go into this folder as well. And that feels much more logical than having templates in the src/ folder.

Let’s say you have a template for displaying a user’s profile. With this approach it would go to app/Resources/views/User/view.html.twig. Its logical name would be :User:view.html.twig so that’s what you would use when referring to this template from other templates: ``.

The template guesser from my bundle will map viewAction to :User:view.html.twig that’s supposed to be in app/Resources/views/User/view.html.twig.

Models Out of Bundles

Models go to the src/Example/Model folder and the Example\Model namespace. There’s nothing special regarding defining the models themselves.

Note that I call them models and not entities. Entities is an ORM term and models are not necessarily being persisted by an ORM or an ORM alone. Since the same models can be persisted by different means, I call them models. They can still be used even if there is no persistence used at all. Remember not to tie your models to an ORM.

So, assuming you’re using the Doctrine ORM and annotations for mapping, here’s how the user model would look like:

With this configuration you can still refer to models with the colon notation: Model:User.

Translation Files Out of Bundles

This one is simple. Just put translation files into the app/Resources/translations folder.

Conclusion

Bundles are great for reusable code that you share between several projects. But they don’t make much sense for the application specific code that probably won’t ever be shared with other projects.

Sure, if while developing your application you find code that you want to reuse in other projects, extract it to a library and/or bundle and share it. But don’t shove the application code itself into bundles.

Writing application code without bundles is an example of applying a framework to your application instead of bending the application to a currently trending framework — no matter how great it is this week.