From 48d3515eb1472789eda9438f3bf7659147fdd3cc Mon Sep 17 00:00:00 2001 From: Brooke Bryan Date: Fri, 7 Feb 2020 10:11:50 +0000 Subject: [PATCH] Performance Improvements (#23) * Cache resource manager base uris, and build resource uri manually * Improve external url check * Cache optimisation * Cache file hashes * Cache file hashes * Static file cache * Hash cache * Clear hash cache when changing salt --- src/Dispatch.php | 15 ++++++--- src/ResourceManager.php | 63 ++++++++++++++++++++++++----------- tests/ResourceManagerTest.php | 6 ++++ 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/Dispatch.php b/src/Dispatch.php index 70d382a..fb5b4c1 100644 --- a/src/Dispatch.php +++ b/src/Dispatch.php @@ -102,9 +102,12 @@ public static function destroy() public function setHashSalt(string $hashSalt) { $this->_hashSalt = $hashSalt; + static::$_hashCache = []; return $this; } + protected static $_hashCache = []; + /** * Generate a hash against specific content, for a desired length * @@ -115,12 +118,16 @@ public function setHashSalt(string $hashSalt) */ public function generateHash($content, int $length = null) { - $hash = md5($content . $this->_hashSalt); + if(!isset(static::$_hashCache[$content])) + { + static::$_hashCache[$content] = md5($content . $this->_hashSalt); + } + if($length !== null) { - return substr($hash, 0, $length); + return substr(static::$_hashCache[$content], 0, $length); } - return $hash; + return static::$_hashCache[$content]; } public function getResourcesPath() @@ -303,7 +310,7 @@ protected function _getClassLoader() { if($this->_classLoader === null) { - foreach(spl_autoload_functions() as list($loader)) + foreach(spl_autoload_functions() as [$loader]) { if($loader instanceof ClassLoader) { diff --git a/src/ResourceManager.php b/src/ResourceManager.php index 185f8a1..ab43fef 100644 --- a/src/ResourceManager.php +++ b/src/ResourceManager.php @@ -11,7 +11,6 @@ use RuntimeException; use function apcu_fetch; use function apcu_store; -use function array_merge; use function array_unshift; use function base_convert; use function count; @@ -43,7 +42,7 @@ class ResourceManager protected $_type = self::MAP_RESOURCES; protected $_mapOptions = []; - protected $_baseUri = []; + protected $_baseUri; protected $_componentPath; protected $_options = []; @@ -69,7 +68,17 @@ public function __construct($type, array $mapOptions = [], array $options = []) $this->setOption($option, $optionValue); } $this->_options = $options; - $this->_baseUri = array_merge([$type], $mapOptions); + } + + public function getBaseUri() + { + if($this->_baseUri === null) + { + $this->_baseUri = (Dispatch::instance() ? Dispatch::instance()->getBaseUri() : '/') + . '/' . $this->_type . '/' . implode('/', $this->_mapOptions); + $this->_baseUri = trim($this->_baseUri, '/'); + } + return $this->_baseUri; } /** @@ -297,20 +306,22 @@ public function getResourceUri($relativeFullPath, bool $allowComponentBubble = t { return null; } - return Path::custom( - '/', - array_merge( - [Dispatch::instance()->getBaseUri()], - $this->_baseUri, - [$hash . $relHash . ($bits > 0 ? '-' . base_convert($bits, 10, 36) : ''), $relativeFullPath] - ) - ); + + return $this->getBaseUri() + . '/' . $hash . $relHash . ($bits > 0 ? '-' . base_convert($bits, 10, 36) : '') + . '/' . $relativeFullPath; } + protected $_optimizeWebP; + protected function _optimisePath($path, $relativeFullPath) { - $optimise = ValueAs::bool(Dispatch::instance()->config()->getItem('optimisation', 'webp', false)); - if($optimise + if($this->_optimizeWebP === null) + { + $this->_optimizeWebP = ValueAs::bool(Dispatch::instance()->config()->getItem('optimisation', 'webp', false)); + } + + if($this->_optimizeWebP && in_array(substr($path, -4), ['.jpg', 'jpeg', '.png', '.gif', '.bmp', 'tiff', '.svg']) && file_exists($path . '.webp')) { @@ -328,11 +339,11 @@ protected function _optimisePath($path, $relativeFullPath) */ public function isExternalUrl($path) { - return strlen($path) > 8 && ( - Strings::startsWith($path, 'http://', true, 7) || - Strings::startsWith($path, 'https://', true, 8) || - Strings::startsWith($path, '//', true, 2) - ); + return isset($path[8]) + && ( + ($path[0] == '/' && $path[1] == '/') + || strncasecmp($path, 'http://', 7) == 0 + || strncasecmp($path, 'https://', 8) == 0); } /** @@ -377,16 +388,27 @@ public function getRelativeHash($filePath) return Dispatch::instance()->generateHash(Dispatch::instance()->calculateRelativePath($filePath), 4); } + protected static $_fileHashCache = []; + public function getFileHash($fullPath) { - if(!file_exists($fullPath)) + $cached = static::$_fileHashCache[$fullPath] ?? null; + + if($cached === -1 || ($cached === null && !file_exists($fullPath))) { + self::$_fileHashCache[$fullPath] = -1; if($this->getOption(self::OPT_THROW_ON_FILE_NOT_FOUND, true)) { throw new RuntimeException("Unable to find dispatch file '$fullPath'", 404); } return null; } + + if(!empty($cached)) + { + return $cached; + } + $key = 'pdspfh-' . md5($fullPath) . '-' . filectime($fullPath); if(function_exists("apcu_fetch")) @@ -396,12 +418,13 @@ public function getFileHash($fullPath) if($exists && $hash) { // @codeCoverageIgnoreStart + self::$_fileHashCache[$fullPath] = $hash; return $hash; // @codeCoverageIgnoreEnd } } - $hash = Dispatch::instance()->generateHash(md5_file($fullPath), 8); + self::$_fileHashCache[$fullPath] = $hash = Dispatch::instance()->generateHash(md5_file($fullPath), 8); if($hash && function_exists('apcu_store')) { apcu_store($key, $hash, 86400); diff --git a/tests/ResourceManagerTest.php b/tests/ResourceManagerTest.php index 5feb215..800773e 100644 --- a/tests/ResourceManagerTest.php +++ b/tests/ResourceManagerTest.php @@ -118,7 +118,13 @@ public function testIsExternalUrl() { $manager = ResourceManager::external(); $this->assertTrue($manager->isExternalUrl('http://www.google.com')); + $this->assertTrue($manager->isExternalUrl('hTTp://www.google.com')); $this->assertFalse($manager->isExternalUrl('abhttp://www.google.com')); + $this->assertTrue($manager->isExternalUrl('https://www.google.com')); + $this->assertTrue($manager->isExternalUrl('httPS://www.google.com')); + $this->assertFalse($manager->isExternalUrl('abhttps://www.google.com')); + $this->assertTrue($manager->isExternalUrl('//www.google.com')); + $this->assertFalse($manager->isExternalUrl('://www.google.com')); //Check external still work on other resource types $manager = ResourceManager::public();