From 8046f1989bb077b4443b5ed651b1effc37decccf Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Tue, 30 May 2023 12:02:16 -0400 Subject: [PATCH] Additional performance optimizations to Modules class, plus fix issue with some moduleInfo properties falling back to default value when they weren't supposed to --- wire/core/Modules.php | 107 +++++++++++++++++++++----------- wire/core/ModulesDuplicates.php | 60 ++++++++++-------- 2 files changed, 104 insertions(+), 63 deletions(-) diff --git a/wire/core/Modules.php b/wire/core/Modules.php index 28ab7657..15f31da1 100644 --- a/wire/core/Modules.php +++ b/wire/core/Modules.php @@ -465,12 +465,12 @@ class Modules extends WireArray { /** * Get data from the verbose module info cache * - * @param int|string $moduleID + * @param int|string|null $moduleID * @param string $property * @return array|mixed|null * */ - protected function moduleInfoCacheVerbose($moduleID = 0, $property = '') { + protected function moduleInfoCacheVerbose($moduleID = null, $property = '') { return $this->moduleInfoCache($moduleID, $property, true); } @@ -672,7 +672,10 @@ class Modules extends WireArray { $className = wireClassName($className, true); } $debugKey = $this->debug ? $this->debugTimerStart("newModule($moduleName)") : null; - if(!class_exists($className, false)) $this->includeModule($moduleName); + if(!class_exists($className, false)) { + $result = $this->includeModule($moduleName); + if(!$result) return null; + } if(!class_exists($className, false)) { // attempt 2.x module in dedicated namespace or root namespace $className = $this->getModuleNamespace($moduleName) . $moduleName; @@ -1122,16 +1125,19 @@ class Modules extends WireArray { } // if the filename doesn't end with .module or .module.php, then stop and move onto the next - if(!strpos($filename, '.module') || (substr($filename, -7) !== '.module' && substr($filename, -11) !== '.module.php')) return false; + if(strpos($filename, '.module') === false) return false; + list(, $ext) = explode('.module', $filename, 2); + if(!empty($ext) && $ext !== '.php') return false; // if the filename doesn't start with the requested path, then continue if(strpos($pathname, $basepath) !== 0) return ''; // if the file isn't there, it was probably uninstalled, so ignore it - if(!file_exists($pathname)) return ''; + // if(!file_exists($pathname)) return ''; // if the module isn't installed, then stop and move on to next - if(!array_key_exists($basename, $installed)) { + if(!isset($installed[$basename]) && !array_key_exists($basename, $installed)) { + // array_key_exists is used as secondary to check the null case $this->installable[$basename] = $pathname; return ''; } @@ -1146,11 +1152,11 @@ class Modules extends WireArray { // this is an Autoload module. // include the module and instantiate it but don't init() it, // because it will be done by Modules::init() - $moduleInfo = $this->getModuleInfo($basename); // determine if module has dependencies that are not yet met - if(count($moduleInfo['requires'])) { - foreach($moduleInfo['requires'] as $requiresClass) { + $requiresClasses = $this->getModuleInfoProperty($basename, 'requires'); + if(!empty($requiresClasses)) { + foreach($requiresClasses as $requiresClass) { $nsRequiresClass = $this->getModuleClass($requiresClass, true); if(!wireClassExists($nsRequiresClass, false)) { $requiresInfo = $this->getModuleInfo($requiresClass); @@ -1167,22 +1173,22 @@ class Modules extends WireArray { return $basename; } } - // if not defined in getModuleInfo, then we'll accept the database flag as enough proof // since the module may have defined it via an isAutoload() function - if(!isset($moduleInfo['autoload'])) $moduleInfo['autoload'] = true; /** @var bool|string|callable $autoload */ - $autoload = $moduleInfo['autoload']; + $autoload = $this->moduleInfoCache($basename, 'autoload'); + if(empty($autoload)) $autoload = true; if($autoload === 'function') { // function is stored by the moduleInfo cache to indicate we need to call a dynamic function specified with the module itself $i = $this->getModuleInfoExternal($basename); if(empty($i)) { $this->includeModuleFile($pathname, $basename); - $className = $moduleInfo['namespace'] . $basename; + $namespace = $this->getModuleNamespace($basename); + $className = $namespace . $basename; if(method_exists($className, 'getModuleInfo')) { $i = $className::getModuleInfo(); } else { - $i = array(); + $i = $this->getModuleInfo($className); } } $autoload = isset($i['autoload']) ? $i['autoload'] : true; @@ -1207,10 +1213,13 @@ class Modules extends WireArray { } } - if(is_null($module)) { + if($module === null) { // placeholder for a module, which is not yet included and instantiated - if(!$moduleInfo) $moduleInfo = $this->getModuleInfo($basename); - $module = $this->newModulePlaceholder($basename, $moduleInfo['namespace'], $pathname, $info['flags'] & self::flagsSingular, $autoload); + // if(!$moduleInfo) $moduleInfo = $this->getModuleInfo($basename); + $ns = $moduleInfo ? $moduleInfo['namespace'] : $this->moduleInfoCache($basename, 'namespace'); + if(empty($namespace)) $ns = __NAMESPACE__ . "\\"; + $singular = $info['flags'] & self::flagsSingular; + $module = $this->newModulePlaceholder($basename, $ns, $pathname, $singular, $autoload); } $this->moduleIDs[$basename] = $info['id']; @@ -1701,14 +1710,17 @@ class Modules extends WireArray { // still can't figure out what file is? fail if(!$file) return false; } - + if(!$this->includeModuleFile($file, $moduleName)) { // module file failed to include(), try to identify and include file again if($fast) { $filePrev = $file; $file = $this->getModuleFile($moduleName, array('fast' => false)); if($file && $file !== $filePrev) { - $this->includeModuleFile($file, $moduleName); + if($this->includeModuleFile($file, $moduleName)) { + // module is missing a module file + return false; + } } } else { // we already tried this earlier, no point in doing it again @@ -2935,7 +2947,7 @@ class Modules extends WireArray { * - `namespace` (string): PHP namespace that module lives in. * * The following properties are also included when "verbose" mode is requested. When not in verbose mode, these - * properties are present but blank: + * properties may be present but with empty values: * * - `versionStr` (string): formatted module version string. * - `file` (string): module filename from PW installation root, or false when it can't be found. @@ -3001,7 +3013,7 @@ class Modules extends WireArray { $moduleName = ''; $moduleID = 0; $fromCache = false; // was the data loaded from cache? - + if(!$getAll && !$getSystem) { $moduleName = $this->getModuleClass($module); $moduleID = (string) $this->getModuleID($module); // typecast to string for cache @@ -3068,9 +3080,16 @@ class Modules extends WireArray { if(empty($this->moduleInfoCache)) $this->loadModuleInfoCache(); $modulesInfo = $this->moduleInfoCache(); if($options['verbose']) { - if(empty($this->moduleInfoCacheVerbose)) $this->loadModuleInfoCacheVerbose(); foreach($this->moduleInfoCacheVerbose() as $moduleID => $moduleInfoVerbose) { - $modulesInfo[$moduleID] = array_merge($modulesInfo[$moduleID], $moduleInfoVerbose); + if($options['noCache']) { + $modulesInfo[$moduleID] = $this->getModuleInfo($moduleID, $options); + } else { + $modulesInfo[$moduleID] = array_merge($modulesInfo[$moduleID], $moduleInfoVerbose); + } + } + } else if($options['noCache']) { + foreach(array_keys($modulesInfo) as $moduleID) { + $modulesInfo[$moduleID] = $this->getModuleInfo($moduleID, $options); } } if(!$options['minify']) { @@ -3159,12 +3178,12 @@ class Modules extends WireArray { } // populate defaults for properties omitted from cache - if(is_null($info['autoload'])) $info['autoload'] = false; - if(is_null($info['singular'])) $info['singular'] = false; - if(is_null($info['configurable'])) $info['configurable'] = false; - if(is_null($info['core'])) $info['core'] = false; - if(is_null($info['installed'])) $info['installed'] = true; - if(is_null($info['namespace'])) $info['namespace'] = strlen(__NAMESPACE__) ? "\\" . __NAMESPACE__ . "\\" : ""; + if($info['autoload'] === null) $info['autoload'] = false; + if($info['singular'] === null) $info['singular'] = false; + if($info['configurable'] === null) $info['configurable'] = false; + if($info['core'] === null) $info['core'] = false; + if($info['installed'] === null) $info['installed'] = true; + if($info['namespace'] === null) $info['namespace'] = strlen(__NAMESPACE__) ? "\\" . __NAMESPACE__ . "\\" : ""; if(!empty($info['requiresVersions'])) $info['requires'] = array_keys($info['requiresVersions']); if($moduleName == 'SystemUpdater') $info['configurable'] = 1; // fallback, just in case @@ -3272,11 +3291,13 @@ class Modules extends WireArray { if($options['minify']) { // when minify, any values that match defaults from infoTemplate are removed - if(!$options['verbose']) foreach($this->moduleInfoVerboseKeys as $key) unset($info[$key]); - foreach($info as $key => $value) { - if(!array_key_exists($key, $infoTemplate)) continue; - if($value !== $infoTemplate[$key]) continue; - unset($info[$key]); + if(!$options['verbose']) { + foreach($this->moduleInfoVerboseKeys as $key) unset($info[$key]); + foreach($info as $key => $value) { + if(!array_key_exists($key, $infoTemplate)) continue; + if($value !== $infoTemplate[$key]) continue; + unset($info[$key]); + } } } @@ -3324,6 +3345,19 @@ class Modules extends WireArray { * */ public function getModuleInfoProperty($class, $property, array $options = array()) { + + if(empty($options['noCache'])) { + // shortcuts where possible + switch($property) { + case 'namespace': + return $this->getModuleNamespace($class); + case 'requires': + $v = $this->moduleInfoCache($class, 'requires'); + if(empty($v)) return array(); // early exist when known not to exist + break; // fallback to calling getModuleInfo + } + } + if(in_array($property, $this->moduleInfoVerboseKeys)) { $info = $this->getModuleInfoVerbose($class, $options); $info['verbose'] = true; @@ -3334,6 +3368,7 @@ class Modules extends WireArray { // try again, just in case we can find it in verbose data $info = $this->getModuleInfoVerbose($class, $options); } + return isset($info[$property]) ? $info[$property] : null; } @@ -3658,7 +3693,7 @@ class Modules extends WireArray { if(!$hasDuplicate) { // see if we can determine it from already stored paths - $path = $config->paths->$moduleName; + $path = $config->paths($moduleName); if($path) { $file = $path . $moduleName . ($this->moduleFileExts[$moduleName] === 2 ? '.module.php' : '.module'); if(!$options['fast'] && !file_exists($file)) $file = false; @@ -3748,7 +3783,7 @@ class Modules extends WireArray { if(!$file && !empty($options['guess'])) { // make a guess about where module would be if we had been able to find it - $file = $config->paths->siteModules . "$moduleName/$moduleName.module"; + $file = $config->paths('siteModules') . "$moduleName/$moduleName.module"; } if($file) { diff --git a/wire/core/ModulesDuplicates.php b/wire/core/ModulesDuplicates.php index f01908d9..215d8f44 100644 --- a/wire/core/ModulesDuplicates.php +++ b/wire/core/ModulesDuplicates.php @@ -6,7 +6,7 @@ * Provides functions for managing sitautions where more than one * copy of the same module is intalled. This is a helper for the Modules class. * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2023 by Ryan Cramer * https://processwire.com * */ @@ -73,7 +73,7 @@ class ModulesDuplicates extends Wire { public function hasDuplicate($className, $pathname = '') { if(!isset($this->duplicates[$className])) return false; if($pathname) { - $rootPath = $this->wire('config')->paths->root; + $rootPath = $this->wire()->config->paths->root; if(strpos($pathname, $rootPath) === 0) $pathname = str_replace($rootPath, '/', $pathname); return in_array($pathname, $this->duplicates[$className]); } @@ -90,7 +90,7 @@ class ModulesDuplicates extends Wire { */ public function addDuplicate($className, $pathname, $current = false) { if(!isset($this->duplicates[$className])) $this->duplicates[$className] = array(); - $rootPath = $this->wire('config')->paths->root; + $rootPath = $this->wire()->config->paths->root; if(strpos($pathname, $rootPath) === 0) $pathname = str_replace($rootPath, '/', $pathname); if(!in_array($pathname, $this->duplicates[$className])) { $this->duplicates[$className][] = $pathname; @@ -153,11 +153,12 @@ class ModulesDuplicates extends Wire { public function getDuplicates($className = '') { if(!$className) return $this->duplicates; - - $className = $this->wire('modules')->getModuleClass($className); + + $modules = $this->wire()->modules; + $className = $modules->getModuleClass($className); $files = isset($this->duplicates[$className]) ? $this->duplicates[$className] : array(); $using = isset($this->duplicatesUse[$className]) ? $this->duplicatesUse[$className] : ''; - $rootPath = $this->wire('config')->paths->root; + $rootPath = $this->wire()->config->paths->root; foreach($files as $key => $file) { $file = rtrim($rootPath, '/') . $file; @@ -167,7 +168,7 @@ class ModulesDuplicates extends Wire { } if(count($files) > 1 && !$using) { - $using = $this->wire('modules')->getModuleFile($className); + $using = $modules->getModuleFile($className); $using = str_replace($rootPath, '/', $using); } @@ -191,8 +192,9 @@ class ModulesDuplicates extends Wire { * */ public function setUseDuplicate($className, $pathname) { - $className = $this->wire('modules')->getModuleClass($className); - $rootPath = $this->wire('config')->paths->root; + $modules = $this->wire()->modules; + $className = $modules->getModuleClass($className); + $rootPath = $this->wire()->config->paths->root; if(!isset($this->duplicates[$className])) { throw new WireException("Module $className does not have duplicates"); } @@ -204,9 +206,9 @@ class ModulesDuplicates extends Wire { throw new WireException("Duplicate module file does not exist: $pathname"); } $this->duplicatesUse[$className] = $pathname; - $configData = $this->wire('modules')->getModuleConfigData($className); + $configData = $modules->getModuleConfigData($className); $configData['-dups-use'] = $pathname; - $this->wire('modules')->saveModuleConfigData($className, $configData); + $modules->saveModuleConfigData($className, $configData); } /** @@ -214,8 +216,9 @@ class ModulesDuplicates extends Wire { * */ public function updateDuplicates() { - - $rootPath = $this->wire('config')->paths->root; + + $modules = $this->wire()->modules; + $rootPath = $this->wire()->config->paths->root; // store duplicate information in each module's data field foreach($this->getDuplicates() as $moduleName => $files) { @@ -228,7 +231,7 @@ class ModulesDuplicates extends Wire { $files[$key] = $file; } $files = array_unique($files); - $configData = $this->wire('modules')->getModuleConfigData($moduleName); + $configData = $modules->getModuleConfigData($moduleName); if((empty($configData['-dups']) && !empty($files)) || (empty($configData['-dups-use']) || $configData['-dups-use'] != $using) || (isset($configData['-dups']) && implode(' ', $configData['-dups']) != implode(' ', $files)) @@ -237,13 +240,13 @@ class ModulesDuplicates extends Wire { $this->duplicatesUse[$moduleName] = $using; $configData['-dups'] = $files; $configData['-dups-use'] = $using; - $this->wire('modules')->saveModuleConfigData($moduleName, $configData); + $modules->saveModuleConfigData($moduleName, $configData); } } // update any modules that no longer have duplicates $removals = array(); - $query = $this->wire('database')->prepare("SELECT `class`, `flags` FROM modules WHERE `flags` & :flag"); + $query = $this->wire()->database->prepare("SELECT `class`, `flags` FROM modules WHERE `flags` & :flag"); $query->bindValue(':flag', Modules::flagsDuplicate, \PDO::PARAM_INT); $query->execute(); @@ -258,10 +261,10 @@ class ModulesDuplicates extends Wire { } foreach($removals as $class => $flags) { - $this->wire('modules')->setFlags($class, $flags); - $configData = $this->wire('modules')->getModuleConfigData($class); + $modules->setFlags($class, $flags); + $configData = $modules->getModuleConfigData($class); unset($configData['-dups'], $configData['-dups-use']); - $this->wire('modules')->saveModuleConfigData($class, $configData); + $modules->saveModuleConfigData($class, $configData); } } @@ -275,7 +278,9 @@ class ModulesDuplicates extends Wire { * */ public function recordDuplicate($basename, $pathname, $pathname2, &$installed) { - $rootPath = $this->wire('config')->paths->root; + $config = $this->wire()->config; + $modules = $this->wire()->modules; + $rootPath = $config->paths->root; // ensure paths start from root of PW install if(strpos($pathname, $rootPath) === 0) $pathname = str_replace($rootPath, '/', $pathname); if(strpos($pathname2, $rootPath) === 0) $pathname2 = str_replace($rootPath, '/', $pathname2); @@ -295,7 +300,7 @@ class ModulesDuplicates extends Wire { if(isset($installed[$basename]['flags'])) { $flags = $installed[$basename]['flags']; } else { - $flags = $this->wire('modules')->getFlags($basename); + $flags = $modules->getFlags($basename); } if($flags & Modules::flagsDuplicate) { // flags already represent duplicate status @@ -303,14 +308,14 @@ class ModulesDuplicates extends Wire { // make database aware this module has multiple files by adding the duplicate flag $this->numNewDuplicates++; // trigger update needed $flags = $flags | Modules::flagsDuplicate; - $this->wire('modules')->setFlags($basename, $flags); + $modules->setFlags($basename, $flags); } $err = sprintf($this->_('There appear to be multiple copies of module "%s" on the file system.'), $basename) . ' '; - $this->wire('log')->save('modules', $err); - $user = $this->wire('user'); + $this->wire()->log->save('modules', $err); + $user = $this->wire()->user; if($user && $user->isSuperuser()) { $err .= $this->_('Please edit the module settings to tell ProcessWire which one to use:') . ' ' . - "urls->admin . 'module/edit?name=' . $basename . "'>$basename"; + "$basename"; $this->warning($err, Notice::allowMarkup); } //$this->message("recordDuplicate($basename, $pathname) $this->numNewDuplicates"); //DEBUG @@ -327,10 +332,11 @@ class ModulesDuplicates extends Wire { * */ public function getDuplicatesConfigData($className, array $configData = array()) { + $config = $this->wire()->config; // ensure original duplicates info is retained and validate that it is still current if(isset($this->duplicates[$className])) { foreach($this->duplicates[$className] as $key => $file) { - $pathname = rtrim($this->wire('config')->paths->root, '/') . $file; + $pathname = rtrim($config->paths->root, '/') . $file; if(!file_exists($pathname)) { unset($this->duplicates[$className][$key]); } @@ -341,7 +347,7 @@ class ModulesDuplicates extends Wire { } else { $configData['-dups'] = $this->duplicates[$className]; if(isset($this->duplicatesUse[$className])) { - $pathname = rtrim($this->wire('config')->paths->root, '/') . $this->duplicatesUse[$className]; + $pathname = rtrim($config->paths->root, '/') . $this->duplicatesUse[$className]; if(file_exists($pathname)) { $configData['-dups-use'] = $this->duplicatesUse[$className]; } else {