Loïc Faugeron Technical Blog

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 AppBundle until 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:

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:

Note: Classes that wouldn't be used in production can be put outside of src (e.g. tests could be put in tests, fixtures in fixtures, etc). They should be configured in composer.json as follow:

{
    "autoload-dev": {
        "psr-4": {
            "Gnugat\\Toasty\\Fixtures\\": "fixtures",
            "Gnugat\\Toasty\\Tests\\": "tests"
        }
    }
}

This way, when running Composer's install command in development we get our tests/fixtures classes autoloaded, and when running the same command with --no-dev option 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:

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:

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:

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: Components could 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:

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: