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 SubscribeThere are two last small - but cool - features I want to talk about.
For the first, search for "Symfony preload" to find a blog post talking about it: "New in Symfony 4.4: Preloading Symfony Applications in PHP 7.4".
Here's the deal: in PHP 7.4 a new feature was added called "preloading". Basically, in your php.ini
file, you can point an opcache.preload
setting at a file that contains a list of all the PHP files that your application uses.
Tip
You may also need to set an opcache.preload_user
setting set to your web server
user (e.g. www-data
).
By doing this, when PHP starts, it will "preload" those files into OPcache. You're effectively giving your web-server a "head" start: telling it to load the source code it will need into memory now so that it's ready when you start serving traffic.
What's the catch? Well, first, you need to create this "list of files", which we'll talk about in a minute. Second, each time these files change - so on each deploy - you need to restart your web server. And third, until PHP 7.4.2, this feature was a little buggy. It should be fine now, but there still could be some bugs left. Proceed with caution.
So how does Symfony fit into this? Symfony knows a lot about your app, like which classes your app uses. And so, it can build that "preload" file automatically.
Check it out, at your terminal, clear the prod cache:
php bin/console cache:clear --env=prod
Now, in PhpStorm, check out the var/cache/prod/
directory... here it is: App_KernelProdContainer.preload.php
. This file - which basically includes a bunch of classes - is a PHP 7.4 preload file. All you need to do is update the opcache.preload
setting in php.ini
to point to this file, restart your web server any time you deploy and, voilĂ ! Instant performance boost!
How much of a boost? I'm not sure. It's such a new feature that benchmarks are only starting to be released. The blog post says 30 to 50%, I've seen other places saying more like 10 or 15%. Either way, if you can get your system set up to use it, free performance!
Next, let's talk about one last feature: a command you can run to make sure all your service wiring and type-hints are playing together nicely. Because in our app, there is a problem.
Hey Kevin B.!
Thanks for posting this :). My guess is that this might only be needed if your fpm is running as root... but I'm not sure - the docs seem to suggest that (I'm thinking between the lines: if your fpm is running as root, then that might be who would do preloading... but then that's not allowed):
> Preloading code as root is not allowed for security reasons. This directive facilitates to let the preloading to be run as another user
Regardless, at the very least, many users will need this, so we'll add a note :).
Thanks!
Hello,
It is not clear to me how Symfony decides witch files to be put in the preload file.
How can I designate specific classes to be loaded in the preload without manually writing them in the preload file.
Hey Donjedi!
Good questions :).
First: how does Symfony decide which files to preload? This is decided on a system-by-system basis. Internally, there is a "cache warmer" system. This is a system that runs if you execute cache:warmup, which you normally should do as part of a deploy. Anyways, this system predates preloading by many years and its job (apologies if you're already aware) is to create as much of the cache as possible - e.g. converting the Twig templates to their cached version, parsing and caching many annotations, routing, etc. When preloading was added, the cache warmers got a second job: during their warmup process, they can return an array of classes that should be preloaded. Any bundle can add a "cache warmer", so each bundle is basically deciding which classes *it* thinks are important enough to preload. An example is the RouteCacheWarmer - https://github.com/symfony/... - which actually just calls the Router itself - https://github.com/symfony/... - and that returns the generator and matcher classes.
Additionally, each "bundle" class is added to the preload automatically. That, in itself, isn't that important, but bundles can use that to notify the preload system of other important classes that should be included - e.g. https://github.com/symfony/...
In *addition* to that, there is also something called the "hot path" in Symfony, which are classes that are determined to basically be *always* needed, like the event dispatcher. These also become part of preloading, in addition to any dependent services that these have.
So... all of this is determined internally, and it's quite complex to get a balance of what's "just right" for most setups.
Second, how can you control this process and add your own stuff? Since Symfony 5.1, there's a tag for that! container.preload - https://symfony.com/blog/ne...
Let me know if you have any more questions - it's a great system, but it is complex internally :).
Cheers!
Hello, do you have any idea on how to set a distinct opcache.preload value for each website when a server host many Symfony projects? There is only one php.ini...
Hey picks
As I know it's impossible to make opcache.preload work with many symfony projects on one server. This feature is low level, and it works on per server basis. So you can run only 1 Symfony project on host where proload configured (I guess you even can run only 1 project at all with preload configured, no matter what project it is).
Cheers!
Yeah! I had same thoughts. But this is reality for now, probably something will change in future!
Hey sherif807,
Unfortunately, I haven't heard about Dokku, so don't know, sorry! Try to search in their docs, or probably ask their support if they have one. Also, this kind of questions might be answered on StackOverflow, would be good to check I think.
Cheers
// composer.json
{
"require": {
"php": "^7.3.0",
"ext-iconv": "*",
"antishov/doctrine-extensions-bundle": "^1.4", // v1.4.2
"aws/aws-sdk-php": "^3.87", // 3.110.11
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^2.0", // 2.0.6
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.1.2
"doctrine/orm": "^2.5.11", // v2.7.2
"doctrine/persistence": "^1.3.7", // 1.3.8
"easycorp/easy-log-handler": "^1.0", // v1.0.9
"http-interop/http-factory-guzzle": "^1.0", // 1.0.0
"knplabs/knp-markdown-bundle": "^1.7", // 1.8.1
"knplabs/knp-paginator-bundle": "^5.0", // v5.0.0
"knplabs/knp-snappy-bundle": "^1.6", // v1.7.0
"knplabs/knp-time-bundle": "^1.8", // v1.11.0
"league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
"league/flysystem-cached-adapter": "^1.0", // 1.0.9
"league/html-to-markdown": "^4.8", // 4.8.2
"liip/imagine-bundle": "^2.1", // 2.3.0
"nexylan/slack-bundle": "^2.1", // v2.2.1
"oneup/flysystem-bundle": "^3.0", // 3.3.0
"php-http/guzzle6-adapter": "^2.0", // v2.0.1
"sensio/framework-extra-bundle": "^5.1", // v5.5.3
"symfony/asset": "5.0.*", // v5.0.2
"symfony/console": "5.0.*", // v5.0.2
"symfony/dotenv": "5.0.*", // v5.0.2
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "5.0.*", // v5.0.2
"symfony/framework-bundle": "5.0.*", // v5.0.2
"symfony/mailer": "5.0.*", // v5.0.2
"symfony/messenger": "5.0.*", // v5.0.2
"symfony/monolog-bundle": "^3.5", // v3.5.0
"symfony/security-bundle": "5.0.*", // v5.0.2
"symfony/sendgrid-mailer": "5.0.*", // v5.0.2
"symfony/serializer-pack": "^1.0", // v1.0.2
"symfony/twig-bundle": "5.0.*", // v5.0.2
"symfony/twig-pack": "^1.0", // v1.0.0
"symfony/validator": "5.0.*", // v5.0.2
"symfony/webpack-encore-bundle": "^1.4", // v1.7.2
"symfony/yaml": "5.0.*", // v5.0.2
"twig/cssinliner-extra": "^2.12", // v2.12.0
"twig/extensions": "^1.5", // v1.5.4
"twig/inky-extra": "^2.12" // v2.12.0
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.3.0
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/browser-kit": "5.0.*", // v5.0.2
"symfony/debug-bundle": "5.0.*", // v5.0.2
"symfony/maker-bundle": "^1.0", // v1.14.3
"symfony/phpunit-bridge": "5.0.*", // v5.0.2
"symfony/profiler-pack": "^1.0", // v1.0.4
"symfony/var-dumper": "5.0.*" // v5.0.2
}
}
FYI, just setting `opcache.preload` didn't work for me with PHP-FPM. When restarting the service, I had an error saying opcache.preload_user was missing.
Setting the `opcache.preload_user` to the PHP-FPM username solved it.