The Ultimate Developer Guide to Symfony - Skeleton 16/03/2016
Reference: This article is intended to be as complete as possible and is kept up to date.
TL;DR: Start by putting everything in
AppBundleuntil we have a better idea of what the project looks like and how to organize it.
In this guide we've explored the main standalone libraries (also known as "Components") provided by Symfony to help us build applications:
We've also seen how HttpKernel enabled reusable code with Bundles.
In this article, we're going to have a closer look at how to organise our applications directory tree.
Finally in the next articles we'll finish by putting all this knowledge in practice by creating a "fortune" project with:
- an endpoint that allows us to submit new fortunes
- a page that lists all fortunes
- a command that prints the last fortune
Editions
Deciding how our project directory is organized is up to us, but for consistency and convenience we usually use "Editions" to bootstrap new projects:
composer create-project gnugat/symfony-empty-edition our-project
cd our-project
Note: Here we've decided to use the Symfony Empty Edition which follows the "add what you need" philosophy (it only contains the strict minimum).
If we're rather fond of the "solve 80% of use cases" philosophy we can go for Standard Edition which includes many tools commonly used to build full-stack websites.
To find more distributions, check the official website.
The directory tree looks like this:
.
├── app
│ ├── AppKernel.php
│ ├── autoload.php
│ └── config
│ ├── config_dev.yml
│ ├── config_prod.yml
│ ├── config_test.yml
│ ├── config.yml
│ └── parameters.yml.dist
├── bin
│ └── console
├── composer.json
├── src
│ └── AppBundle
│ └── AppBundle.php
├── var
│ ├── cache
│ └── logs
└── web
├── app.php
├── favicon.ico
└── robots.txt
Each folder in the root directory has a purpose:
app: configurationbin: scripts, binariessrc: our codevar: temporary filesweb: public directory exposed via the web server (app.phpis the front controller)
Note: Classes that wouldn't be used in production can be put outside of
src(e.g. tests could be put intests, fixtures infixtures, etc). They should be configured incomposer.jsonas follow:{ "autoload-dev": { "psr-4": { "Gnugat\\Toasty\\Fixtures\\": "fixtures", "Gnugat\\Toasty\\Tests\\": "tests" } } }This way, when running Composer's
installcommand in development we get our tests/fixtures classes autoloaded, and when running the same command with--no-devoption in production we don't.
AppBundle
Once we have an empty skeleton, we can start organizing our code by puting all
new classes in src/AppBundle, as advised by the official best practice.
Symfony specific classes can be put in the following directories:
src/AppBundle/Command, for Console Commandssrc/AppBundle/Controllerfor HttpKernel Controllerssrc/AppBundle/DependencyInjection, forCompilerPassInterfaceandExtensionInterfaceimplementationssrc/AppBundle/EventListener, for EventDispatcher Listeners
Our project specific classes can be put the src/AppBundle/Service directory.
The number of classes in will grow overtime, at some point we'll have an itch to organize them in a better way: we can group them by entity.
Regarding configuration, we can organize it this way:
app/config/routings/, contains Router configurationapp/config/services/, contains Dependency Injection configuration
The directory tree looks like this:
.
├── app
│ ├── AppKernel.php
│ ├── autoload.php
│ └── config
│ ├── config_dev.yml
│ ├── config_prod.yml
│ ├── config_test.yml
│ ├── config.yml
│ ├── parameters.yml.dist
│ ├── routings
│ └── services
├── bin
│ └── console
├── composer.json
├── composer.lock
├── src
│ └── AppBundle
│ ├── AppBundle.php
│ ├── Command
│ ├── Controller
│ ├── DependencyInjection
│ │ └── CompilerPass
│ ├── EventListener
│ └── Service
├── var
│ ├── cache
│ └── logs
└── web
├── app.php
├── favicon.ico
└── robots.txt
Decoupling from framework
Starting by putting everything in AppBundle is fine until we have a better idea
of what the project looks like and how to organize it.
As suggested in the official best practice,
we can move our "business logic" (everything in src/AppBundle/Service) to a new
src/<vendor>/<project> directory.
Note: Replace
<vendor>by the organization/author (e.g.Gnugat) and<project>by the project name (e.g.Toasty).
The directory tree looks like this:
.
├── app
│ ├── AppKernel.php
│ ├── autoload.php
│ └── config
│ ├── config_dev.yml
│ ├── config_prod.yml
│ ├── config_test.yml
│ ├── config.yml
│ ├── parameters.yml.dist
│ ├── routings
│ └── services
├── bin
│ └── console
├── composer.json
├── composer.lock
├── src
│ ├── AppBundle
│ │ ├── AppBundle.php
│ │ ├── Command
│ │ ├── Controller
│ │ ├── DependencyInjection
│ │ │ └── CompilerPass
│ │ └── EventListener
│ └── <vendor>
│ └── <project>
├── var
│ ├── cache
│ └── logs
└── web
├── app.php
├── favicon.ico
└── robots.txt
By leaving Symfony related classes in src/AppBundle and our "business logic"
in src/<vendor>/<project>, it becomes easier to decouple from the framework.
Decouple from libraries
Building on "decoupling from frameworks", we might also want to decouple from libraires. To do so our "business logic" classes should rely on interfaces, and their implementation would use libraries.
At this point we can get three different categories of classes:
Domainones, classes that reflect our business logicComponentones, classes that don't have a direct link to our project and could be reused as librariesBridgeones, classes that map our Domain to Component (or third party libraries)
By organizing our directory tree with those categories, it could looks like this:
.
├── app
│ ├── AppKernel.php
│ ├── autoload.php
│ └── config
│ ├── config_dev.yml
│ ├── config_prod.yml
│ ├── config_test.yml
│ ├── config.yml
│ ├── parameters.yml.dist
│ ├── routings
│ └── services
├── bin
│ └── console
├── composer.json
├── composer.lock
├── src
│ ├── AppBundle
│ │ ├── AppBundle.php
│ │ ├── Command
│ │ ├── Controller
│ │ ├── DependencyInjection
│ │ │ └── CompilerPass
│ │ └── EventListener
│ └── <vendor>
│ └── <project>
│ ├── Bridge
│ ├── Component
│ └── Domain
├── var
│ ├── cache
│ └── logs
└── web
├── app.php
├── favicon.ico
└── robots.txt
The issue with the previous organization is that classes in Bridge are now away
from their interface. Wouldn't it better to keep related classes close?
Here's an alternative organization, where we move Bridge to be in Domain:
.
├── app
│ ├── AppKernel.php
│ ├── autoload.php
│ └── config
│ ├── config_dev.yml
│ ├── config_prod.yml
│ ├── config_test.yml
│ ├── config.yml
│ ├── parameters.yml.dist
│ ├── routings
│ └── services
├── bin
│ └── console
├── composer.json
├── composer.lock
├── src
│ ├── AppBundle
│ │ ├── AppBundle.php
│ │ ├── Command
│ │ ├── Controller
│ │ ├── DependencyInjection
│ │ │ └── CompilerPass
│ │ └── EventListener
│ └── <vendor>
│ └── <project>
│ ├── Component
│ └── Domain
│ └── Bridge
├── var
│ ├── cache
│ └── logs
└── web
├── app.php
├── favicon.ico
└── robots.txt
Note:
Componentscould also need their own bridges. Also, a "Bundle" is a kind of bridge: it maps a library to Symfony.
Monolithic Repository
There's a possibility that our application grows out of proportion and we decide it'd be better to split it into smaller applications.
For example if we have an application that creates resources through a backend
and then provides them through an API for other applications, we could split it
in two: backend (note that backend could also be split in two:
backend-api and backend-ui) and api.
The problem is that those two applications would share a lot of logic, so splitting them in different repositories could become cumbersome to maintain. A good indicator to know if they need to be in the same repository: when we create a new version, do we need to release them together?
In that case it might be worth keeping those two applications in the same repository, this practice being called "Monolithic Repository".
For our project, it would mean:
- creating an
appsdirectory where we would put small symfony applications, similar to the first directory tree we've seen - creating a
packagesdirectory where we would put the previous content ofsrc/<vendor>/<project>, with each component in their own directory (to enable us to use them selectively in each apps)
Here's an overview:
.
├── apps
│ └── <app>
│ ├── app
│ │ ├── AppKernel.php
│ │ ├── autoload.php
│ │ └── config
│ │ ├── config_dev.yml
│ │ ├── config_prod.yml
│ │ ├── config_test.yml
│ │ ├── config.yml
│ │ ├── parameters.yml.dist
│ │ ├── routings
│ │ └── services
│ ├── bin
│ │ └── console
│ ├── composer.json
│ ├── composer.lock
│ ├── src
│ │ └── AppBundle
│ │ ├── AppBundle.php
│ │ ├── Command
│ │ ├── Controller
│ │ ├── DependencyInjection
│ │ │ └── CompilerPass
│ │ └── EventListener
│ ├── var
│ │ ├── cache
│ │ └── logs
│ └── web
│ ├── app.php
│ ├── favicon.ico
│ └── robots.txt
└── packages
└── <package>
├── composer.json
└── src
Note: More information about Monolithic Repository:
Conclusion
There are many ways to organize our application directory tree, and it's difficult to pick one when we don't have a clear idea on their impact or on what our project should look like.
The best way to tackle this is to first start small (everything in src/AppBundle),
and then move gradually files around. It's also important to make sure that change
is possible.
Here are some alternative ways of organizing the project directory tree: