How I improved the performance of my big PHP web project

June 2, 2019
How I improved the performance of my big PHP web project | Ivo Petkov
In this article, I'll talk about the single architecture approach that improved the most the performance of Bear CMS (it's a content management system I work on) and made me confident about its future updates. I have to admit that designing and optimizing systems is more like a passion for me than just a job, but I can also see the benefits this brings when running such systems on a high scale (CPU cycles can be costly and user time is too valuable to waste).

You may have heard about lazy loading

This is a concept that enables resources to be loaded and initialized only when needed. It's recommended when rendering a web page that has a large number of images (an online shop for example) and most of them are under the fold (not visible until the user scrolls). You may have heard of autoloading PHP classes using spl_autoload_register(). Composer already uses this technique, so you can be certain no precious memory and time is wasted when using third-party libraries.

Lazy loading requirements

To apply this concept you need the following components:
1. A way to register a resource for lazy loading.
2. A mechanism to detect if a resource is needed and load it.
The spl_autoload_register() method from the example above, with a combination of a function (to include the classes) covers both requirements.

How lazy loading helped me big time

I hope you will agree, but a Content Management System is no small project. There is a very diverse set of features that must be supported (themes, editing UI, pages, content elements, etc.) and many different types of requests to handle (HTML pages, images, form submits, background tasks, etc.). To make development easy and enable future improvements without performance implications I decided to enforce lazy loading in the heaviest APIs. Here is an example:
$cms->themes->register('the-name-of-the-theme', function() { // initialize the theme here. });
This enables registering multiple themes and only using the one needed, only when needed.

As you can see the trick here is using ...

Anonymous functions!

Anonymous functions in PHP enable you to postpone or skip the execution of some code. Even defining an array takes memory, and it makes a difference if this array holds lots of data. Let's compare:
$data = array_fill(0, 1000000, 'John'); // Fills an array with a million text entries
The code above takes around 35MB memory and 0.016 seconds on my machine.
$source = function() { return array_fill(0, 1000000, 'John'); };
This code takes 0 memory and 0 seconds because ... the content of the function is not executed. Smart! :)

This type of code (the code that is never run in a request) is the single biggest reason I was able to lower the CPU and memory requirements of my application and still keep it functional.

The real code from the CMS example above looks more like this:
$cms->themes->register('the-name-of-the-theme', function($theme) { $theme->options = 'data!'; // Defines theme options so that the administrators can customize it $theme->styles = 'data!'; // Defines ready-made styles to choose from $theme->manifest = 'data!'; // Information about the theme and its author });
As you can see there are a lot of things that need to be done to define a theme. They all take memory and CPU cycles and are not needed on every request. Requests for images, favicons or sitemaps do not need themes.

Anonymous functions as properties

We can take the approach even further and bring anonymous functions to properties. Here is another update on the themes example above:
$cms->themes->register('the-name-of-the-theme', function($theme) { $theme->manifest = function() { return [ 'name' => 'My awesome theme', 'description' => 'This is ...', 'author' => [ 'name' => 'John Smith', 'email' => '' ] ]; }; });
As you might have guessed it, the information in the manifest is needed only in very rare cases, so there is no need to spend time and memory on every request.

Identifying code that can be skipped can make a big difference.

Anonymous functions are beautiful!

If you've written, or seen JavaScript code, you might take them for granted as they are the basic way of defining asynchronous code (in setTimeout for example). In PHP however, due to lack of async support, they are rarely used. You might have used them to sort and filter arrays and I'd like to encourage you to use them more. They keep your code clean (and can even improve it) and can benefit the performance significantly. Even if you are not architecting systems or working on big projects, please, challenge yourself to try them for recursions or other internal optimizations.


Thanks for getting this far. I hope this article has inspired you to optimize your projects and improve their performance. I'll be happy to learn about your results and thoughts on this technique.