Fast Autoloading Classes in PHP
Fast Autoloading Classes in PHP
Cheatsheet, steps to better autoloading
- OPcache
- composer classmap dump
- Tune your file/path caches
- Hybrid PSR-0 autoloader
- Runtime classmap generator
OPcache
opcache.enable_file_override=1
opcache.memory_consumption=96
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=7963
opcache.save_comments=1
opcache.consistency_checks=0
opcache.enable=1
opcache.optimization_level=0xFFFFFFFF
opcache.huge_code_pages=0
Tune your production OPcache to ensure persistent opcache w/o invalidation
opcache.revalidate_freq=300
opcache.validate_timestamps=0
opcache.blacklist_filename=/some/path/to/blacklist_file
Invalidate & recompile OPcache on each deploy ONLY for files you modified, see
#!/bin/bash
# cd "$(dirname "$0")";
# http://mywiki.wooledge.org/BashSheet#Parameter_Operations
cd ${0%/*};
printf "\n";
find $PWD/.. \( -iname "*.php" -o -iname "*.tpl" -o -iname "*.ini" \) -type f -mmin -10 | \
xargs -n1 -I % ./php-fpm-cli -connect '/var/run/php5-fpm.sock' -r \
"echo 'PHP OPCODE CACHE';
echo (opcache_get_status(FALSE) && opcache_get_status(FALSE)['opcache_enabled']) ? (
(opcache_invalidate('%') && opcache_compile_file('%')) ? 'REFRESHED %' : 'FAILED OPCODE INVALIDATION %'
) : 'OFF OR DONE %';print \"\n\";";
printf "\n";
Composer
composer dump-autoload -o
(real) path cache
Each time code does include or require, include_path is used to try to find file (unless its absolute path), that means if the needed file is in the last folder in your include path, it will take the longest time to find it. So keep your include path’s short (1 or 2 paths) and keep all your files organized if you use relative path’s.
realpath_cache_size=256k
realpath_cache_ttl=86400
include_path=.
user_ini.cache_ttl=86400
Hybrid PSR-0 autoloader
Another thing u could do is, if you have a library, lets say zendframework in (composer/zendframework/) instead of loading whole composer with all your packages, you should do hybrid autoloader
Instead of loading whole composer classmap, just put a symlink into your include_path to your zendframework, if library supports PSR-0 ofc.
ln -s composer/zendframework/zendframework1/library/Zend mylibs/Zend
And we could also modify PSR-0 autoloader to work with composer more efficiently, and ONLY include composer if class is not found without our own libraries (u dont need 100% of our composer libs on EVERY request)
- optional classmap feature
- lazy composer include
- PHP-7 fix & error_reporting level side-effect fix
/**
* Hybrid PSR-0 Autoloader based on PHP-FIG
* http://www.php-fig.org/psr/psr-0/
* @author Sergei Shilko <contact@sshilko.com>
*/
$classMap = [];
spl_autoload_register(function (string $className) use (&$classMap) {
if (isset($classMap[$className])) {
return include $classMap[$className];
}
$oname = $className;
$className = ltrim($className, '\\');
$fileName = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
$filePath = stream_resolve_include_path($fileName);
if ($filePath) {
$classMap[$oname] = $filePath;
/**
* some frequent packages are directly mapped there via PSR-0
* to prevent full composer autoloaded
*/
return include_once $filePath;
} else {
/**
* Only need autoloader once
*/
if (!isset($GLOBALS['composer_autoloaded'])) {
$nativeErrorLevel = error_reporting();
$GLOBALS['composer_autoloaded'] = true;
$loader = require __DIR__ . '/../composer/autoload.php';
/**
* PHP7 fix for first-time composer autoload -->
*/
$loader->loadClass($oname);
/**
* PHP7 fix for first-time composer autoload <--
*/
if ($nativeErrorLevel != error_reporting()) {
/**
* Bad packages broke the error levels, alert that and restore level
*/
error_log('Autoloader classes broke error_reporting(): ' . $nativeErrorLevel . ' != ' . error_reporting());
error_reporting($nativeErrorLevel);
}
}
return false;
}
});
Runtime classmap generator
To get the last bits of performance (±1ms) having a classmap for your own classes w/o inventing a classmap generator for your own (legacy) code, just autogenerate map on-fly
- generates classmap on-demand
- plug-in into hybrid autoloader
$classMapFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'someprefix_' . 'runtime_classmap.php';
$classMap = [];
if (is_readable($classMapFile)) {
$classMap = include($classMapFile);
if (!is_array($classMap)) {
$classMap = [];
}
}
$classMapSize = count($classMap);
register_shutdown_function(function () use (&$classMap, $classMapFile, $classMapSize) {
if (count($classMap) > $classMapSize) {
clearstatcache();
if ($newMapFile = tmpfile()) {
$metaDatas = stream_get_meta_data($newMapFile);
if ($metaDatas['uri']) {
$newMap = '<?php return ' . var_export($classMap, true) . ';';
if (strlen($newMap) == fwrite($newMapFile, $newMap) && fflush($newMapFile)) {
@copy($metaDatas['uri'], $classMapFile);
if (md5_file($metaDatas['uri']) == md5_file($classMapFile)) {
if (function_exists('opcache_invalidate') && function_exists('opcache_compile_file')) {
opcache_invalidate($classMapFile, true);
opcache_compile_file($classMapFile);
}
chmod($classMapFile, 0644);
}
}
}
fclose($newMapFile);
}
}
});
Update Dec 2016
- Updated register_shutdown_function for high concurrent reads&writes
Update Mar 2017
- Additionnal checks for written files consistency
Update Dec 2020
- Fix for include_once in case file was included by some external loaders already
https://moar.sshilko.com/2016/10/27/Composer-autoload