If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWhy are the cache
files writable by everyone? The answer is inside our code.
Open up bin/console
. In my project, I uncommented a umask(0000)
line:
... lines 1 - 7 | |
// if you don't want to setup permissions the proper way, just uncomment the following PHP line | |
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information | |
umask(0000); | |
... lines 11 - 27 |
I also added this in web/app.php
:
... lines 1 - 4 | |
// If you don't want to setup permissions the proper way, just uncomment the following PHP line | |
// read http://symfony.com/doc/current/book/installation.html#checking-symfony-application-configuration-and-setup | |
// for more information | |
umask(0000); | |
... lines 9 - 27 |
Thanks to this, whenever Symfony creates a file - like cache files - the permissions default to be writable by everyone.
I added these precisely to avoid permissions problems. But it's time to fix them properly. In app.php
, comment that out:
... lines 1 - 4 | |
// If you don't want to setup permissions the proper way, just uncomment the following PHP line | |
// read http://symfony.com/doc/current/book/installation.html#checking-symfony-application-configuration-and-setup | |
// for more information | |
//umask(0000); | |
... lines 9 - 27 |
In console
, comment it out... but also copy it and move it inside the debug if statement:
... lines 1 - 7 | |
// if you don't want to setup permissions the proper way, just uncomment the following PHP line | |
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information | |
//umask(0000); | |
... lines 11 - 19 | |
if ($debug) { | |
umask(0000); | |
... line 22 | |
} | |
... lines 24 - 28 |
During development, umask()
makes our life really easy... cache files can be created and re-created by everyone. So I want to keep it. In fact, in web/app_dev.php
, we also have a umask()
call:
... lines 1 - 5 | |
// If you don't want to setup permissions the proper way, just uncomment the following PHP line | |
// read http://symfony.com/doc/current/book/installation.html#checking-symfony-application-configuration-and-setup | |
// for more information | |
umask(0000); | |
... lines 10 - 32 |
Again, this matches how Symfony 4 will work, out of the box.
Find your local terminal, commit those changes and push them:
git add -u
git commit -m "no writable in prod mode"
git push origin master
Deploy!
ansible-playbook ansible/deploy.yml -i ansible/hosts.ini --ask-vault-pass
Ok! Let's see what happens without umask
on production. When it finishes, find your server terminal, move out of current/
and then back in. Check the permissions:
ls -la var/cache/prod
There it is! The files are writable by the user and group, but not by everyone. Our web server user - www-data
- is not in the same group as our terminal user. Translation: the cache files are not writable by the web server.
So... will it blend? I mean, will the site work? Moment of Truth. Refresh! It does work! Woh! This is huge!
How is this possible? How can the site work if our Symfony app can't write to the cache/
directory? The key is the cache:warmup
task:
... lines 1 - 14 | |
- name: Warm up the cache | |
command: '{{ release_console_path }} cache:warmup --env=prod' | |
... lines 17 - 48 |
I'm going to tell you a small lie first. The cache:warmup
command creates every single cache file that your application will ever need. Thanks to this, the cache
directory can totally be read-only after running this command.
Great, right? Now, here is the true story. The cache:warmup
task creates almost all of the cache files that you will ever need. But, there are a few types of things that simply can't be cached during warm up: they must be cached at the moment they're needed. These include the serializer and validation cache, for example.
Knowing this, our site works now, but it should break as soon as we try to use the serializer or validation system... because Symfony won't be able to cache their config. Well, let's try it!
I created an API endpoint: /api/videos
that uses the serializer. Try it! Woh! It works! But... the serializer cache shouldn't be able to save. What the heck is going on?
Here is the secret: whenever Symfony needs to cache something after cache:warmup
, it uses a service called cache.system
to do this:
./bin/console debug:container cache.system
This is not a service you should use directly, but it's critically important.
Tip
Actually, you can use this service, but only to cache things that are needed to make your app work (e.g. config). It's cleared on each deploy
This service is special because it automatically tries several ways of caching. First, if APCu is available, it uses that. On the server, check for it:
php -i | grep apcu
Right now, we don't have that. No problem, the service then checks to see if OpCache is installed:
php -i | grep opcache
We do have this installed, and you should to. Thanks to it, instead of trying to write to the var/cache
directory, Symfony uses temporary file storage and a super fast caching mechanism.
If neither APCu nor OpCache are installed, then it finally falls back to trying to write to the cache/
directory. So basically, in order for the cache
directory to be read only... we don't need to do anything! Just, install OpCache - which you should always have - or APCu.
Great! But, I do have one more question: if we use APCu or OpCache, do we need to clear these caches when we deploy? For example, if some validation config was cached to APCu and that config is changed... don't we need to clear the old cache when we deploy? Actually, no! Each time you deploy, well, each time you run cache:warmup
, Symfony chooses a new, random cache key to use for system.cache
. This effectively clears the system cache on each deploy automatically!
This is a long way of saying that... well... the cache directory simply does not need to be writable. But, we can do a few things to improve performance!
Hey Cesar
You can add a step for fixing file permissions, something like this:
- name: Fix directories permissions
become: true
file:
path: 'var/cache/prod/easy_admin' # adjust this value to your needs
state: directory
mode: 0777
recurse: yes
Cheers!
Hey Ivan,
I just double checked it: downloaded the course code and look at src/AppBundle/Controller/DefaultController.php in finish/ directory and I can see the endpoint there, here's its code:
/**
* @Route("/api/videos")
*/
public function videosApiAction()
{
$videos = $this->getVideoRepository()
->findAll();
return $this->json(['videos' => $videos]);
}
Maybe you have to clear the cache? :)
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"doctrine/doctrine-bundle": "^1.6", // 1.6.8
"doctrine/orm": "^2.5", // v2.7.2
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"sensio/distribution-bundle": "^5.0.19", // v5.0.20
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
"symfony/monolog-bundle": "^3.1.0", // v3.1.0
"symfony/polyfill-apcu": "^1.0", // v1.4.0
"symfony/swiftmailer-bundle": "^2.3.10", // v2.6.3
"symfony/symfony": "3.3.*", // v3.3.5
"twig/twig": "^1.0||^2.0", // v1.34.4
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
"predis/predis": "^1.1", // v1.1.1
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0", // v3.3.5
"doctrine/data-fixtures": "^1.1", // 1.3.3
"hautelook/alice-bundle": "^1.3" // v1.4.1
}
}
# ansible/requirements.yml
-
src: DavidWittman.redis
version: 1.2.4
-
src: ansistrano.deploy
version: 2.7.0
-
src: ansistrano.rollback
version: 2.0.1
Hi guys:
I returned to check Chapter 16 because I have deployed a Symfony App that uses Easy Admin Bundle and I got an error saying ../var/cache/prod/easy_admin was not writable. I fixed it giving 0777 to that folder but I wonder if there is a better solution. Do you have any tip about this? I will appreciate it.
Cesar