Upgrading to the Symfony 3.0 Directory Structure
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Hey, we're on Symfony 2.8! Before we fully upgrade, let's move into the new 3.0 directory structure... which you can actually use with any version of Symfony... But in 3.0, it's super official. And remember: the new structure isn't required. Want to keep things how they are now? Do it! We talk more about the new structure in our What's new in Symfony 3 screencast.
People often ask me:
What are the first 10 digits of pi?
Great question: they're 3.1415926535. They also ask me:
What do I need to change in my code after upgrading?
The answer to that is... nothing! Ya know, because Symfony doesn't break backwards compatibility.
But we do sometimes tweak things in the default directory structure of Symfony. A sure-fire way to find out about those changes is to go to the Symfony Standard repository and just compare the versions. If you compare 2.8 to master, you'll see all the changes needed to move into this new directory structure.
Ready to do this?
Autoload AppKernel
In each front controller we require the AppKernel
file:
// ... lines 1 - 30 | |
require_once __DIR__.'/../app/AppKernel.php'; | |
// ... lines 32 - 39 |
That's because this file doesn't have a namespace and doesn't live in src
. Basically,
it's weird, and can't be autoloaded.
That was lame, so it is autoloaded now! In composer.json
, add a files
key with
app/AppKernel.php
inside of it:
// ... lines 1 - 5 | |
"autoload": { | |
"psr-4": { "": "src/" }, | |
"files": ["app/AppKernel.php"] | |
}, | |
// ... lines 10 - 63 |
With that, we can rip out the require statement in app.php
, app_dev.php
, app/console
and in AppCache.php
. Nice!
For this to take effect, head over to the terminal and dump the autoloader:
composer dump-autoload
Running composer install
also does this.
Refresh! And it still works!
The var/ Directory
The most noticeable change is the new var/
directory which holds stuff you don't
need to modify, like the cache
and logs
. To update your project, open AppKernel
and override a few methods. I'll use command+n
to open the generate menu. Select
override and choose getRootDir()
, getCacheDir()
, and getLogDir()
. I'm not sure
why we override getRootDir()
, because that defaults to this directory, but I
do like that it looks less magic now:
// ... lines 1 - 5 | |
class AppKernel extends Kernel | |
{ | |
// ... lines 8 - 47 | |
public function getRootDir() | |
{ | |
return __DIR__; | |
} | |
// ... lines 52 - 61 | |
} |
For getCacheDir()
, return dirName(__DIR__)
- that looks in app/
then goes up
to the root - and then add .'/var/cache/'.$this->environment;
:
// ... lines 1 - 52 | |
public function getCacheDir() | |
{ | |
return dirname(__DIR__).'/var/cache/'.$this->environment; | |
} | |
// ... lines 57 - 63 |
Do a similar thing in getLogDir()
except change to .'/var/logs/';
:
// ... lines 1 - 57 | |
public function getLogDir() | |
{ | |
return dirname(__DIR__).'/var/logs'; | |
} | |
// ... lines 62 - 63 |
That's all you need to do to move the cache & logs directories.
In the terminal create a var/
directory, and then git mv app/cache/
into var/
.
Do the same to move app/logs/
into var/
:
mkdir var
git mv app/cache var/
git mv app/logs var/
This might seem weird to you because the cache and logs directories aren't normally
stored in git. But we do keep a .gitkeep
file inside each to make sure they don't
get deleted. That's what we just moved.
Back to the browser and refresh! It's still alive! In our IDE, we have a new var/
directory that's happily populated with cache and logs files.
Changes to bin/
Next, look at the bin
directory. It exists because composer.json
holds a little
bit of configuration: "config:" "bin-dir:"
:
{ | |
// ... lines 2 - 51 | |
"config": { | |
"bin-dir": "bin" | |
}, | |
// ... lines 55 - 61 | |
} |
Sometimes when you install an external library via composer, it comes with a binary
file. Composer usually puts that into a vendor/bin
directory for convenience. With
this config, it goes into bin/
instead?
Why did we override this? I have no idea! Composer was young, it was the wild-west,
things happen. And really, it's not a big deal, except that it makes Symfony projects
look a bit "weird" compared to others. Remove this config so that Composer uses
the normal vendor/bin
.
In the terminal, completely remove the bin/
directory. But wait! The bin
directory
does still exist in the Symfony 3 directory structure, it just has a new job.
Put it back!
rm -rf bin
mkdir bin
bin/console
Phew! Now that it's back, move the app/console
file into bin/
:
mv app/console bin/console
This is one of the biggest changes. Goodbye app/console
, hello bin/console
.
Of course this file is angry because the bootstrap.php.cache
file is not in the same
directory anymore. But instead of loading it, load /../app/autoload.php
instead:
// ... lines 1 - 9 | |
require __DIR__.'/../app/autoload.php'; | |
// ... lines 11 - 27 |
Why? Well, in part because bootstrap.php.cache
is going to move too.
Head to the terminal and try out your very first bin/console
:
bin/console
It's still happy, and I'm still happy.
Moving bootstrap.php.cache
Speaking of bootstrap.php.cache
, this now lives in the var
directory. But wait,
we aren't responsible for this file. In fact, who created this in the first place?
Who put this in the app
directory to begin with? It was Santa! Just kidding: it's
the SensioDistributionBundle, via one of these post install hooks in composer.json
:
// ... lines 1 - 33 | |
"scripts": { | |
"post-install-cmd": [ | |
// ... line 36 | |
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", | |
// ... lines 38 - 41 | |
], | |
// ... lines 43 - 50 | |
}, | |
// ... lines 52 - 60 |
We need to tell the SensioDistributionBundle that we want it to be built in var
instead of app
. To do that, add a couple of new configuration lines in the extra
section of composer.json
.
Add "symfony-var-dir":
set to "var"
, "symfony-bin-dir":
set to "bin"
and
"symfony-tests-dir":
set to "tests"
. The really important one here symfony-var-dir
:
// ... lines 1 - 51 | |
"extra": { | |
"symfony-app-dir": "app", | |
"symfony-web-dir": "web", | |
"symfony-var-dir": "var", | |
"symfony-bin-dir": "bin", | |
"symfony-tests-dir": "tests", | |
// ... lines 58 - 60 | |
} | |
// ... lines 62 - 63 |
When you run composer install
it's going to see this and realize that you want
it to put the bootstrap.php.cache
file into the var
directory. It's as simple
as that.
The symfony-bin-dir
is also important because of the check.php
and SymfonyRequirements.php
files. These also moved, and the SensioDistributionBundle is also in charge of
putting those inside of either the app
, bin
or var
directories. In a second,
you'll see these move.
The symfony-test-dir
, well, as far as I can see, that doesn't do anything. But
it exists in the official Symfony Standard Edition and darn it, I'm a rule-follower.
Back to our favorite place, the terminal! Run:
composer install
Nothing is installing, but it does run the post install scripts. And... ding! Suddenly
a few files disappeared from the app
directory: check.php
and SymfonyRequirements.php
.
check.php
is now called bin/symfony_requirements
and the SymfonyRequirements
file
has been moved over to the var
directory. All of that was done thanks to that extra
configuration.
Require autoload.php instead of bootstrap.php.cache
In the var
directory, there's the bootstrap.php.cache
file. Delete the original
one in app
. To un-break our application, we need to go into app_dev.php
and require
the new path to the bootstrap file. But stop! Like the console
file, we're no longer
going to include bootstrap.php.cache
. Instead require app/autoload.php
:
// ... lines 1 - 27 | |
$loader = require __DIR__.'/../app/autoload.php'; | |
// ... lines 29 - 37 |
Tip
Actually, the require_once
was also changed to require
, which guarantees that
the $loader
is returned.
Why? Well, the whole point of bootstrap.php.cache
is performance: by having a
bunch of classes in one file, it means that the autoloading mechanism has to work
less. But when you're debugging, it's not helpful to see errors coming from a deep
line in bootstrap.php.cache
.
Instead, in the dev
environment, only include the normal autoloader. In app.php
do the same thing. But since performance is totally rad to have in the prod
environment,
add an include_once
below that for /../var/bootstrap.php.cache
:
// ... lines 1 - 11 | |
$loader = require __DIR__.'/../app/autoload.php'; | |
include_once __DIR__.'/../var/bootstrap.php.cache'; | |
// ... lines 14 - 47 |
Let's give that a try in the browser.
And it still works. I can't seem to break our app.
Moving Tests
We're now down to the last change: it involves the tests
directory. Wait, you
don't write tests? You are a brave, brave developer.
For the rest of you law-abiding citizens. the tests used to live inside of the
bundles themselves. These have now been moved down into a new root directory called,
well, tests
! Move the AppBundle/Tests
directory into tests
. Once it's there,
rename it to AppBundle
so that you have a nice tests/AppBundle
.
The idea is to have paths like tests/bundleName/individualClasses
. There's no technical
reason for this change, it's just a new standard.
With everything moved around, you'll need to update the namespace of each class to
be Tests/AppBundle
:
// ... lines 1 - 11 | |
namespace Tests\AppBundle\Controller; | |
// ... lines 13 - 41 |
The reason for this is autoloading: you know, the old "your directory structure must match your namespace" thing. You can sometimes get away with messing this up with tests... but not always.
This is sweet... except that Composer doesn't know to look in the tests/
directory.
Open composer.json
. Below autoload
, create a new section called autoload-dev
.
Below it add a psr-4
key for { "Tests\\": }
pointing at the tests/
directory:
// ... lines 1 - 9 | |
"autoload-dev": { | |
"psr-4": { "Tests\\": "tests/" } | |
}, | |
// ... lines 13 - 66 |
We're good!
Moving phpunit.xml.dist
On the topic of tests, the phpunit.xml.dist
file normally lives in the app/
directory. That's why you run phpunit -c app
.
To simplify things, that has been moved to the root of the project:
mv app/phpunit.xml.dist .
After moving it, we need to update a few things. Update bootstrap
to app/autoload.php
.
In the testsuites
section, this is how phpunit know where tests live.
This can now simply be changed to tests
... which is pretty cool:
// ... lines 1 - 3 | |
<phpunit | |
// ... lines 5 - 13 | |
bootstrap = "app/autoload.php" > | |
<testsuites> | |
<testsuite name="Project Test Suite"> | |
<directory>tests</directory> | |
</testsuite> | |
</testsuites> | |
// ... lines 21 - 39 | |
</phpunit> |
Finally, uncomment out the php
block and tell Symfony where your app directory
is by changing the KERNEL_DIR
value to app/
:
// ... lines 1 - 3 | |
<phpunit | |
// ... lines 5 - 21 | |
<php> | |
<server name="KERNEL_DIR" value="app/" /> | |
</php> | |
// ... lines 25 - 39 | |
</phpunit> |
This is for functional tests: when Symfony tries to boot your kernel, it needs to know... where you kernel lives!
Get back to the terminal. But don't run phpunit -c app
, that's old news. Just
run:
phpunit
And hey, our tests are even passing!
Changes to .gitignore
After making all of these changes, git status
looks a little crazy:
git status
Open up .gitignore
. Ok, this guy is totally out of date now. Let's help him out.
The var
directory now completely holds things that you do not need to commit
to your repository. So, instead of ignoring individual things, ignore the entire
/var/
directory. We do want to keep the .gitkeep
file in cache
and logs
,
but change each to start with /var/
. The app/config/parameters.yml
file doesn't
change and now we need to ignore any phpunit.xml
file if it exists. We don't need
to ignore /bin/
any longer and I'll remove composer.phar
from the list since
that's usually installed globally. Keep vendor
and web/bundles
at the bottom:
/var/ | |
!var/cache/.gitkeep | |
!var/logs/.gitkeep | |
/app/config/parameters.yml | |
/phpunit.xml | |
/vendor/ | |
/web/bundles/ |
Ah that looks much better.
Run git status
again in the terminal:
git status
SO much better.
Tiny Configuration Changes
Make one last little change in config.yml
. Under the framework
key, add assets: ~
:
// ... lines 1 - 20 | |
framework: | |
// ... lines 22 - 47 | |
assets: ~ | |
// ... lines 49 - 104 |
This guarantees that you can still use the asset()
function in twig. We don't need
this in 2.8 but in 3.0 you have to turn that on explicitly. This is actually pretty
cool: instead of the framework bundle turning on a lot of features automatically,
you get to opt into the features you want available.
Okay that is it! Enjoy your brand new fancy directory structure. I really like it
because the var
directory is stuff I don't need to touch, the app
directory is
just configuration and the tests/
are all together for the holidays.
this was an awesome article! thanks for writing it!