Composer: How it should preload in PHP 7.4

Nobody can agree with something, but I can!

Italo Baeza Cabrera
The Startup
6 min readNov 27, 2019

--

Updated 2019–11–28 with better keys and functionality.

Preloading it's one of the important things that PHP 7.4 will enable for developers who look for performance. This feature seems like a “warm up” before the JIT engine that PHP 8 will (or should) introduce. This may suffice until it’s ready, and who knows if it and may work in tandem.

Anyway, the preloading feature is explained in this article, but the basics of it are very simple: a PHP script is pointed in the php.ini to load files in memory (preload) when the process starts. In conjunction with Opcache and Composer’s Autoloader, the files can also be compiled and linked together once, and become available to all subsequent requests. With that, PHP doesn’t have to retrieve the files and compile them in each request.

Composer, by the other hand, haven’t agreed about how to help to preload, apart from the offering the convenience of the Autoloader itself. The facts are:

  • Preloading is being introduced first in PHP 7.4.
  • There is no Composer directive to aid in preloading files.
  • Preloading needs access to php.ini, which means having access to the process itself.
  • Preloading all files may not necessarily incur in better performance than preloading only most requested files.

In other words, preloading will be only usable for those who have access to their servers skeletons and know their gist. That excludes shared servers and some PaaS that do not offer a way to tinker with the php.ini.

Now, how Composer can help on preloading, considering it’s like a novelty? Here is my take.

How preloading should work, the easy way

The preload mechanism should be based on a list of files that would be uploaded and kept in memory at startup. Since its a list, we should work with an array of files, and let Composer to handle the magic, instead of uploading each file manually.

Composer should take the list of files set by the application (the root project), and compile everything into a file that PHP could consume with no problem.

At the same time, we should have flexibility on including and excluding packages from preloading.

Preloading should never be enabled at package-level, since it should be responsibility of the developer to enable or disable preloading for each package.

Preloading in Composer should be optional. The developer could rescind from this functionality and just tell PHP to load it’s own preloader, that may be based on Opcache analytics — this will depend on the application load and would be very efficient compared to a blind all-files preload.

Everything starts in the preload.json

To keep things simple, we should slap a preload.json file in the root of the project.

This file would include the preloading files that Composer could pick up. Since it’s a JSON file, the developer is free to use even a custom package to automatically generate it — if you ask me, it would be cool if Composer could come with an utility that created this JSON file from a script.

Basing our preloading with this preload.json file allows to quickly check if a the project has enabled preloading: if this file doesn’t exists, preloading is not supported or not desired.

Let’s have a walk on the keys and what they do.

pre-compile

These files will be run by Composer. Each script must return an array of absolute paths of files to include into the preloading list, that will serve as a base list.

"pre-compile": [
"my-script.php",
"my-other-script.php"
]

These files will be run in the order given.

The purpose of this is to let the developer create its own list of files as they see fit, instead of relying solely on the JSON file. These are run before anything else, and yes, you could have a preload.json with only this key. Since they’re PHP files, you could even include other files to compile your array.

extensions

This is a list of file extensions to preload. By default it only picks up files with the php extension.

"extensions": ["php", "php5", "php7"]

For example, you could add a directory full of *.phtml files, but only a few handy php files. This will allow Composer to only pick the latter, instead of include every single file inside the directory, even some you don’t desire.

Obviously this is overridden by including a file manually.

files

This key tells composer to load each file in the list given, using the path relative to where the composer.json lives.

"files": [
"helpers.php",
"app/Models/*",
"app/Controllers/*/Http/*",
"app/Views/Compiled*.php",
]

The list is very easy to understand:

  • Use relative paths to include files or directories.
  • Directories will include only the child files inside them (non-recursively).
  • Recursive paths are indicated with an ending wildcard *.
  • You can also combine the wildcard to include, for example, some files or some directories: src/Clients/*/Stores, or src/Model*.php.

Large applications can benefit from selectively including files without having to put each file manually or create large scripts tied to the application.

If you would want to simply preload all files set in the autoload key of your Composer JSON file, set this to true.

namespace

This will instruct Composer to load files be the given namespace or class name, like a file or directory. Indeed, the same mechanics applies, and allows to even dynamically call namespaces from other packages installed, if they exist.

"namespaces": [
"App\\Models",
"App\\Controllers\\",
"App\\Views\\MainView",
"Vendor\\Package\\*",
]

This may be also handy for large applications, since it may be dependant more on namespaces rather than files that could change from place at any given time. Composer would automatically retrieve the files using that namespace and append them to the list accordingly.

packages

This key would allow to load other files registered from external packages, like helpers files or clases tied to a namespace.

"packages": {
"symfony/http-client": true,
"robert/*-client": true,
"vendor/package": {
"files": true,
"namespace": true
},
"foo/bar": {
"files": {
"helpers.php",
"loaders/*"
},
"namespace": [
"Foo\\Bar\\DynamicLoaders\\*",
"Foo\\Bar\\Clients"
]
}
}

This is very easy to understand: if it is true, it will load everything inside the autoload key of the composer.json of that package. If not, we can have more granular control on what to include.

If the file key is true, it will include all the files registered in the autoload, otherwise it will default to false. Same goes for the namespace key.

And we can go even further by cherry picking each file or namespace using the same rules from above. Obviously this will not use the autoload key.

output

This is just the filename of the compiled preload list.

"output": "preload-compiled.php"

Simple to build

Now that our project has its preloading list ready, we can just call Composer to build the main preloading script:

composer preload

This will create a preload-compiled.php with all the files that should be preloaded by PHP. Of course you can change the output name for whatever you want.

You should also override the preload keys with parameters:

composer preload \
--input=my-custom-preload-list.json \
--output=my-preload.php

Disabled by default

Projects without a preload.json will return an error when trying to build a preload. This is because Composer won’t (and shouldn’t) blindly guess what to preload.

Going back to the original point, the Preload doesn’t meddles with Composer normal functionality. Since it’s a console command away, on local development you can totally leave out preloading. The only thing that the preload mechanisms needs from Composer is an Autoload file, that should be generated if it doesn’t exists. I mean, 2020, everything uses PSR-4 and Autoloader… right?

The end result

You should receive something like this:

It’s basically a raw list of files that will be preloaded, with some aiding of Composer autoloader. PHP will run this file once and that’s history.

These are my two and a half cents. I sincerely expect that Composer offers some way to help preloading files without needing to hack your own way into preloading your project files.

Since it’s not part of the Composer core, you should still be able to put your own files based on Opcache usage analytics to just preload the most important files and leave aside those less accessed. To say something, imagine that instead of pre-loading 1500 files for 100MB, you just could preload 150 for 10MB and still have 99% of the original performance.

--

--

Italo Baeza Cabrera
The Startup

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com