public function onAfterWrite() { $stage_mode_mapping = array("Stage" => "stage", "Live" => "live"); foreach ($stage_mode_mapping as $stage => $mode) { $service = new CacheableNavigationService($mode, $this->owner); $service->refreshCachedConfig(); } }
/** * Ensure with and without filters, we get expected output */ public function testGetChildren() { $config = $this->objFromFixture('SiteConfig', 'default'); $parent = $this->objFromFixture('SiteTree', 'sitetreetest-page-1'); $children = $parent->Children()->toArray(); $models = array_merge(array($parent), $children); // Fake a rebuild task - Populate the object-cache with our fixture data foreach ($models as $model) { $service = new CacheableNavigationService('Live', $config, $model); $service->refreshCachedPage(); } // Fetch the cache $objCache = $service->getObjectCache(); $siteMap = $objCache->get_site_map(); // Default: No custom filters passed - return everything $children = $siteMap[1]->getChildren(); $this->assertEquals(2, $children->count()); // Default: No custom filters passed - return everything $children = $siteMap[1]->getChildren(true, null); $this->assertEquals(2, $children->count()); // Default: No custom filters passed - return everything $children = $siteMap[1]->getChildren(true, array()); $this->assertEquals(2, $children->count()); // Default: No custom filters passed - return everything $children = $siteMap[1]->getChildren(false, array()); $this->assertEquals(2, $children->count()); // Don't filter and still no filters passed $children = $siteMap[1]->getChildren(false, null); $this->assertEquals(2, $children->count()); // Don't filter and filters passed $children = $siteMap[1]->getChildren(false, array('Title' => 'This is a child page 2')); $this->assertEquals(1, $children->count()); // Do filter and filters passed $children = $siteMap[1]->getChildren(true, array('Title' => 'This is a child page 1')); $this->assertEquals(2, $children->count()); // Do filter and filters passed $children = $siteMap[1]->getChildren(true, array('Title' => 'This is a child page 2')); $this->assertEquals(2, $children->count()); // Do filter and filters passed $children = $siteMap[1]->getChildren(false, array('Title' => 'This is a child page 2')); $this->assertEquals(1, $children->count()); // Do filter and "bad" filter passed $children = $siteMap[1]->getChildren(false, array('Title' => 'This is a child page null')); $this->assertEquals(0, $children->count()); $children = $siteMap[1]->getChildren(true, array('Dummy' => null)); $this->assertEquals(2, $children->count()); $children = $siteMap[1]->getChildren(false, array('Dummy' => null)); $this->assertEquals(2, $children->count()); }
/** * * Create a {@link ChunkedCachableRefreshJob} for each "chunk" of N pages * to refresh the caches of. Once run, $chunk is truncated and passed back its * original reference. * * @param CacheableNavigationService $service * @param array $chunk * @param string $stage * @param number $subsiteID * @return number $jobDescriptorID (Return value not used) */ public function queue(CacheableNavigationService $service, $chunk, $stage, $subsiteID) { // We only need to do this during queueing $service->clearInternalCache(); $job = new CachableChunkedRefreshJob($service, $chunk, $stage, $subsiteID); $jobDescriptorID = singleton('QueuedJobService')->queueJob($job); return $jobDescriptorID; }
/** * */ public function testCompleteBuild() { $model = $this->objFromFixture('SiteTree', 'servicetest-page-1'); $service = new CacheableNavigationService('Live', null, $model); // Cache should be empty $cachable = $service->getCacheableFrontEnd()->load($service->getIdentifier()); $this->assertFalse($cachable->get_completed()); // Populate the cache with a page $service->refreshCachedPage(); // Set it to complete and re-test $service->completeBuild(); $cachable = $service->getCacheableFrontEnd()->load($service->getIdentifier()); $this->assertTrue($cachable->get_completed()); }
/** * * Template function for debugging. Allows you to see at-a-glance, * the fields, functions and child nodes held in the Object-Cache about * the current object. * * Usage: * * <code> * <% with $CachedData %> * $Debug(98) * <% end_with %> * <code> * * @return string */ public function Debug($id = null) { if (!Director::isDev() || !isset($_REQUEST['showcache'])) { return; } if ($id) { $mode = strtolower($_REQUEST['showcache']); $conf = SiteConfig::current_site_config(); $cacheService = new CacheableNavigationService($mode, $conf); $objectCache = $cacheService->getObjectCache(); $cachedSiteTree = $objectCache->get_site_map(); if (isset($cachedSiteTree[$id])) { $object = $cachedSiteTree[$id]; } else { return false; } } else { $object = $this; } $message = "<h2>Object-Cache fields & functions for: " . get_class($object) . "</h2>"; $message .= "<ul>"; $message .= "\t<li><strong>Cached specifics:</strong>"; $message .= "\t\t<ul>"; $message .= "\t\t\t<li>ID: " . $object->ID . "</li>"; $message .= "\t\t\t<li>Title: " . $object->Title . "</li>"; $message .= "\t\t\t<li>ClassName: " . $object->ClassName . "</li>"; $message .= "\t\t\t<li>Child count: " . $object->getChildren()->count() . "</li>"; $message .= "\t\t</ul>"; $message .= "\t</li>"; $message .= "</ul>"; $message .= "<ul>"; $message .= "\t<li><strong>Cached Fields:</strong>"; $message .= "\t\t<ul>"; foreach ($object->get_cacheable_fields() as $field) { $message .= "\t\t\t<li>" . $field . ': ' . $object->{$field} . "</li>"; } $message .= "\t\t</ul>"; $message .= "\t</li>"; $message .= "\t<li><strong>Cached Functions:</strong>"; $message .= "\t\t<ul>"; foreach ($object->get_cacheable_functions() as $function) { $message .= "\t\t\t<li>" . $function . '</li>'; } $message .= "\t\t</ul>"; $message .= "\t</li>"; $message .= "</ul>"; $message .= "<h2>Child nodes of this object:</h2>"; $message .= '<ol>'; foreach ($object->getChildren() as $child) { $message .= "\t<li>" . $child->Title . ' (#' . $child->ID . ')</li>'; } $message .= '</ol>'; return $message; }
/** * * Get the total no. objects in the cache. Due to the way {@link CacheableNavigation} * works, this is restricted to "SiteTree-ish" objects only at this point. * * @param string $stage * @param string $className * @return int $total */ private function getTotalCachedObjects($stage, $className = 'CacheableSiteTree') { $conf = SiteConfig::current_site_config(); $service = new CacheableNavigationService($stage, $conf); $cache = $service->getObjectCache(); $cachedIDs = $this->_cachedIDs; if ($cache && ($siteMap = $cache->get_site_map())) { // For reasons unknown, items appear in object-cache with NULL properties $cachedSiteTree = ArrayList::create($siteMap)->filterByCallback(function ($item) use(&$cachedIDs) { try { if (!is_null($item->ParentID)) { // push the item ID to the _cachedIDs array for comparison later $cachedIDs[] = $item->ID; return true; } return false; } catch (Exception $e) { echo $e->getMessage(); } }); $this->_cachedIDs = $cachedIDs; return $cachedSiteTree->count(); } return 0; }
/** * * @param array $modes * @param boolean $forceRemoval Whether to unset() children in {@link CacheableSiteTree::removeChild()}. * @return void */ public function removePageCache($modes, $forceRemoval = true) { // Increase memory to max-allowable CacheableConfig::configure_memory_limit(); $siteConfig = $this->owner->getSiteConfig(); if (!$siteConfig->exists()) { $siteConfig = SiteConfig::current_site_config(); } foreach ($modes as $stage => $mode) { $service = new CacheableNavigationService($mode, $siteConfig, $this->owner); $cache_frontend = $service->getCacheableFrontEnd(); $id = $service->getIdentifier(); $cached = $cache_frontend->load($id); if ($cached) { $cached_site_config = $cached->get_site_config(); if (!$cached_site_config) { $service->refreshCachedConfig(); } $service->removeCachedPage($forceRemoval); } } }
/** * * Physically runs the task which - dependent on QueuedJobs being installed and * not skipped via script params - will queue-up chunks of pages to be cached, * or just attempt to cache call objects at once. * * @param SS_HTTPRequest $request * @return void */ public function run($request) { // Increase memory to max-allowable CacheableConfig::configure_memory_limit(); $startTime = time(); $skipQueue = $request->getVar('SkipQueue'); $currentStage = Versioned::current_stage(); /* * Restrict cache rebuild to the given stage - useful for debugging or * "Poor Man's" chunking. */ if ($paramStage = $request->getVar('Stage')) { $stage_mode_mapping = array(ucfirst($paramStage) => strtolower($paramStage)); // All stages } else { $stage_mode_mapping = array("Stage" => "stage", "Live" => "live"); } $canQueue = interface_exists('QueuedJob'); $siteConfigs = DataObject::get('SiteConfig'); foreach ($stage_mode_mapping as $stage => $mode) { Versioned::set_reading_mode('Stage.' . $stage); if (class_exists('Subsite')) { Subsite::disable_subsite_filter(true); Config::inst()->update("CacheableSiteConfig", 'cacheable_fields', array('SubsiteID')); Config::inst()->update("CacheableSiteTree", 'cacheable_fields', array('SubsiteID')); } foreach ($siteConfigs as $config) { $service = new CacheableNavigationService($mode, $config); if ($service->refreshCachedConfig()) { echo 'Caching: SiteConfig object ' . trim($config->ID) . ' (' . $config->Title . ') with mode: ' . $mode . self::new_line(2); } else { echo 'Caching fails: SiteConfig object ' . trim($config->ID) . ' (' . $config->Title . ') with mode: ' . $mode . self::new_line(2); } if (class_exists('Subsite')) { $pages = DataObject::get("Page", "SubsiteID = '" . $config->SubsiteID . "'"); } else { $pages = DataObject::get("Page"); } $pageCount = $pages->count(); /* * * Queueing should only occur if: * - QueuedJob module is available * - SkipQueue param is not set * - Total no. pages is greater than the chunk divisor */ $lowPageCount = self::$chunk_divisor > $pageCount; $doQueue = $canQueue && !$skipQueue && !$lowPageCount; if ($pageCount) { $i = 0; $chunkNum = 0; $chunk = array(); foreach ($pages as $page) { $i++; // If QueuedJobs exists and isn't user-disabled: Chunk if ($doQueue) { // Start building a chunk of pages to be refreshed $chunk[] = $page; $chunkSize = count($chunk); /* * Conditions of chunking: * - Initial chunks are chunk-size == self::$chunk_divisor * - Remaining object count equals no. objects in current $chunk */ $doChunking = $this->chunkForQueue($pageCount, $chunkSize, $i); if ($doChunking) { $chunkNum++; $this->queue($service, $chunk, $stage, $config->SubsiteID); echo "Queued chunk #" . $chunkNum . ' (' . $chunkSize . ' objects).' . self::new_line(); $chunk = array(); } // Default to non-chunking if no queuedjobs or script instructed to skip queuing } else { $percentComplete = $this->percentageComplete($i, $pageCount); $service->set_model($page); if ($service->refreshCachedPage(true)) { echo 'Caching: ' . trim($page->Title) . ' (' . $percentComplete . ') ' . self::new_line(); } else { echo 'Caching fails: ' . trim($page->Title) . ' (' . $percentComplete . ') ' . self::new_line(); } } $page->flushCache(); } } $service->completeBuild(); // Completion message $msg = self::new_line() . $pageCount . ' ' . $stage . ' pages in subsite ' . $config->ID; if ($doQueue) { $msg .= ' queued for caching.' . self::new_line(); } else { $msg .= ' objects cached.' . self::new_line(); } echo $msg . self::new_line(); } if (class_exists('Subsite')) { Subsite::disable_subsite_filter(false); } } Versioned::set_reading_mode($currentStage); $endTime = time(); $totalTime = $endTime - $startTime; $this->showConfig($totalTime, $request, $lowPageCount); }