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?
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 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.
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
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.
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.
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.
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!
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!
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.
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.
After following your instructions i can start the application but if i want to run the /app_dev.php dev mode,i get an error:
FatalErrorException in AppKernel.php line 0:
Compile Error: Cannot declare class AppKernel, because the name is already in use
I followered your instructions to the changes of the AppKernel and app_dev php files.
Whats confusing, a new bootstrap.php.cache file will be created inside the /app folder instead of the var /var folder.
So i guess there is something wrong with the ScriptHandler::buildBootstrap part
In the official Symfony 2.7 documentation the Incenteev yaml loader is used. This is not part of your description.
Do i need to adjust it too? Does it even matter? It is used for building a parameters.yaml out of a parameters.yaml.dist.
Why does my buildBootrap Scripthandler creates a bootstrap.php.cache file still in my /app folder, if i configured it to var, and why does the app_dev.php thinks there is an duplicate clss issue?
Running without app_dev.php works fine.
@Edit:
I found the Error myself. I still had one require_once __DIR__.'/../app/AppKernel.php'; left which i overlooked.
With autoload.php in both app.php and app_dev.php it works just fine :)
Hi,
I follow the tutorial doing line by line all.
But at video 7min 19 sec
just after to set directory (app,web,var, bin and tests) in "extra" (in composer.json)
I run the command "composer install" and error appears:
/application$ composer install
[...]
Could not open input file: app/console
Script Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::clearCache handling the post-install-cmd event terminated with an exception
In ScriptHandler.php line 220:
An error occurred when executing the "'cache:clear --no-warmup'" command.
[...]
it seems it try to look at app/console and not at /bin/console
Now? what can I look for?
// composer.json
{
"require": {
"php": ">=5.3.9",
"ext-pdo_sqlite": "*",
"doctrine/doctrine-bundle": "~1.5", // 1.6.3
"doctrine/doctrine-fixtures-bundle": "~2.2", // 2.3.0
"doctrine/orm": "~2.4", // v2.5.4
"erusev/parsedown": "~1.5", // 1.6.0
"ezyang/htmlpurifier": "~4.7", // v4.7.0
"incenteev/composer-parameter-handler": "~2.1", // v2.1.2
"ircmaxell/password-compat": "~1.0", // v1.0.4
"knplabs/knp-paginator-bundle": "~2.4", // 2.5.3
"leafo/scssphp": "~0.1.5", // v0.1.10
"patchwork/jsqueeze": "~1.0", // v1.0.7
"sensio/distribution-bundle": "^5.0", // v5.0.7
"sensio/framework-extra-bundle": "~3.0", // v3.0.16
"symfony/assetic-bundle": "~2.8", // v2.8.0
"symfony/monolog-bundle": "~2.7", // 2.11.1
"symfony/swiftmailer-bundle": "~2.3", // v2.3.11
"symfony/symfony": "3.0.*", // v3.0.9
"twig/extensions": "~1.2" // v1.3.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0" // v3.0.7
}
}
this was an awesome article! thanks for writing it!