"Fossies" - the Fresh Open Source Software Archive

Member "grav/system/src/Grav/Common/Cache.php" (1 Sep 2020, 18053 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Cache.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 /**
    4  * @package    Grav\Common
    5  *
    6  * @copyright  Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
    7  * @license    MIT License; see LICENSE file for details.
    8  */
    9 
   10 namespace Grav\Common;
   11 
   12 use \Doctrine\Common\Cache as DoctrineCache;
   13 use Grav\Common\Config\Config;
   14 use Grav\Common\Filesystem\Folder;
   15 use Grav\Common\Scheduler\Scheduler;
   16 use Psr\SimpleCache\CacheInterface;
   17 use RocketTheme\Toolbox\Event\Event;
   18 use RocketTheme\Toolbox\Event\EventDispatcher;
   19 
   20 /**
   21  * The GravCache object is used throughout Grav to store and retrieve cached data.
   22  * It uses DoctrineCache library and supports a variety of caching mechanisms. Those include:
   23  *
   24  * APCu
   25  * RedisCache
   26  * MemCache
   27  * MemCacheD
   28  * FileSystem
   29  */
   30 class Cache extends Getters
   31 {
   32     /**
   33      * @var string Cache key.
   34      */
   35     protected $key;
   36 
   37     protected $lifetime;
   38     protected $now;
   39 
   40     /** @var Config $config */
   41     protected $config;
   42 
   43     /**
   44      * @var DoctrineCache\CacheProvider
   45      */
   46     protected $driver;
   47 
   48     /**
   49      * @var CacheInterface
   50      */
   51     protected $simpleCache;
   52 
   53     protected $driver_name;
   54 
   55     protected $driver_setting;
   56 
   57     /**
   58      * @var bool
   59      */
   60     protected $enabled;
   61 
   62     protected $cache_dir;
   63 
   64     protected static $standard_remove = [
   65         'cache://twig/',
   66         'cache://doctrine/',
   67         'cache://compiled/',
   68         'cache://validated-',
   69         'cache://images',
   70         'asset://',
   71     ];
   72 
   73     protected static $standard_remove_no_images = [
   74         'cache://twig/',
   75         'cache://doctrine/',
   76         'cache://compiled/',
   77         'cache://validated-',
   78         'asset://',
   79     ];
   80 
   81     protected static $all_remove = [
   82         'cache://',
   83         'cache://images',
   84         'asset://',
   85         'tmp://'
   86     ];
   87 
   88     protected static $assets_remove = [
   89         'asset://'
   90     ];
   91 
   92     protected static $images_remove = [
   93         'cache://images'
   94     ];
   95 
   96     protected static $cache_remove = [
   97         'cache://'
   98     ];
   99 
  100     protected static $tmp_remove = [
  101         'tmp://'
  102     ];
  103 
  104     /**
  105      * Constructor
  106      *
  107      * @param Grav $grav
  108      */
  109     public function __construct(Grav $grav)
  110     {
  111         $this->init($grav);
  112     }
  113 
  114     /**
  115      * Initialization that sets a base key and the driver based on configuration settings
  116      *
  117      * @param  Grav $grav
  118      *
  119      * @return void
  120      */
  121     public function init(Grav $grav)
  122     {
  123         /** @var Config $config */
  124         $this->config = $grav['config'];
  125         $this->now = time();
  126 
  127         if (null === $this->enabled) {
  128             $this->enabled = (bool)$this->config->get('system.cache.enabled');
  129         }
  130 
  131         /** @var Uri $uri */
  132         $uri = $grav['uri'];
  133 
  134         $prefix = $this->config->get('system.cache.prefix');
  135         $uniqueness = substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
  136 
  137         // Cache key allows us to invalidate all cache on configuration changes.
  138         $this->key = ($prefix ? $prefix : 'g') . '-' . $uniqueness;
  139         $this->cache_dir = $grav['locator']->findResource('cache://doctrine/' . $uniqueness, true, true);
  140         $this->driver_setting = $this->config->get('system.cache.driver');
  141         $this->driver = $this->getCacheDriver();
  142         $this->driver->setNamespace($this->key);
  143 
  144         /** @var EventDispatcher $dispatcher */
  145         $dispatcher = Grav::instance()['events'];
  146         $dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
  147     }
  148 
  149     /**
  150      * @return CacheInterface
  151      */
  152     public function getSimpleCache()
  153     {
  154         if (null === $this->simpleCache) {
  155             $cache = new \Grav\Framework\Cache\Adapter\DoctrineCache($this->driver, '', $this->getLifetime());
  156 
  157             // Disable cache key validation.
  158             $cache->setValidation(false);
  159 
  160             $this->simpleCache = $cache;
  161         }
  162 
  163         return $this->simpleCache;
  164     }
  165 
  166     /**
  167      * Deletes the old out of date file-based caches
  168      *
  169      * @return int
  170      */
  171     public function purgeOldCache()
  172     {
  173         $cache_dir = dirname($this->cache_dir);
  174         $current = basename($this->cache_dir);
  175         $count = 0;
  176 
  177         foreach (new \DirectoryIterator($cache_dir) as $file) {
  178             $dir = $file->getBasename();
  179             if ($dir === $current || $file->isDot() || $file->isFile()) {
  180                 continue;
  181             }
  182 
  183             Folder::delete($file->getPathname());
  184             $count++;
  185         }
  186 
  187         return $count;
  188     }
  189 
  190     /**
  191      * Public accessor to set the enabled state of the cache
  192      *
  193      * @param bool|int $enabled
  194      */
  195     public function setEnabled($enabled)
  196     {
  197         $this->enabled = (bool)$enabled;
  198     }
  199 
  200     /**
  201      * Returns the current enabled state
  202      *
  203      * @return bool
  204      */
  205     public function getEnabled()
  206     {
  207         return $this->enabled;
  208     }
  209 
  210     /**
  211      * Get cache state
  212      *
  213      * @return string
  214      */
  215     public function getCacheStatus()
  216     {
  217         return 'Cache: [' . ($this->enabled ? 'true' : 'false') . '] Setting: [' . $this->driver_setting . '] Driver: [' . $this->driver_name . ']';
  218     }
  219 
  220     /**
  221      * Automatically picks the cache mechanism to use.  If you pick one manually it will use that
  222      * If there is no config option for $driver in the config, or it's set to 'auto', it will
  223      * pick the best option based on which cache extensions are installed.
  224      *
  225      * @return DoctrineCache\CacheProvider  The cache driver to use
  226      */
  227     public function getCacheDriver()
  228     {
  229         $setting = $this->driver_setting;
  230         $driver_name = 'file';
  231 
  232         // CLI compatibility requires a non-volatile cache driver
  233         if ($this->config->get('system.cache.cli_compatibility') && (
  234             $setting === 'auto' || $this->isVolatileDriver($setting))) {
  235             $setting = $driver_name;
  236         }
  237 
  238         if (!$setting || $setting === 'auto') {
  239             if (extension_loaded('apcu')) {
  240                 $driver_name = 'apcu';
  241             } elseif (extension_loaded('wincache')) {
  242                 $driver_name = 'wincache';
  243             }
  244         } else {
  245             $driver_name = $setting;
  246         }
  247 
  248         $this->driver_name = $driver_name;
  249 
  250         switch ($driver_name) {
  251             case 'apc':
  252             case 'apcu':
  253                 $driver = new DoctrineCache\ApcuCache();
  254                 break;
  255 
  256             case 'wincache':
  257                 $driver = new DoctrineCache\WinCacheCache();
  258                 break;
  259 
  260             case 'memcache':
  261                 if (extension_loaded('memcache')) {
  262                     $memcache = new \Memcache();
  263                     $memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'),
  264                         $this->config->get('system.cache.memcache.port', 11211));
  265                     $driver = new DoctrineCache\MemcacheCache();
  266                     $driver->setMemcache($memcache);
  267                 } else {
  268                     throw new \LogicException('Memcache PHP extension has not been installed');
  269                 }
  270                 break;
  271 
  272             case 'memcached':
  273                 if (extension_loaded('memcached')) {
  274                     $memcached = new \Memcached();
  275                     $memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
  276                         $this->config->get('system.cache.memcached.port', 11211));
  277                     $driver = new DoctrineCache\MemcachedCache();
  278                     $driver->setMemcached($memcached);
  279                 } else {
  280                     throw new \LogicException('Memcached PHP extension has not been installed');
  281                 }
  282                 break;
  283 
  284             case 'redis':
  285                 if (extension_loaded('redis')) {
  286                     $redis = new \Redis();
  287                     $socket = $this->config->get('system.cache.redis.socket', false);
  288                     $password = $this->config->get('system.cache.redis.password', false);
  289 
  290                     if ($socket) {
  291                         $redis->connect($socket);
  292                     } else {
  293                         $redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
  294                             $this->config->get('system.cache.redis.port', 6379));
  295                     }
  296 
  297                     // Authenticate with password if set
  298                     if ($password && !$redis->auth($password)) {
  299                         throw new \RedisException('Redis authentication failed');
  300                     }
  301 
  302                     $driver = new DoctrineCache\RedisCache();
  303                     $driver->setRedis($redis);
  304                 } else {
  305                     throw new \LogicException('Redis PHP extension has not been installed');
  306                 }
  307                 break;
  308 
  309             default:
  310                 $driver = new DoctrineCache\FilesystemCache($this->cache_dir);
  311                 break;
  312         }
  313 
  314         return $driver;
  315     }
  316 
  317     /**
  318      * Gets a cached entry if it exists based on an id. If it does not exist, it returns false
  319      *
  320      * @param  string $id the id of the cached entry
  321      *
  322      * @return object|bool     returns the cached entry, can be any type, or false if doesn't exist
  323      */
  324     public function fetch($id)
  325     {
  326         if ($this->enabled) {
  327             return $this->driver->fetch($id);
  328         }
  329 
  330         return false;
  331     }
  332 
  333     /**
  334      * Stores a new cached entry.
  335      *
  336      * @param  string       $id       the id of the cached entry
  337      * @param  array|object $data     the data for the cached entry to store
  338      * @param  int          $lifetime the lifetime to store the entry in seconds
  339      */
  340     public function save($id, $data, $lifetime = null)
  341     {
  342         if ($this->enabled) {
  343             if ($lifetime === null) {
  344                 $lifetime = $this->getLifetime();
  345             }
  346             $this->driver->save($id, $data, $lifetime);
  347         }
  348     }
  349 
  350     /**
  351      * Deletes an item in the cache based on the id
  352      *
  353      * @param string $id    the id of the cached data entry
  354      * @return bool         true if the item was deleted successfully
  355      */
  356     public function delete($id)
  357     {
  358         if ($this->enabled) {
  359             return $this->driver->delete($id);
  360         }
  361 
  362         return false;
  363     }
  364 
  365     /**
  366      * Deletes all cache
  367      *
  368      * @return bool
  369      */
  370     public function deleteAll()
  371     {
  372         if ($this->enabled) {
  373             return $this->driver->deleteAll();
  374         }
  375 
  376         return false;
  377     }
  378 
  379     /**
  380      * Returns a boolean state of whether or not the item exists in the cache based on id key
  381      *
  382      * @param string $id    the id of the cached data entry
  383      * @return bool         true if the cached items exists
  384      */
  385     public function contains($id)
  386     {
  387         if ($this->enabled) {
  388             return $this->driver->contains(($id));
  389         }
  390 
  391         return false;
  392     }
  393 
  394     /**
  395      * Getter method to get the cache key
  396      */
  397     public function getKey()
  398     {
  399         return $this->key;
  400     }
  401 
  402     /**
  403      * Setter method to set key (Advanced)
  404      */
  405     public function setKey($key)
  406     {
  407         $this->key = $key;
  408         $this->driver->setNamespace($this->key);
  409     }
  410 
  411     /**
  412      * Helper method to clear all Grav caches
  413      *
  414      * @param string $remove standard|all|assets-only|images-only|cache-only
  415      *
  416      * @return array
  417      */
  418     public static function clearCache($remove = 'standard')
  419     {
  420         $locator = Grav::instance()['locator'];
  421         $output = [];
  422         $user_config = USER_DIR . 'config/system.yaml';
  423 
  424         switch ($remove) {
  425             case 'all':
  426                 $remove_paths = self::$all_remove;
  427                 break;
  428             case 'assets-only':
  429                 $remove_paths = self::$assets_remove;
  430                 break;
  431             case 'images-only':
  432                 $remove_paths = self::$images_remove;
  433                 break;
  434             case 'cache-only':
  435                 $remove_paths = self::$cache_remove;
  436                 break;
  437             case 'tmp-only':
  438                 $remove_paths = self::$tmp_remove;
  439                 break;
  440             case 'invalidate':
  441                 $remove_paths = [];
  442                 break;
  443             default:
  444                 if (Grav::instance()['config']->get('system.cache.clear_images_by_default')) {
  445                     $remove_paths = self::$standard_remove;
  446                 } else {
  447                     $remove_paths = self::$standard_remove_no_images;
  448                 }
  449 
  450         }
  451 
  452         // Delete entries in the doctrine cache if required
  453         if (in_array($remove, ['all', 'standard'])) {
  454             $cache = Grav::instance()['cache'];
  455             $cache->driver->deleteAll();
  456         }
  457 
  458         // Clearing cache event to add paths to clear
  459         Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
  460 
  461         foreach ($remove_paths as $stream) {
  462 
  463             // Convert stream to a real path
  464             try {
  465                 $path = $locator->findResource($stream, true, true);
  466                 if($path === false) continue;
  467 
  468                 $anything = false;
  469                 $files = glob($path . '/*');
  470 
  471                 if (is_array($files)) {
  472                     foreach ($files as $file) {
  473                         if (is_link($file)) {
  474                             $output[] = '<yellow>Skipping symlink:  </yellow>' . $file;
  475                         } elseif (is_file($file)) {
  476                             if (@unlink($file)) {
  477                                 $anything = true;
  478                             }
  479                         } elseif (is_dir($file)) {
  480                             if (Folder::delete($file)) {
  481                                 $anything = true;
  482                             }
  483                         }
  484                     }
  485                 }
  486 
  487                 if ($anything) {
  488                     $output[] = '<red>Cleared:  </red>' . $path . '/*';
  489                 }
  490             } catch (\Exception $e) {
  491                 // stream not found or another error while deleting files.
  492                 $output[] = '<red>ERROR: </red>' . $e->getMessage();
  493             }
  494         }
  495 
  496         $output[] = '';
  497 
  498         if (($remove === 'all' || $remove === 'standard') && file_exists($user_config)) {
  499             touch($user_config);
  500 
  501             $output[] = '<red>Touched: </red>' . $user_config;
  502             $output[] = '';
  503         }
  504 
  505         // Clear stat cache
  506         @clearstatcache();
  507 
  508         // Clear opcache
  509         if (function_exists('opcache_reset')) {
  510             @opcache_reset();
  511         }
  512 
  513         return $output;
  514     }
  515 
  516     public static function invalidateCache()
  517     {
  518         $user_config = USER_DIR . 'config/system.yaml';
  519 
  520         if (file_exists($user_config)) {
  521             touch($user_config);
  522         }
  523 
  524         // Clear stat cache
  525         @clearstatcache();
  526 
  527         // Clear opcache
  528         if (function_exists('opcache_reset')) {
  529             @opcache_reset();
  530         }
  531 
  532     }
  533 
  534     /**
  535      * Set the cache lifetime programmatically
  536      *
  537      * @param int $future timestamp
  538      */
  539     public function setLifetime($future)
  540     {
  541         if (!$future) {
  542             return;
  543         }
  544 
  545         $interval = (int)($future - $this->now);
  546         if ($interval > 0 && $interval < $this->getLifetime()) {
  547             $this->lifetime = $interval;
  548         }
  549     }
  550 
  551 
  552     /**
  553      * Retrieve the cache lifetime (in seconds)
  554      *
  555      * @return mixed
  556      */
  557     public function getLifetime()
  558     {
  559         if ($this->lifetime === null) {
  560             $this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default
  561         }
  562 
  563         return $this->lifetime;
  564     }
  565 
  566     /**
  567      * Returns the current driver name
  568      *
  569      * @return mixed
  570      */
  571     public function getDriverName()
  572     {
  573         return $this->driver_name;
  574     }
  575 
  576     /**
  577      * Returns the current driver setting
  578      *
  579      * @return mixed
  580      */
  581     public function getDriverSetting()
  582     {
  583         return $this->driver_setting;
  584     }
  585 
  586     /**
  587      * is this driver a volatile driver in that it resides in PHP process memory
  588      *
  589      * @param string $setting
  590      * @return bool
  591      */
  592     public function isVolatileDriver($setting)
  593     {
  594         if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
  595             return true;
  596         }
  597 
  598         return false;
  599     }
  600 
  601     /**
  602      * Static function to call as a scheduled Job to purge old Doctrine files
  603      */
  604     public static function purgeJob()
  605     {
  606         /** @var Cache $cache */
  607         $cache = Grav::instance()['cache'];
  608         $deleted_folders = $cache->purgeOldCache();
  609 
  610         echo 'Purged ' . $deleted_folders . ' old cache folders...';
  611     }
  612 
  613     /**
  614      * Static function to call as a scheduled Job to clear Grav cache
  615      *
  616      * @param string $type
  617      */
  618     public static function clearJob($type)
  619     {
  620         $result = static::clearCache($type);
  621         static::invalidateCache();
  622 
  623         echo strip_tags(implode("\n", $result));
  624     }
  625 
  626     public function onSchedulerInitialized(Event $event)
  627     {
  628         /** @var Scheduler $scheduler */
  629         $scheduler = $event['scheduler'];
  630         $config = Grav::instance()['config'];
  631 
  632         // File Cache Purge
  633         $at = $config->get('system.cache.purge_at');
  634         $name = 'cache-purge';
  635         $logs = 'logs/' . $name . '.out';
  636 
  637         $job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [], $name );
  638         $job->at($at);
  639         $job->output($logs);
  640         $job->backlink('/config/system#caching');
  641 
  642         // Cache Clear
  643         $at = $config->get('system.cache.clear_at');
  644         $clear_type = $config->get('system.cache.clear_job_type');
  645         $name = 'cache-clear';
  646         $logs = 'logs/' . $name . '.out';
  647 
  648         $job = $scheduler->addFunction('Grav\Common\Cache::clearJob', [$clear_type], $name );
  649         $job->at($at);
  650         $job->output($logs);
  651         $job->backlink('/config/system#caching');
  652 
  653     }
  654 
  655 
  656 }