Skip to content

Instantly share code, notes, and snippets.

@sergant210
Last active July 5, 2021 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sergant210/a6fea34ecb94b8642918c879e992717a to your computer and use it in GitHub Desktop.
Save sergant210/a6fea34ecb94b8642918c879e992717a to your computer and use it in GitHub Desktop.
Optimized cache manager
<?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;
}
}
@arslanovdev
Copy link

Важно назвать файл optcachemanager.class.php

@sergant210
Copy link
Author

Для автоматической загрузки MODX обязательно. Поправил. Спасибо!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment