One reasons for PHP still being considered slow is a consequence of how it works under web server environment: every time a client sends request, application is initialized from scratch - it runs all the bootstrap code. Bootstrapping is repeated over and over again, for every connecting client.
While this is an obvious waste of resources, it is also very difficult to avoid without rewriting an application under different architecture. Is there anything that could be done to at least reduce impact of application bootstrap, without making any changes to actual application? As it turns out, there is.
Merging class files
Even simple application written in modern, full-stack framework can use 100-200 classes just to show "Hello, World" in the browser. For each of this classes PHP needs to check if file exists, then open it and parse. This creates significant overhead even when opcache is enabled. There's an easy way to go around it though: you can put all classes that used on every request in a single file.
There are at least two frameworks that can merge this files for you: Symfony (via Bootstrap Files) and Zend Framework (via EdpSuperluminal module). Their solutions are difficult to reuse though, as they are tailored to work with given framework.
Using ideas from this projects as a base, I crafted a general solution, that can be used everywhere:
Idea is to let you choose any classes you want, and merge them together. Just as it is done in Symfony, you can automate this process with composer hooks - merged file will be refreshed whenever you update your dependencies.
This is how your config could look like when building Zend Expressive app:
<?php return [ Zend\ServiceManager\Config::class, Zend\ServiceManager\ServiceManager::class, Zend\Expressive\Container\ApplicationFactory::class, Zend\Expressive\Router\Aura::class, Aura\Router\Router::class, Aura\Router\AbstractSpec::class, Aura\Router\RouteCollection::class, Aura\Router\RouteFactory::class, Aura\Router\Regex::class, Aura\Router\Generator::class, Zend\Expressive\Emitter\EmitterStack::class, Zend\Diactoros\Response\SapiEmitter::class, Zend\Stratigility\MiddlewarePipe::class, Zend\Expressive\Application::class, Zend\Stratigility\Route::class, Zend\Expressive\Router\Route::class, Aura\Router\Route::class, Zend\Diactoros\ServerRequestFactory::class, Zend\Diactoros\ServerRequest::class, Zend\Diactoros\Uri::class, Zend\Diactoros\Stream::class, Zend\Diactoros\PhpInputStream::class, Zend\Diactoros\HeaderSecurity::class, Zend\Diactoros\Response::class, Zend\Stratigility\FinalHandler::class, Zend\Stratigility\Http\Request::class, Zend\Stratigility\Http\Response::class, Zend\Stratigility\Next::class, Zend\Stratigility\Dispatch::class, Zend\Stratigility\Utils::class, Zend\Diactoros\Response\HtmlResponse::class, ];
You don't have to worry about order in which you place this classes, nor about their hard dependencies (parent classes, interfaces...) - Class Dumper will add them automatically to the merged file.
Let's assume you put this list in
classes-to-cache.php file. Merging files
together is as easy as running console command:
php ./vendor/bin/dump-classes.php classes-to-cache.php classes.php.cache --strip
--strip switch will ensure that generated file is as small as possible.
Finally, start using cached classes, by including generated file in your
<?php include 'vendor/autoload.php'; include 'classes.php.cache';
Real performance gain can vary depending on number of files cached and your PHP configuration. I've tried it under different conditions, and I've seen improvements ranging from 2% to 10%. Of course if your App does some IO (database access), the end result will be even smaller. On the other hand, efficiency will increase with number of files merged together. Always ensure that OpCache enabled, and that merged file doesn't grow too big (try keeping it below 1MB).
Despite low efficiency in some cases, this optimization is worth considering, as it comes almost for free - create list of classes, update composer.json, and that's it!