(Written by Thomas Botton and Patrick Zahnd)
Symfony2 was released as Beta 1 few weeks ago. Since it starts to be our main PHP framework here at Liip, we decided to dive deeper into the core component of Symfony2: the bundles. Indeed, the structure of it needs to be clearly understood in order to have maintainable and sustainable code.
This blogpost aims to propose a project that one can take as a real life example for building its own bundle set with a correct structure – at least avoiding the main common mistakes.
In general, a bundle is meant to be a standalone component that can be reused and implemented by anyone in their project – as nobody wants to reinvent the wheel.
Bundles can have controllers, entities, forms, views, etc… To prevent these components to end up in a mess, Symfony2 comes with some rules and architecture to follow.
We recommend reading the Symfony2 “Bundle Directory Structure” post before starting at this point: symfony.com/doc/2.0/book/page_creation.html
Full bundle structure
Liip/
EventBundle/
EventBundle.php
Command/
AddEventCommand.php
Controller/
Backend/
ConcertController.php
Frontend/
ConcertController.php
DependencyInjection/
ConcertExtension.php
Document/
Registration.php
Entity/
Concert.php
ConcertManager.php
Form/
ConcertForm.php
RegistrationForm.php
Resources/
meta/
LICENSE
config/
config.xml
doctrine/
metadata/
orm/
Liip.EventBundle.Entity.Concert.dcm.xml
routing.xml
validation.xml
doc/
index.rst
translations/
messages.en.xliff
views/
Backend/
concert_new.html.twig
Frontend/
concert_show.html.twig
public/
css/
main.css
js/
form.js
Tests/
Controller/
Backend/
ConcertControllerTest.php
Frontend/
ConcertControllerTest.php
/Command
Used to expose commands to the console (app/console).
/Controller
The place you put your controllers. You should separate Frontend and Backend controllers into subdirectories – if you have both – in order to have it more readable.
/DependencyInjection
Here you can load your own services by creating a so-called Extension.
/Document
Mapping for ODM objects. For example Contact Messages which you send as an email.
/Entity
Mappings for your ORM objects.
/Form
Form definitions.
/Resources
Configuration of your project.
/Resources/config
XML (or YML, or PHP) configuration files, like the routing.xml for example. Default is to use XML, and you have to use XML in Bundles you'd like to commit.
/Resources/views
The templates in the format you prefer (PHP, Twig, …).
/Resources/translations
Translations file in XLIFF format.
/Resources/public
Public files like images, CSS stylesheets and Javascript files.
/Tests
Bundle functionality tests.
Sample project
As an example, we decided to go with a web application handling various music events and the user comments.
The following shows how to translate/divide a simple feature list within a set of bundles.
- Events
- Frontend
- Event list
- Search event
- User can register for event
- Backend
- CRUD event over an admin interface and on the console
- Visitor Echoes
- Users can write a comment
- Contact form
- Pages displaying static content
Mockup:
Described in bundles
- EventBundle
- EchoBundle
- ContactBundle
- StaticBundle
Event bundle
The EventBundle encloses
- commands to access the backend function
- frontend and backend controllers
- entity for the concerts
- form to add concerts (backend)
- form to register for an event (frontend)
Structure
Liip/
EventBundle/
EventBundle.php
Command/
AddEventCommand.php
Controller/
Backend/
ConcertController.php
Frontend/
ConcertController.php
DependencyInjection/
ConcertExtension.php
Document/
Registration.php
Entity/
Concert.php
ConcertManager.php
Form/
ConcertForm.php
RegistrationForm.php
Resources/
meta/
LICENSE
config/
config.xml
doctrine/
metadata/
orm/
Liip.EventBundle.Entity.Concert.dcm.xml
routing.xml
validation.xml
doc/
index.rst
translations/
messages.en.xliff
views/
Backend/
concert_new.html.twig
Frontend/
concert_show.html.twig
public/
css/
main.css
js/
form.js
Tests/
Controller/
Backend/
ConcertControllerTest.php
Frontend/
ConcertControllerTest.php
EchoBundle
The EchoBundle encloses
- frontend controller
- entity for the echoes
- form to add an echo
Structure
Liip/
EchoBundle/
EchoBundle.php
Controller/
FrontendController.php
Entity/
Echo.php
EchoManager.php
Form/
EchoForm.php
Resources/
meta/
LICENSE
config/
doctrine/
metadata/
orm/
Liip.EchoBundle.Entity.Echo.dcm.xml
routing.xml
doc/
index.rst
translations/
messages.en.xliff
views/
Frontend/
echo_new.html.twig
echo_index.html.twig
public/
css/
main.css
js/
form.js
Tests/
Controller/
FrontendControllerTest.php
ContactBundle
The ContactBundle encloses
- a frontend controller
- contact form
Structure
Liip/
ContactBundle/
ContactBundle.php
Controller/
FrontendController.php
Document/
Message.php
Form/
ContactForm.php
Resources/
meta/
LICENSE
config/
routing.xml
doc/
index.rst
views/
Frontend/
contact_form.html.twig
public/
css/
main.css
js/
form.js
Tests/
Controller/
FrontendControllerTest.php
StaticBundle
You should not use this approach in production, this is just for example
The StaticBundle only encloses a frontend controller which will be responsible to take a page name as routing argument and then to display the corresponding template. If none is matching then 404 is displayed.
Structure
Liip/
StaticBundle/
StaticBundle.php
Controller/
PageController.php
Resources/
meta/
LICENSE
config/
routing.xml
doc/
index.rst
views/
Frontend/
static_home.html.twig
static_about.html.twig
public/
css/
main.css
js/
fancy.js
Tests/
Controller/
PageControllerTest.php
CONCLUSION
From this small case study, we can see some “bundle properties” drawing up which can be pick up depending on the bundle you are building:
- Backend/Frontend differentiation: this structure allows you the get more readability and help you find faster what you want to work on, depending on if it is front or backend. This splitting usually occurs to the following directories: /Controller, /Ressources/views and /Tests
- DB interaction based bundle (i.e. Entity/Document and Forms): that is the case for a lot of bundles which rely on one or more entities needing forms to interact with (create, modify or delete)
- Customizable/Dependency Injection: this is the place where you can handle your own services. Therefore you have to create a so-called Extension. To understand that, you might want to read the following two blog posts: martinsikora.com/symfony2-and-dependency-injection / fabien.potencier.org/article/11/what-is-dependency-injection
To sum up, we could say that the bundles' goal is to split feature/functionality in order to be reusable as much as possible. If you end up with only one bundle for your web application, you might be wrong. Just get your head back from code and think: it must be a possibility to break your “SiteBundle” into at least 2 bundles. Else it means that you are having a static website, then you should go with a “StaticBundle”.