1466 1280 1553 1738 1413 1407 1874 1232 1561 1779 1294 1387 1943 1851 1497 1976 1042 1738 1381 1327 1856 1745 1123 1140 1164 1081 1263 1685 1220 1514 1724 1640 1747 1421 1475 1134 1508 1265 1009 1429 1749 1838 1037 1898 1236 1293 1411 1085 1206 1362 1609 1732 1736 1896 1861 1449 1675 1792 1480 1699 1543 1834 1889 1055 1955 1185 1104 1833 1780 1269 1536 1091 1893 1201 1675 1344 1766 1739 1081 1481 1660 1371 1512 1405 1331 1343 1016 1023 1894 1972 1941 1069 1405 1145 1372 1266 1804 1199 1705 Preloading in PHP 7.4 | PHPnews.io


Preloading in PHP 7.4

Written by Stitcher.io / Original link on Jul. 4, 2019

With PHP 7.4, support for preloading was added, a feature that could improve the performance of your code significantly.

In a nutshell, this is how it works:

Let's look at it in depth.

# Opcache, but more

While preloading is built on top of opcache, it's not exactly the same. Opcache will take your PHP source files, compile it to "opcodes", and store those compiled files on disk.

You can think of opcodes as a low-level representation of your code, that can be easily interpreted at runtime. So opcache skips the translation step between your source files and what the PHP interpreter actually needs at runtime. A huge win!

But there's more to be gained. Opcached files don't know about other files. If you've got a class A extending from class B, you'd still need to link them together at runtime. Furthermore, opcache performs checks to see whether the source files were modified, and will invalidate its caches based on that.

So this is where preloading comes into play: it will not only compile source files to opcodes, but also link related classes, traits and interfaces together. It will then keep this "compiled" blob of runnable code — that is: code usable by the PHP interpreter — in memory.

When a request arrives at the server, it can now use parts of the codebase that were already loaded in memory, without any overhead.

So, what "parts of the codebase" are we talking about?

# Preloading in practice

For preloading to work, you — developers — have to tell the server which files to load. This is done with a simple PHP script, there really isn't anything difficult to it.

The rules are simple:

Say you want to preload a framework, Laravel for example. Your script will have to loop over all PHP files in the vendor/laravel directory, and include them one by one.

Here's how you'd link to this script in php.ini:


And here's a dummy implementation:

$files = /* An array of files you want to preload */;

foreach ($files as $file) {

# Warning: Can't preload unlinked class

Hang on though, there's a caveat! In order for files to be preloaded, their dependencies — interfaces, traits and parent classes — must also be preloaded.

If there are any problems with the class dependencies, you'll be notified of it on server start up:

Can't preload unlinked class 
Unknown parent 

See, opcache_compile_file() will parse a file, but not execute it. This means that if a class has dependencies that aren't preloaded, itself can also not be preloaded.

This isn't a fatal problem, your server will work just fine; but you won't have all the preloaded files you actually wanted.

Luckily, there's a way to ensure linked files are loaded as well: instead of using opcache_compile_file you can use require_once, and let the registered autoloader (probably composer's) take care of the rest.

$files = /* All files in eg. vendor/laravel */;

