1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-12 09:44:38 +02:00

Update Modules class to use its own internal caches rather than using those from $cache API var. Now it only uses $cache as a backup. This means you can now safely omit the 'caches' table from DB exports, or you delete the caches table, and it'll get re-created etc. After upgrading to this version, you'll have to do a 'Modules > Refresh' before it'll start using its own caches.

This commit is contained in:
Ryan Cramer
2023-05-26 13:29:02 -04:00
parent 797d6c9b37
commit 0747c54ddf

View File

@@ -10,7 +10,7 @@
* in order to save resources. As a result, anything iterating through these Modules should check to make sure it's not a ModulePlaceholder
* before using it. If it's a ModulePlaceholder, then the real Module can be instantiated/retrieved by $modules->get($className).
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Loads and manages all modules in ProcessWire.
@@ -90,6 +90,14 @@ class Modules extends WireArray {
*
*/
const flagsNoFile = 64;
/**
* Indicates row is for Modules system cache use and not an actual module
*
* @since 3.0.218
*
*/
const flagsSystemCache = 8192;
/**
* Filename for module info cache file
@@ -274,16 +282,13 @@ class Modules extends WireArray {
protected $refreshing = false;
/**
* Use 'info' and 'info_verbose' cols in modules table rather than system cache?
* Runtime caches
*
* Experimental, not currently used except for development/testing
* Set to true to enable, false to specifically disable, null to detect when enabled.
*
* @var null|bool
* @var array
* @since 3.0.218
*
*/
protected $useModuleInfoCols = false;
protected $caches = array();
/**
* Properties that only appear in 'verbose' moduleInfo
@@ -395,8 +400,8 @@ class Modules extends WireArray {
*/
public function init() {
$this->setTrackChanges(false);
$this->loadModuleInfoCache();
$this->loadModulesTable();
$this->loadModuleInfoCache();
if(!empty($this->autoloadOrders)) $this->preloadModules();
foreach($this->paths as $path) {
$this->load($path);
@@ -417,8 +422,10 @@ class Modules extends WireArray {
protected function moduleInfoCache($moduleID = null, $property = '', $verbose = false) {
if($verbose) {
$infos = &$this->moduleInfoCacheVerbose;
if(empty($infos)) $this->loadModuleInfoCacheVerbose();
} else {
$infos = &$this->moduleInfoCache;
if(empty($infos)) $this->loadModuleInfoCache();
}
if($moduleID === null) {
// get all
@@ -909,12 +916,23 @@ class Modules extends WireArray {
*
*/
protected function loadModulesTable() {
$this->autoloadOrders = array();
$database = $this->wire()->database;
// we use SELECT * so that this select won't be broken by future DB schema additions
// Currently: id, class, flags, data, with created added at sysupdate 7
$query = $database->prepare("SELECT * FROM modules ORDER BY class", "modules.loadModulesTable()"); // QA
$query->execute();
// skip loading dymanic caches at this stage
$skipCaches = array(
self::moduleInfoCacheUninstalledName,
self::moduleInfoCacheVerboseName
);
$query = $database->query(
// Currently: id, class, flags, data, with created added at sysupdate 7
"SELECT * FROM modules " .
"WHERE class NOT IN('" . implode("','", $skipCaches) . "') " .
"ORDER BY class",
"modules.loadModulesTable()"
);
/** @noinspection PhpAssignmentInConditionInspection */
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
@@ -922,18 +940,19 @@ class Modules extends WireArray {
$moduleID = (int) $row['id'];
$flags = (int) $row['flags'];
$class = $row['class'];
if($flags & self::flagsSystemCache) {
// system cache names are prefixed with a '.' so they load first
$this->caches[ltrim($class, '.')] = $row['data'];
continue;
}
$this->moduleIDs[$class] = $moduleID;
$this->moduleFlags[$moduleID] = $flags;
$autoload = $flags & self::flagsAutoload;
$loadSettings = $autoload || ($flags & self::flagsDuplicate) || ($class == 'SystemUpdater');
if(isset($row['info']) && ($this->useModuleInfoCols === true || $this->useModuleInfoCols === null)) {
$this->useModuleInfoCols = true;
// initially populate as JSON string, converted to array on demand by moduleInfoCache() method
if(empty($this->moduleInfoCache[$moduleID])) $this->moduleInfoCache[$moduleID] = $row['info'];
if(empty($this->moduleInfoCacheVerbose[$moduleID])) $this->moduleInfoCacheVerbose[$moduleID] = $row['info_verbose'];
}
if($loadSettings) {
// preload config data for autoload modules since we'll need it again very soon
$data = strlen((string) $row['data']) ? wireDecodeJSON($row['data']) : array();
@@ -967,8 +986,6 @@ class Modules extends WireArray {
}
$query->closeCursor();
if($this->useModuleInfoCols === null) $this->useModuleInfoCols = false;
}
/**
@@ -1096,7 +1113,7 @@ class Modules extends WireArray {
// check if module has already been loaded, or maybe we've got duplicates
if(wireClassExists($basename, false)) {
$module = parent::get($basename);
$dir = rtrim((string) $this->wire()->config->paths->$basename, '/');
$dir = rtrim((string) $this->wire()->config->paths($basename), '/');
if($module && $dir && $dirname != $dir) {
$duplicates->recordDuplicate($basename, $pathname, "$dir/$filename", $installed);
return '';
@@ -1221,22 +1238,14 @@ class Modules extends WireArray {
$callNum++;
$config = $this->wire()->config;
$cache = $this->wire()->cache;
$cacheName = '';
if($level == 0) {
$startPath = $path;
$cacheName = "Modules." . str_replace($config->paths->root, '', $path);
if($readCache && $cache) {
$cacheContents = $cache->get($cacheName);
if($cacheContents !== null) {
if(empty($cacheContents) && $callNum === 1) {
// don't accept empty cache for first path (/wire/modules/)
} else {
$cacheContents = explode("\n", trim($cacheContents));
return $cacheContents;
}
}
if($readCache) {
$cacheContents = $this->getCache($cacheName);
if($cacheContents) return explode("\n", trim($cacheContents));
}
}
@@ -1297,8 +1306,8 @@ class Modules extends WireArray {
$files = array_merge(array_keys($prependFiles), $files);
$prependFiles = array();
}
if($cache && $cacheName) {
$cache->save($cacheName, implode("\n", $files), WireCache::expireReserved);
if($cacheName) {
$this->saveCache($cacheName, implode("\n", $files));
}
}
@@ -4957,19 +4966,26 @@ class Modules extends WireArray {
*
*/
protected function loadModuleInfoCache() {
$cache = $this->wire()->cache;
$data = $cache->get(self::moduleLastVersionsCacheName);
if(is_array($data)) $this->modulesLastVersions = $data;
if($this->useModuleInfoCols === false) {
$data = $cache->get(self::moduleInfoCacheName);
if($data) {
// if module class name keys in use (i.e. ProcessModule) it's an older version of
// module info cache, so we skip over it to force its re-creation
if(is_array($data) && !isset($data['ProcessModule'])) $this->moduleInfoCache = $data;
if(empty($this->modulesLastVersions)) {
$name = self::moduleLastVersionsCacheName;
$data = $this->getCache($name);
if(is_array($data)) $this->modulesLastVersions = $data;
}
if(empty($this->moduleInfoCache)) {
$name = self::moduleInfoCacheName;
$data = $this->getCache($name);
// if module class name keys in use (i.e. ProcessModule) it's an older version of
// module info cache, so we skip over it to force its re-creation
if(is_array($data) && !isset($data['ProcessModule'])) {
$this->moduleInfoCache = $data;
return true;
}
return false;
}
return false;
return true;
}
/**
@@ -4980,9 +4996,11 @@ class Modules extends WireArray {
*
*/
protected function loadModuleInfoCacheVerbose($uninstalled = false) {
$name = $uninstalled ? self::moduleInfoCacheUninstalledName : self::moduleInfoCacheVerboseName;
if($this->useModuleInfoCols === true && !$uninstalled) return true;
$data = $this->wire()->cache->get($name);
$data = $this->getCache($name);
if($data) {
if(is_array($data)) {
if($uninstalled) {
@@ -4993,6 +5011,7 @@ class Modules extends WireArray {
}
return true;
}
return false;
}
@@ -5004,7 +5023,7 @@ class Modules extends WireArray {
*/
protected function clearModuleInfoCache($showMessages = false) {
$cache = $this->wire()->cache;
// $cache = $this->wire()->cache;
$versionChanges = array();
$newModules = array();
$moveModules = array();
@@ -5021,9 +5040,9 @@ class Modules extends WireArray {
}
// delete the caches
$cache->delete(self::moduleInfoCacheName);
$cache->delete(self::moduleInfoCacheVerboseName);
$cache->delete(self::moduleInfoCacheUninstalledName);
$this->deleteCache(self::moduleInfoCacheName);
$this->deleteCache(self::moduleInfoCacheVerboseName);
$this->deleteCache(self::moduleInfoCacheUninstalledName);
$this->moduleInfoCache = array();
$this->moduleInfoCacheVerbose = array();
@@ -5126,9 +5145,9 @@ class Modules extends WireArray {
if(!in_array($id, $this->moduleIDs)) unset($this->modulesLastVersions[$id]);
}
if(count($this->modulesLastVersions)) {
$this->wire()->cache->save(self::moduleLastVersionsCacheName, $this->modulesLastVersions, WireCache::expireReserved);
$this->saveCache(self::moduleLastVersionsCacheName, $this->modulesLastVersions);
} else {
$this->wire()->cache->delete(self::moduleLastVersionsCacheName);
$this->deleteCache(self::moduleLastVersionsCacheName);
}
}
@@ -5340,16 +5359,10 @@ class Modules extends WireArray {
self::moduleInfoCacheUninstalledName => 'moduleInfoCacheUninstalled',
);
$cols = array(
'moduleInfoCache' => 'info',
'moduleInfoCacheVerbose' => 'info_verbose'
);
$defaultNS = array("\\" . __NAMESPACE__ . "\\", __NAMESPACE__ . "\\");
foreach($caches as $cacheName => $varName) {
$data = $this->$varName;
$col = isset($cols[$varName]) ? $cols[$varName] : ''; // info, info_verbose
foreach($data as $moduleID => $moduleInfo) {
foreach($moduleInfo as $key => $value) {
// remove unpopulated properties
@@ -5377,54 +5390,15 @@ class Modules extends WireArray {
unset($data[$moduleID][$key]);
}
}
if($col) $this->saveModuleInfoCacheCol($moduleID, $data[$moduleID], $col);
}
$this->wire()->cache->save($cacheName, $data, WireCache::expireReserved);
$this->saveCache($cacheName, $data);
}
$this->log('Saved module info caches');
// $this->log('Saved module info caches');
if($languages && $language) $user->language = $language; // restore
}
/**
* Save module info cache in a column of the modules table
*
* Experimental, used only if $this->useModuleInfoCols is true
*
* @param int $moduleID
* @param array $info
* @param string $col 'info' or 'info_verbose'
* @since 3.0.218
*
*/
protected function saveModuleInfoCacheCol($moduleID, array $info, $col) {
$database = $this->wire()->database;
$col = $database->escapeCol($col);
static $action = '';
if($this->useModuleInfoCols) {
if($action !== 'added' && !$database->columnExists('modules', $col)) {
$database->exec("ALTER TABLE modules ADD $col TEXT");
$this->message("Added column modules.$col");
$action = 'added';
}
$sql = "UPDATE modules SET $col=:info WHERE id=:id";
$query = $database->prepare($sql);
$query->bindValue(':info', json_encode($info));
$query->bindValue(':id', $moduleID, \PDO::PARAM_INT);
$query->execute();
} else if($this->useModuleInfoCols === false) {
if($action !== 'dropped' && $database->columnExists('modules', $col)) {
$database->exec("ALTER TABLE modules DROP $col");
$this->message("Dropped column modules.$col");
$action = 'dropped';
}
}
}
/**
* Start a debug timer, only works when module debug mode is on ($this->debug)
*
@@ -5695,5 +5669,86 @@ class Modules extends WireArray {
return $file;
}
/**
* Save cache
*
* @param string $cacheName
* @param string|array $data
* @return bool
* @since 3.0.218
*
*/
protected function saveCache($cacheName, $data) {
$database = $this->wire()->database;
if(!$this->saveCacheReady) {
$this->saveCacheReady = true;
$col = $database->getColumns('modules', 'data');
if(strtolower($col['type']) === 'text') {
try {
// increase size of data column for cache storage in 3.0.218
$database->exec("ALTER TABLE modules MODIFY `data` MEDIUMTEXT NOT NULL");
$this->message("Updated modules.data to mediumtext", Notice::debug);
} catch(\Exception $e) {
$this->error($e->getMessage());
}
}
}
$cache = $this->wire()->cache;
if($cache) $cache->save($cacheName, $data, WireCache::expireReserved);
if(is_array($data)) $data = json_encode($data);
$sql = "INSERT INTO modules SET class=:name, data=:data, flags=:flags ON DUPLICATE KEY UPDATE data=VALUES(data)";
$query = $database->prepare($sql);
$query->bindValue(':name', ".$cacheName");
$query->bindValue(':data', $data);
$query->bindValue(':flags', self::flagsSystemCache);
return $query->execute();
}
protected $saveCacheReady = false;
/**
* Get cache
*
* @param string $cacheName
* @return string|array|bool
* @since 3.0.218
*
*/
protected function getCache($cacheName) {
$data = null;
if(isset($this->caches[$cacheName])) {
$data = $this->caches[$cacheName];
unset($this->caches[$cacheName]);
}
if(empty($data)) {
$sql = "SELECT data FROM modules WHERE class=:name";
$query = $this->wire()->database->prepare($sql);
$query->bindValue(':name', ".$cacheName");
$query->execute();
$data = $query->fetchColumn();
$query->closeCursor();
}
if(empty($data)) {
// fallback to $cache API var, necessary only temporarily
$data = $this->wire()->cache->get($cacheName);
if($data) return $data;
}
if(is_string($data) && (strpos($data, '{') === 0 || strpos($data, '[') === 0)) {
$data = json_decode($data, true);
}
return $data;
}
/**
* Delete cache by name
*
* @param string $cacheName
* @since 3.0.218
*
*/
protected function deleteCache($cacheName) {
$this->wire()->cache->delete($cacheName);
unset($this->caches[$cacheName]);
}
}