Rev 1 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire Pages Loader Cache** Implements page caching of loaded pages and PageArrays for $pages API variable** ProcessWire 3.x, Copyright 2022 by Ryan Cramer* https://processwire.com**/class PagesLoaderCache extends Wire {/*** Pages that have been cached, indexed by ID**/protected $pageIdCache = array();/*** Cached selector strings and the PageArray that was found.**/protected $pageSelectorCache = array();/*** [ 'cache group name' => [ page IDs ] ]** @var array**/protected $cacheGroups = array();/*** @var Pages**/protected $pages;/*** Construct** @param Pages $pages**/public function __construct(Pages $pages) {$this->pages = $pages;}/*** Get cache status** Returns count of each cache type, or contents of each cache type of verbose option is specified.** @param bool|null $verbose Specify true to get contents of cache, false to get string counts, or omit for array of counts* @return array|string* @since 3.0.198**/public function getCacheStatus($verbose = null) {$a = array('pages' => ($verbose ? $this->pageIdCache : count($this->pageIdCache)),'selectors' => ($verbose ? $this->pageSelectorCache : count($this->pageSelectorCache)),'groups' => ($verbose ? $this->cacheGroups : count($this->cacheGroups)),);return ($verbose === false ? "pages=$a[pages], selectors=$a[selectors], groups=$a[groups]" : $a);}/*** Given a Page ID, return it if it's cached, or NULL of it's not.** If no ID is provided, then this will return an array copy of the full cache.** You may also pass in the string "id=123", where 123 is the page_id** @param int|string|null $id* @return Page|array|null**/public function getCache($id = null) {if(!$id) return $this->pageIdCache;if(!ctype_digit("$id")) $id = str_replace('id=', '', $id);if(ctype_digit("$id")) $id = (int) $id;if(!isset($this->pageIdCache[$id])) return null;/** @var Page $page */$page = $this->pageIdCache[$id];$page->setOutputFormatting($this->pages->outputFormatting);return $page;}/*** Cache the given page.** @param Page $page* @return void**/public function cache(Page $page) {if($page->id) $this->pageIdCache[$page->id] = $page;}/*** Cache given page into a named group that it can be uncached with** @param Page $page* @param string $groupName* @since 3.0.198**/public function cacheGroup(Page $page, $groupName) {if(!$page->id) return;if(!isset($this->cacheGroups[$groupName])) $this->cacheGroups[$groupName] = array();$this->pageIdCache[$page->id] = $page;$this->cacheGroups[$groupName][] = $page->id;}/*** Remove the given page from the cache.** Note: does not remove pages from selectorCache. Call uncacheAll to do that.** @param Page|int $page Page to uncache or ID of page (prior to 3.0.153 only Page object was accepted)* @param array $options Additional options to modify behavior:* - `shallow` (bool): By default, this method also calls $page->uncache(). To prevent call to $page->uncache(), set 'shallow' => true.* @return bool True if page was uncached, false if it didn't need to be**/public function uncache($page, array $options = array()) {if($page instanceof Page) {$pageId = $page->id;} else {$pageId = is_int($page) ? $page : (int) "$page";$page = isset($this->pageIdCache[$pageId]) ? $this->pageIdCache[$pageId] : null;}if(empty($options['shallow']) && $page) {$page->uncache();}if(isset($this->pageIdCache[$pageId])) {unset($this->pageIdCache[$pageId]);return true;} else {return false;}}/*** Remove all pages from the cache** @param Page $page Optional Page that initiated the uncacheAll* @param array $options Additional options to modify behavior:* - `shallow` (bool): By default, this method also calls $page->uncache(). To prevent call to $page->uncache(), set 'shallow' => true.* @return int Number of pages uncached**/public function uncacheAll(Page $page = null, array $options = array()) {if($page) {} // to ignore unused parameter inspection$user = $this->wire()->user;$language = $this->wire()->languages ? $user->language : null;$cnt = 0;$this->pages->sortfields(true); // resetif($this->wire()->config->debug) {$this->pages->debugLog('uncacheAll', 'pageIdCache=' . count($this->pageIdCache) . ', pageSelectorCache=' .count($this->pageSelectorCache));}foreach($this->pageIdCache as $id => $page) {if($id == $user->id || ($language && $language->id == $id)) continue;if($page->numChildren) continue;if(empty($options['shallow'])) $page->uncache();unset($this->pageIdCache[$page->id]);$cnt++;}$this->pageIdCache = array();$this->pageSelectorCache = array();$this->cacheGroups = array();Page::$loadingStack = array();Page::$instanceIDs = array();return $cnt;}/*** Uncache pages that were cached with given group name** @param string $groupName* @param array $options* @return int* @since 3.0.198**/public function uncacheGroup($groupName, array $options = array()) {$qty = 0;if(!isset($this->cacheGroups[$groupName])) return 0;foreach($this->cacheGroups[$groupName] as $pageId) {if(!isset($this->pageIdCache[$pageId])) continue;$page = $this->pageIdCache[$pageId];if($page && empty($options['shallow'])) $page->uncache();unset($this->pageIdCache[$pageId]);$qty++;}unset($this->cacheGroups[$groupName]);return $qty;}/*** Cache the given selector string and options with the given PageArray** @param string $selector* @param array $options* @param PageArray $pages* @return bool True if pages were cached, false if not**/public function selectorCache($selector, array $options, PageArray $pages) {// get the string that will be used for caching$selector = $this->getSelectorCache($selector, $options, true);// optimization: don't cache single pages that have an unpublished status or higherif(count($pages) && !empty($options['findOne']) && $pages->first()->status >= Page::statusUnpublished) return false;$this->pageSelectorCache[$selector] = clone $pages;return true;}/*** Convert an options array to a string** @param array $options* @return string**/protected function optionsArrayToString(array $options) {$str = '';ksort($options);foreach($options as $key => $value) {if(is_array($value)) {$value = $this->optionsArrayToString($value);} else if(is_object($value)) {if(method_exists($value, '__toString')) {$value = (string) $value;} else {$value = wireClassName($value);}}$str .= "[$key:$value]";}return $str;}/*** Retrieve any cached page IDs for the given selector and options OR false if none found.** You may specify a third param as TRUE, which will cause this to just return the selector string (with hashed options)** @param string $selector* @param array $options* @param bool $returnSelector default false* @return array|null|string|PageArray**/public function getSelectorCache($selector, $options, $returnSelector = false) {if(count($options)) {$optionsHash = $this->optionsArrayToString($options);$selector .= "," . $optionsHash;} else {$selector .= ",";}// optimization to use consistent conventions for commonly interchanged names$selector = str_replace(array('path=/,','parent=/,'),array('id=1,','parent_id=1,'),$selector);// optimization to filter out common status checks for pages that won't be cached anywayif(!empty($options['findOne'])) {$selector = str_replace(array('status<' . Page::statusUnpublished,'status<' . Page::statusMax,'start=0','limit=1',',',' '),'',$selector);$selector = trim($selector, ", ");}// cache non-default languages separatelyif($this->wire()->languages) {$language = $this->wire()->user->language;if(!$language->isDefault()) {$selector .= ", _lang=$language->id"; // for caching purposes only, not recognized by PageFinder}}if($returnSelector) return $selector;if(isset($this->pageSelectorCache[$selector])) return $this->pageSelectorCache[$selector];return null;}}