Last active
July 5, 2021 13:56
-
-
Save sergant210/a6fea34ecb94b8642918c879e992717a to your computer and use it in GitHub Desktop.
Optimized cache manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class_exists('xPDOCacheManager') or include MODX_CORE_PATH . 'xpdo/cache/xpdocachemanager.class.php'; | |
class_exists('modCacheManager') or include MODX_CORE_PATH . 'model/modx/modcachemanager.class.php'; | |
class OptCacheManager extends modCacheManager | |
{ | |
/** | |
* Generates a cache entry for a MODX site Context. | |
* | |
* Context cache entries can override site configuration settings and are responsible for | |
* loading the various listings and maps in the modX class, including resourceMap, aliasMap, | |
* and eventMap. It can also be used to setup or transform any other modX properties. | |
* | |
* @param string $key The modContext key to be cached. | |
* @param array $options Options for context settings generation. | |
* @return array An array containing all the context variable values. | |
* @todo Further refactor the generation of aliasMap and resourceMap so it uses less memory/file size. | |
* | |
*/ | |
public function generateContext($key, array $options = []) | |
{ | |
$results = []; | |
if (!$this->getOption('transient_context', $options, false)) { | |
/** @var modContext $context */ | |
$context = $this->modx->getObject('modContext', ['key' => $key]); | |
if (is_object($context)) { | |
$cacheKey = $context->getCacheKey(); | |
$contextKey = is_object($this->modx->context) ? $this->modx->context->get('key') : $key; | |
$contextConfig = array_merge($this->modx->_systemConfig, $options); | |
/* generate the ContextSettings */ | |
$results['config'] = []; | |
if ($settings = $context->getMany('ContextSettings')) { | |
/** @var modContextSetting $setting */ | |
foreach ($settings as $setting) { | |
$k = $setting->get('key'); | |
$v = $setting->get('value'); | |
$matches = []; | |
if (preg_match_all('~{(.*?)}~', $v, $matches, PREG_SET_ORDER)) { | |
foreach ($matches as $match) { | |
if (array_key_exists("{$match[1]}", $contextConfig)) { | |
$matchValue = $contextConfig["{$match[1]}"]; | |
$v = str_replace($match[0], $matchValue, $v); | |
} | |
} | |
} | |
$results['config'][$k] = $v; | |
$contextConfig[$k] = $v; | |
} | |
} | |
$results['config'] = array_merge($results['config'], $options); | |
/* generate the aliasMap and resourceMap */ | |
$collResources = $this->getResourceCacheMapStmt($context); | |
$friendlyUrls = $this->getOption('friendly_urls', $contextConfig, false); | |
$cacheAliasMap = $this->getOption('cache_alias_map', $options, false); | |
$cacheResourceMap = $this->getOption('cache_resource_map', $options, true); | |
if ($friendlyUrls && $cacheAliasMap) { | |
$results['aliasMap'] = []; | |
} | |
if ($collResources) { | |
/** @var Object $r */ | |
while ($r = $collResources->fetch(PDO::FETCH_OBJ)) { | |
if ($cacheResourceMap) { | |
if (!isset($results['resourceMap'][(int)$r->parent])) { | |
$results['resourceMap'][(int)$r->parent] = []; | |
} | |
$results['resourceMap'][(int)$r->parent][] = (int)$r->id; | |
} | |
if ($friendlyUrls && $cacheAliasMap) { | |
if (isset($results['aliasMap'][$r->uri])) { | |
$this->modx->log( | |
xPDO::LOG_LEVEL_ERROR, | |
"Resource URI {$r->uri} already exists for resource id = {$results['aliasMap'][$r->uri]}; skipping duplicate resource URI for resource id = {$r->id}" | |
); | |
continue; | |
} | |
$results['aliasMap'][$r->uri] = (int)$r->id; | |
} | |
} | |
} | |
/* generate the webLinkMap */ | |
$collWebLinks = $context->getWebLinkCacheMap(); | |
$results['webLinkMap'] = []; | |
if ($collWebLinks) { | |
while ($wl = $collWebLinks->fetch(PDO::FETCH_OBJ)) { | |
$results['webLinkMap'][(int)$wl->id] = $wl->content; | |
} | |
} | |
/* generate the eventMap and pluginCache */ | |
$results['eventMap'] = []; | |
$results['pluginCache'] = []; | |
$eventMap = $this->modx->getEventMap($context->get('key')); | |
if (is_array($eventMap) && !empty($eventMap)) { | |
$results['eventMap'] = $eventMap; | |
$pluginIds = []; | |
$plugins = []; | |
$this->modx->loadClass('modScript'); | |
foreach ($eventMap as $pluginKeys) { | |
foreach ($pluginKeys as $pluginKey) { | |
if (isset ($pluginIds[$pluginKey])) { | |
continue; | |
} | |
$pluginIds[$pluginKey] = $pluginKey; | |
} | |
} | |
if (!empty($pluginIds)) { | |
$pluginQuery = $this->modx->newQuery('modPlugin', ['id:IN' => array_keys($pluginIds)]); | |
$pluginQuery->select($this->modx->getSelectColumns('modPlugin', 'modPlugin')); | |
if ($pluginQuery->prepare() && $pluginQuery->stmt->execute()) { | |
$plugins = $pluginQuery->stmt->fetchAll(PDO::FETCH_ASSOC); | |
} | |
} | |
if (!empty($plugins)) { | |
foreach ($plugins as $plugin) { | |
$results['pluginCache'][(string)$plugin['id']] = $plugin; | |
} | |
} | |
} | |
/* cache the Context ACL policies */ | |
$results['policies'] = $context->findPolicy($contextKey); | |
} else { | |
$results = false; | |
} | |
} else { | |
$results = $this->getOption("{$key}_results", $options, []); | |
$cacheKey = "{$key}/context"; | |
$options['cache_context_settings'] = isset($results['cache_context_settings']) ? (boolean)$results : false; | |
} | |
if ($this->getOption('cache_context_settings', $options, true) && is_array($results) && !empty($results)) { | |
$options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_context_settings_key', $options, 'context_settings'); | |
$options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_context_settings_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options)); | |
$options[xPDO::OPT_CACHE_FORMAT] = (integer)$this->getOption('cache_context_settings_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP)); | |
$options[xPDO::OPT_CACHE_ATTEMPTS] = (integer)$this->getOption('cache_context_settings_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10)); | |
$options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer)$this->getOption('cache_context_settings_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000)); | |
$lifetime = (integer)$this->getOption('cache_context_settings_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0)); | |
if (!$this->set($cacheKey, $results, $lifetime, $options)) { | |
$this->modx->log(modX::LOG_LEVEL_WARN, 'Could not cache context settings for ' . $key . '.'); | |
} | |
} | |
return $results; | |
} | |
/** | |
* Очищает кэш указанного ресурса и шлёт запрос для пересоздания кэша. | |
* @param modResource|int $resource | |
* @param bool $regenerateCache | |
* | |
*/ | |
public function clearResourceCache($resource, $regenerateCache = false) | |
{ | |
$resource = is_int($resource) ? $this->modx->getObject('modResource', ['id' => $resource]) : $resource; | |
if (is_object($resource)) { | |
$resource->_contextKey = $resource->context_key; | |
$cache = $this->modx->cacheManager->getCacheProvider($this->modx->getOption('cache_resource_key', null, 'resource')); | |
$key = $resource->getCacheKey(); | |
$cache->delete($key, ['deleteTop' => true]); | |
$cache->delete($key); | |
if ($regenerateCache) { | |
$this->sendCurlRequest($resource); | |
} | |
} | |
} | |
/** | |
* @param modResource $resource | |
*/ | |
public function sendCurlRequest($resource) | |
{ | |
$uri = $resource->uri; | |
$siteUrl = $this->modx->getOption('site_url'); | |
/** @var modRestCurlClient $client */ | |
if ($client = $this->modx->getService('rest.modRestCurlClient')) { | |
$client->request($siteUrl, $uri, 'GET', ['curlopt_nobody' => true]); | |
} | |
} | |
/** | |
* Возвращает ресурсы только первого уровня в целях оптимизации. Таким образом для этих ресурсов | |
* можно использовать ссылки типа [[~1]] и пользоваться методами getChildIds() и getParentIds. | |
* @param $context | |
* @return false|\PDOStatement|null | |
*/ | |
private function getResourceCacheMapStmt($context) | |
{ | |
$stmt = null; | |
if ($context instanceof modContext) { | |
$time = microtime(true); | |
$use_context_resource_table = (bool)$context->getOption('use_context_resource_table', null, false); | |
$tblResource = $context->xpdo->getTableName('modResource'); | |
$tblContextResource = $context->xpdo->getTableName('modContextResource'); | |
$resourceFields = ['id', 'parent', 'uri']; | |
$resourceCols = $context->xpdo->getSelectColumns('modResource', 'r', '', $resourceFields); | |
$contextKey = $context->get('key'); | |
$bindings = []; | |
$sql = "SELECT {$resourceCols} FROM {$tblResource} `r` "; | |
if ($use_context_resource_table) { | |
$bindings = [$contextKey, $contextKey]; | |
$sql .= "FORCE INDEX (`cache_refresh_idx`) "; | |
$sql .= "LEFT JOIN {$tblContextResource} `cr` ON `cr`.`context_key` = ? AND `r`.`id` = `cr`.`resource` "; | |
} else { | |
$sql .= "FORCE INDEX (`parent`) "; | |
} | |
$sql .= "WHERE `r`.`deleted` = 0 and `r`.`parent` = 0 "; | |
if ($use_context_resource_table) { | |
$sql .= "AND (`r`.`context_key` = ? OR `cr`.`context_key` IS NOT NULL) "; | |
$sql .= "GROUP BY `r`.`parent`, `r`.`menuindex`, `r`.`id`, `r`.`uri` "; | |
} else { | |
$bindings = [$contextKey]; | |
$sql .= "AND `r`.`context_key` = ?"; | |
} | |
$criteria = new xPDOCriteria($context->xpdo, $sql, $bindings, false); | |
if ($criteria && $criteria->stmt && $criteria->stmt->execute()) { | |
$stmt =& $criteria->stmt; | |
} | |
// output warning if query is too slow | |
$time = (microtime(true) - $time); | |
if ($time >= 1.0 && $use_context_resource_table == 1) { | |
$context->xpdo->log(modX::LOG_LEVEL_WARN, "[modContext_mysql] Slow query detected. Consider to set 'use_context_resource_table' to false."); | |
} | |
} | |
return $stmt; | |
} | |
} |
Для автоматической загрузки MODX обязательно. Поправил. Спасибо!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Важно назвать файл optcachemanager.class.php