foreach ($files as $file) {

There are some caveats still. If you're trying to preload Laravel for example, there are some classes within the framework that have dependencies on other classes that don't exist yet. For example, the filesystem cache class \Illuminate\Filesystem\Cache has a dependency on \League\Flysystem\Cached\Storage\AbstractCache, which might not be installed in your project if you're never using filesystem caches.

This can that you might run into "class not found" errors trying to preload everything. Luckily, in a default Laravel installation, there's only a handful of these classes, which can easily be ignored. For convenience, I wrote a little preloader class to make ignoring files more easy, here's what it looks like:

class Preloader
    private array $ignores = [];

    private static int $count = 0;

    private array $paths;

    private array $fileMap;

    public function __construct(string ...$paths)
        $this->paths = $paths;

        // We'll use composer's classmap
        // to easily find which classes to autoload,
        // based on their filename
        $classMap = require __DIR__ . '/vendor/composer/autoload_classmap.php';

        $this->fileMap = array_flip($classMap);
    public function paths(string ...$paths): Preloader
        $this->paths = array_merge(

        return $this;

    public function ignore(string ...$names): Preloader
        $this->ignores = array_merge(

        return $this;

    public function load(): void
        // We'll loop over all registered paths
        // and load them one by one
        foreach ($this->paths as $path) {
            $this->loadPath(rtrim($path, '/'));

        $count = self::$count;

        echo "[Preloader] Preloaded {$count} classes" . PHP_EOL;

    private function loadPath(string $path): void
        // If the current path is a directory,
        // we'll load all files in it 
        if (is_dir($path)) {


        // Otherwise we'll just load this one file

    private function loadDir(string $path): void
        $handle = opendir($path);

        // We'll loop over all files and directories
        // in the current path,
        // and load them one by one
        while ($file = readdir($handle)) {
            if (in_array($file, ['.', '..'])) {



    private function loadFile(string $path): void
        // We resolve the classname from composer's autoload mapping
        $class = $this->fileMap[$path] ?? null;

        // And use it to make sure the class shouldn't be ignored
        if ($this->shouldIgnore($class)) {

        // Finally we require the path,
        // causing all its dependencies to be loaded as well


        echo "[Preloader] Preloaded `{$class}`" . PHP_EOL;

    private function shouldIgnore(?string $name): bool
        if ($name === null) {
            return true;

        foreach ($this->ignores as $ignore) {
            if (strpos($name, $ignore) === 0) {
                return true;

        return false;

By adding this class in the same preload script, we're now able to load the whole Laravel framework like so:

// …

(new Preloader())
    ->paths(__DIR__ . '/vendor/laravel')

# Does it work?

That's of course the most important question: were all files correctly loaded? You can simply test it by restarting the server, and dump the output of opcache_get_status() in a PHP script. You'll see it has a key called preload_statistics, which will list all preloaded functions, classes and scripts; as well as the memory consumed by the preloaded files.

# Composer support

One promising feature is probably an automated preloading solution based on composer, which is used by most modern day PHP projects already. People are working to add a preload configuration option in composer.json, which in turn will generate the preload file for you! At the moment, this feature is still a work in progress, but you can follow it here.

# Server requirements

There's two more important things to mention about the devops side when using preloading.

You already know that you need to specify an entry in php.ini in order for preloading to work. This means that if you're using shared hosting, you won't be able to freely configure PHP however you want. In practice, you'll need a dedicated (virtual) server to be able to optimise the preloaded files for a single project. So keep that in mind.

Also remember you'll need to restart the server (php-fpm is sufficient if you're using it) every time you want to reload the in-memory files. This might seem obvious for most, but still worth the mention.

# Performance

Now to the most important question: does preloading actually improve performance?

The answer is yes, of course: Ben Morel shared some benchmarks, which can be found in the same composer issue linked to earlier.

Interestingly enough, you could decide to only preload "hot classes" — classes that are used often in your codebase. Ben's benchmarks shows that only loading around 100 hot classes, actually yields better performance gains than preloading everything. It's a difference of a 13% and 17% performance increase.

Which classes should be preloaded relies of course on your specific project. It would be wise to simply preload as much as possible at the start. If you really need the few percentage increases, you would have to monitor your code while running.

All of this can of course also be automated, and will probably be done in the future.

For now, most important to remember is that composer will add support, so that you don't have to make preload files yourself, and that this feature is very easy to setup on your server, given that you've got full control over it.

Will you be using preloading once PHP 7.4 arrives? Any remarks or thoughts after reading this post? Let me know via Twitter or e-mail.


« Things dependency injection is not about - Typed properties in PHP 7.4 »