Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

PHP 7.4 preload

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.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

There are two last small - but cool - features I want to talk about.

Huh? Preload?

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.

The Generated Preload File

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.

Leave a comment!

11
Login or Register to join the conversation
Kevin B. Avatar
Kevin B. Avatar Kevin B. | posted 2 years ago

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.

Reply

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!

Reply

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.

Reply

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!

2 Reply

Thank you Ryan.
Great answer and explaining!

Reply
picks Avatar

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...

Reply

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!

Reply
picks Avatar
picks Avatar picks | sadikoff | posted 2 years ago | edited

Hi sadikoff thanks for your answer. This is sad :/

Reply

Yeah! I had same thoughts. But this is reality for now, probably something will change in future!

Reply
Sherif B. Avatar
Sherif B. Avatar Sherif B. | posted 3 years ago

hi do you know how to do this opcache on something like dokku

Reply

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

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice