📜 ⬆️ ⬇️

Magento Enterprise: What is Full Page Cache and why is it needed?

Magento Enterprise - Full Page Cache

Magento Enterprise: What is Full Page Cache and why it is needed.



For those who are familiar with Magento, it is not a secret that this e-commerce engine is quite demanding on hardware. But the developers of this online store tried to solve this problem and came up with many different kinds of "accelerators", without which, perhaps, launching a store on the Magento engine in production is not worth it. For too long, Magento will give the page to the end user. Among such "accelerators" are caches, indices, compilation, combining JS / CSS into one compressed file, etc.
')
One of the main "chips" Magento Enterprise is Full Page Cache (hereinafter referred to as FPC). This "chip" implements the module Enterprise_PageCache, which is part of the package Magento Enterprise.

The article discusses the latest version of Magento Enterprise at the time of this writing: 1.13.1.

FPC allows you to give the server a page in milliseconds, practically without loading the server. I spent time measuring the return time of the product page by the server (in one of the projects I was working on), here are the results:

Magento Enterprise - FPC


Why is the difference so great? Let's figure it out.

What and when to cache FPC


As the name implies, Full Page Cache caches the entire page. But not all pages are cached. At least, because there is no point for it. By default, only product pages, category pages, CMS pages and a 404 error page (page not found) are cached. This can be seen by looking at the module configuration (config.xml):

<frontend> <cache> <requests> <_no_route>enterprise_pagecache/processor_noroute</_no_route> <cms>enterprise_pagecache/processor_default</cms> <catalog> <category> <view>enterprise_pagecache/processor_category</view> </category> </catalog> <catalog> <product> <view>enterprise_pagecache/processor_product</view> </product> </catalog> </requests> </cache> </frontend> 


In the frontend / cache / requests section, here is the frontName of the module controller (its value is stored in the module configuration along the path: frontend / routers / route_name / args / frontName), then the controller and the action (controller and action are optional parameters). And the transmitted value is the request processor.

In the same way, you can force the caching of the controllers / controller / action of your module; it is enough to add data to the necessary section in the same way as the Enterprise_PageCache module does. Example:
 <frontend> <cache> <requests> <productinfo> <index>enterprise_pagecache/processor_default</index> </productinfo> </requests> </cache> </frontend> 


At the same time there are a number of conditions under which FPC does not cache pages. For example, HTTPS pages, pages with the GET parameter no_cache are not cached. The canProcessRequest and isAllowed methods of the Enterprise_PageCache_Model_Processor class:

 /** * Do basic validation for request to be cached * * @param Zend_Controller_Request_Http $request * @return bool */ public function canProcessRequest(Zend_Controller_Request_Http $request) { $res = $this->isAllowed(); $res = $res && Mage::app()->useCache('full_page'); if ($request->getParam('no_cache')) { $res = false; } if ($res) { $maxDepth = Mage::getStoreConfig(self::XML_PATH_ALLOWED_DEPTH); $queryParams = $request->getQuery(); unset($queryParams[Enterprise_PageCache_Model_Cache::REQUEST_MESSAGE_GET_PARAM]); $res = count($queryParams)<=$maxDepth; } if ($res) { $multicurrency = Mage::getStoreConfig(self::XML_PATH_CACHE_MULTICURRENCY); if (!$multicurrency && !empty($_COOKIE['currency'])) { $res = false; } } return $res; } 

 /** * Check if processor is allowed for current HTTP request. * Disable processing HTTPS requests and requests with "NO_CACHE" cookie * * @return bool */ public function isAllowed() { if (!$this->_requestId) { return false; } if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { return false; } if (isset($_COOKIE['NO_CACHE'])) { return false; } if (isset($_GET['no_cache'])) { return false; } if (isset($_GET[Mage_Core_Model_Session_Abstract::SESSION_ID_QUERY_PARAM])) { return false; } if (!Mage::app()->useCache('full_page')) { return false; } return true; } 


How does FPC work


If the page is cached by the FPC cache, the FPC disables the standard block cache. See the process_PreDispatch method of the Enterprise_PageCache_Model_Observer class, which is triggered when the controller_action_predispatch event occurs:

 /** * Check when cache should be disabled * * @param Varien_Event_Observer $observer * @return Enterprise_PageCache_Model_Observer */ public function processPreDispatch(Varien_Event_Observer $observer) { if (!$this->isCacheEnabled()) { return $this; } $action = $observer->getEvent()->getControllerAction(); /* @var $request Mage_Core_Controller_Request_Http */ $request = $action->getRequest(); $noCache = $this->_getCookie()->get(Enterprise_PageCache_Model_Processor::NO_CACHE_COOKIE); if ($noCache) { Mage::getSingleton('catalog/session')->setParamsMemorizeDisabled(false); $this->_getCookie()->renew(Enterprise_PageCache_Model_Processor::NO_CACHE_COOKIE); } elseif ($action) { Mage::getSingleton('catalog/session')->setParamsMemorizeDisabled(true); } /** * Check if request will be cached */ if ($this->_processor->canProcessRequest($request)) { Mage::app()->getCacheInstance()->banUse(Mage_Core_Block_Abstract::CACHE_GROUP); } $this->_getCookie()->updateCustomerCookies(); return $this; } 


FPC generates for each HTTP request its cache identifier, which will be used to save the page to the cache. The cache identifier depends on several parameters: whether the user is authorized or not, which segments it belongs to, which group of users it belongs to, etc. You can see how this identifier is generated in the _ _ _ _ _ _; _RequestIds method of the Enterprise_PageCache_Model_Processor class:

 /** * Populate request ids * @return Enterprise_PageCache_Model_Processor */ protected function _createRequestIds() { $uri = $this->_getFullPageUrl(); //Removing get params $pieces = explode('?', $uri); $uri = array_shift($pieces); /** * Define COOKIE state */ if ($uri) { if (isset($_COOKIE['store'])) { $uri = $uri.'_'.$_COOKIE['store']; } if (isset($_COOKIE['currency'])) { $uri = $uri.'_'.$_COOKIE['currency']; } if (isset($_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER_GROUP])) { $uri .= '_' . $_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER_GROUP]; } if (isset($_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER_LOGGED_IN])) { $uri .= '_' . $_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER_LOGGED_IN]; } if (isset($_COOKIE[Enterprise_PageCache_Model_Cookie::CUSTOMER_SEGMENT_IDS])) { $uri .= '_' . $_COOKIE[Enterprise_PageCache_Model_Cookie::CUSTOMER_SEGMENT_IDS]; } if (isset($_COOKIE[Enterprise_PageCache_Model_Cookie::IS_USER_ALLOWED_SAVE_COOKIE])) { $uri .= '_' . $_COOKIE[Enterprise_PageCache_Model_Cookie::IS_USER_ALLOWED_SAVE_COOKIE]; } $designPackage = $this->_getDesignPackage(); if ($designPackage) { $uri .= '_' . $designPackage; } } $this->_requestId = $uri; $this->_requestCacheId = $this->prepareCacheId($this->_requestId); return $this; } 


FPC uses placeholders. They are added to Magento using the cache.xml configuration file in the etc folder of the module. A placeholder is handled by a placeholder container. The container is the class that the placeholder will process during the FPC rendering (the rendering process of the block by the FPC cache is different from the usual rendering of the block). An example of the cache.xml file:

 <?xml version="1.0" encoding="UTF-8"?> <config> <placeholders> <turnkeye_popup> <block>turnkeye_popup/popup</block> <placeholder>POPUP</placeholder> <container>Turnkeye_Popup_Model_PageCache_Container_Popup</container> <cache_lifetime>86400</cache_lifetime> </turnkeye_popup> </placeholders> </config> 


Briefly about the configuration of the placeholder:


During rendering, with FPC turned on, the contents of each block are wrapped with a placeholder and cached: when the core_block_abstract_to_html_after event fires, the renderBlockPlaceholder method of the Enterprise_PageCache_Model_Observer class is executed:

 /** * Render placeholder tags around the block if needed * * @param Varien_Event_Observer $observer * @return Enterprise_PageCache_Model_Observer */ public function renderBlockPlaceholder(Varien_Event_Observer $observer) { if (!$this->_isEnabled) { return $this; } $block = $observer->getEvent()->getBlock(); $transport = $observer->getEvent()->getTransport(); $placeholder = $this->_config->getBlockPlaceholder($block); if ($transport && $placeholder && !$block->getSkipRenderTag()) { $blockHtml = $transport->getHtml(); $request = Mage::app()->getFrontController()->getRequest(); /** @var $processor Enterprise_PageCache_Model_Processor_Default */ $processor = $this->_processor->getRequestProcessor($request); if ($processor && $processor->allowCache($request)) { $container = $placeholder->getContainerClass(); if ($container && !Mage::getIsDeveloperMode()) { $container = new $container($placeholder); $container->setProcessor(Mage::getSingleton('enterprise_pagecache/processor')); $container->setPlaceholderBlock($block); $container->saveCache($blockHtml); } } $blockHtml = $placeholder->getStartTag() . $blockHtml . $placeholder->getEndTag(); $transport->setHtml($blockHtml); } return $this; } 


Here $ placeholder is obtained using the Enterprise_PageCache_Model_Config class using the getBlockPlaceholder method:

 /** * Create placeholder object based on block information * * @param Mage_Core_Block_Abstract $block * @return Enterprise_PageCache_Model_Container_Placeholder */ public function getBlockPlaceholder($block) { $this->_initPlaceholders(); $type = $block->getType(); if (isset($this->_placeholders[$type])) { $placeholderData = false; foreach ($this->_placeholders[$type] as $placeholderInfo) { if (!empty($placeholderInfo['name'])) { if ($placeholderInfo['name'] == $block->getNameInLayout()) { $placeholderData = $placeholderInfo; } } else { $placeholderData = $placeholderInfo; } } if (!$placeholderData) { return false; } $placeholder = $placeholderData['code'] . ' container="' . $placeholderData['container'] . '"' . ' block="' . get_class($block) . '"'; $placeholder.= ' cache_id="' . $block->getCacheKey() . '"'; if (!empty($placeholderData['cache_lifetime'])) { $placeholder .= ' cache_lifetime="' . $placeholderData['cache_lifetime'] . '"'; } foreach ($block->getCacheKeyInfo() as $k => $v) { if (is_string($k) && !empty($k)) { $placeholder .= ' ' . $k . '="' . $v . '"'; } } $placeholder = Mage::getModel('enterprise_pagecache/container_placeholder', $placeholder); return $placeholder; } return false; } 


The constructor for the Enterprise_PageCache_Model_Container_Placeholder class is:

 /** * Class constructor. * Initialize placeholder name and attributes based on definition * * @param string $definition */ public function __construct($definition) { if ($definition && array_key_exists($definition, self::$_definitionMap)) { $definition = self::$_definitionMap[$definition]; } $this->_definition = $definition; $definition = explode(' ', $definition); $this->_name = $definition[0]; $count = count($definition); if ($count>1) { for ($i=1; $i_attributes[$info[0]] = isset($info[1]) ? trim($info[1], '"\'') : null; } } } 


In the future, it will be possible to get block attributes in the container class using the getAttribute method:

 /** * Get attribute by specific code * @param $code string * @return string */ public function getAttribute($code) { return isset($this->_attributes[$code]) ? $this->_attributes[$code] : null; } 


Methods for wrapping content block wrapper:

 /** * Retrieve placeholder definition hash * * @return string */ protected function _getDefinitionHash() { $definition = $this->getDefinition(); $result = array_search($definition, self::$_definitionMap); if ($result === false) { $result = $this->getName() . '_' . md5($definition); self::$_definitionMap[$result] = $definition; } return $result; } /** * Get placeholder start tag for block html generation * * @return string */ public function getStartTag() { return '<!--{' . $this->_getDefinitionHash() . '}-->'; } /** * Get placeholder end tag for block html generation * * @return string */ public function getEndTag() { return '<!--/{' . $this->_getDefinitionHash() . '}-->'; } 


Example of a wrapper content wrapper block content:

 < !--{TOPMENU_21c7f778e21d072d331836703b6295f5}--> < div class="nav-container"> < ul id="nav"> < li class="level0 nav-1 first last level-top"> < a class="level-top" href="http://example.com/test-category.html"> < span>Test category</span> </a> </li> </ul> </div> < !--/{TOPMENU_21c7f778e21d072d331836703b6295f5}--> 


Before a response is sent to the request, the controller_front_send_response_before event is fired and the cacheResponse method of the Enterprise_PageCache_Model_Observer class is executed, which saves the page to the cache, if necessary. The code for the cacheResponse method is:

 /** * Save page body to cache storage * * @param Varien_Event_Observer $observer * @return Enterprise_PageCache_Model_Observer */ public function cacheResponse(Varien_Event_Observer $observer) { if (!$this->isCacheEnabled()) { return $this; } $frontController = $observer->getEvent()->getFront(); $request = $frontController->getRequest(); $response = $frontController->getResponse(); $this->_saveDesignException(); $this->_processor->processRequestResponse($request, $response); return $this; } 


Here, the processRequestResponse method saves the entire page and the necessary data to the cache, if necessary:

 /** * Process response body by specific request * * @param Zend_Controller_Request_Http $request * @param Zend_Controller_Response_Http $response * @return Enterprise_PageCache_Model_Processor */ public function processRequestResponse(Zend_Controller_Request_Http $request, Zend_Controller_Response_Http $response ) { // we should add original path info tag as another way we can't drop some entities from cron job $this->addRequestTag(Enterprise_PageCache_Helper_Url::prepareRequestPathTag($request->getOriginalPathInfo())); $cacheInstance = Enterprise_PageCache_Model_Cache::getCacheInstance(); /** * Basic validation for request processing */ if ($this->canProcessRequest($request)) { $processor = $this->getRequestProcessor($request); if ($processor && $processor->allowCache($request)) { $this->setMetadata('cache_subprocessor', get_class($processor)); $cacheId = $this->prepareCacheId($processor->getPageIdInApp($this)); $content = $processor->prepareContent($response); /** * Replace all occurrences of session_id with unique marker */ Enterprise_PageCache_Helper_Url::replaceSid($content); Enterprise_PageCache_Helper_Form_Key::replaceFormKey($content); if (function_exists('gzcompress')) { $content = gzcompress($content); } $contentSize = strlen($content); $currentStorageSize = (int) $cacheInstance->load(self::CACHE_SIZE_KEY); if (Mage::getStoreConfig(Enterprise_PageCache_Model_Processor::XML_PATH_CACHE_DEBUG)) { $response->setBody(implode(', ', $this->getRequestTags()) . $response->getBody()); } $maxSizeInBytes = Mage::getStoreConfig(self::XML_PATH_CACHE_MAX_SIZE) * 1024 * 1024; if ($currentStorageSize >= $maxSizeInBytes) { Mage::app()->getCacheInstance()->invalidateType('full_page'); return $this; } $cacheInstance->save($content, $cacheId, $this->getRequestTags()); $cacheInstance->save( $currentStorageSize + $contentSize, self::CACHE_SIZE_KEY, $this->getRequestTags() ); /* * Save design change in cache */ $designChange = Mage::getSingleton('core/design'); if ($designChange->getData()) { $cacheInstance->save( serialize($designChange->getData()), $this->getRequestCacheId() . self::DESIGN_CHANGE_CACHE_SUFFIX, $this->getRequestTags() ); } // save response headers $this->setMetadata('response_headers', $response->getHeaders()); // save original routing info $this->setMetadata('routing_aliases', Mage::app()->getRequest()->getAliases()); $this->setMetadata('routing_requested_route', Mage::app()->getRequest()->getRequestedRouteName()); $this->setMetadata('routing_requested_controller', Mage::app()->getRequest()->getRequestedControllerName()); $this->setMetadata('routing_requested_action', Mage::app()->getRequest()->getRequestedActionName()); $this->setMetadata('sid_cookie_name', Mage::getSingleton('core/session')->getSessionName()); Mage::dispatchEvent('pagecache_processor_metadata_before_save', array('processor' => $this)); $this->_saveMetadata(); } if (isset($_GET[Mage_Core_Model_Session_Abstract::SESSION_ID_QUERY_PARAM])) { Mage::getSingleton('enterprise_pagecache/cookie')->updateCustomerCookies(); Mage::getModel('enterprise_pagecache/observer')->updateCustomerProductIndex(); } } return $this; } 


Here, a very important point is that before storing the page cache, the contents of the blocks with the placeholder are replaced with the block description: $ content = $ processor-> prepareContent ($ response) ;. PrepareContent method:

 /** * Prepare response body before caching * * @param Zend_Controller_Response_Http $response * @return string */ public function prepareContent(Zend_Controller_Response_Http $response) { return $this->replaceContentToPlaceholderReplacer($response->getBody()); } 


The replaceContentToPlaceholderReplacer method:

 /** * Replace block content to placeholder replacer * * @param string $content * @return string */ public function replaceContentToPlaceholderReplacer($content) { $placeholders = array(); preg_match_all( Enterprise_PageCache_Model_Container_Placeholder::HTML_NAME_PATTERN, $content, $placeholders, PREG_PATTERN_ORDER ); $placeholders = array_unique($placeholders[1]); try { foreach ($placeholders as $definition) { $this->_placeholder = Mage::getModel('enterprise_pagecache/container_placeholder', $definition); $content = preg_replace_callback($this->_placeholder->getPattern(), array($this, '_getPlaceholderReplacer'), $content); } $this->_placeholder = null; } catch (Exception $e) { $this->_placeholder = null; throw $e; } return $content; } 


As a result, the page cache will contain information about the block instead of the block contents. This information will help to render the block in the future. Here is an example of such information that FPC will replace with the block contents during its rendering:

 < !--{POPUP container="Turnkeye_Popup_Model_PageCache_Container_Popup" block="Turnkeye_Popup_Block_Popup" cache_id="c3b32091a1ebd3b276a8fd70496a8e6da20865d0" cache_lifetime="86400" template="turnkeye/popup/popup.phtml" handles="a:2:{i:0;s:15:"cms_index_index";i:1;s:8:"cms_page";}" customer_segment_ids="a:1:{i:0;i:0;}" popup_ids="a:1:{i:0;s:1:"1";}" excluded_popup_ids="a:1:{i:0;i:1;}"}--> 


The next time this page is loaded, Magento will load this page from the cache (see the EnterpriseConageCache_Model_Processor class extractContent method). If the cache is empty - the usual rendering with the initialization of Magento (as if the FPC was not), followed by saving the page to the cache. If the page is in the cache, the FPC will process the content from this cache.

The _processContent and _processContainers methods of the Enterprise_PageCache_Model_Processor class:

 /** * Determine and process all defined containers. * Direct request to pagecache/request/process action if necessary for additional processing * * @param string $content * @return string|false */ protected function _processContent($content) { $containers = $this->_processContainers($content); $isProcessed = empty($containers); // renew session cookie $sessionInfo = Enterprise_PageCache_Model_Cache::getCacheInstance()->load($this->getSessionInfoCacheId()); if ($sessionInfo) { $sessionInfo = unserialize($sessionInfo); foreach ($sessionInfo as $cookieName => $cookieInfo) { if (isset($_COOKIE[$cookieName]) && isset($cookieInfo['lifetime']) && isset($cookieInfo['path']) && isset($cookieInfo['domain']) && isset($cookieInfo['secure']) && isset($cookieInfo['httponly']) ) { $lifeTime = (0 == $cookieInfo['lifetime']) ? 0 : time() + $cookieInfo['lifetime']; setcookie($cookieName, $_COOKIE[$cookieName], $lifeTime, $cookieInfo['path'], $cookieInfo['domain'], $cookieInfo['secure'], $cookieInfo['httponly'] ); } } } else { $isProcessed = false; } if (isset($_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_FORM_KEY])) { $formKey = $_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_FORM_KEY]; } else { $formKey = Enterprise_PageCache_Helper_Data::getRandomString(16); Enterprise_PageCache_Model_Cookie::setFormKeyCookieValue($formKey); } Enterprise_PageCache_Helper_Form_Key::restoreFormKey($content, $formKey); /** * restore session_id in content whether content is completely processed or not */ $sidCookieName = $this->getMetadata('sid_cookie_name'); $sidCookieValue = $sidCookieName && isset($_COOKIE[$sidCookieName]) ? $_COOKIE[$sidCookieName] : ''; // XSS vulnerability protection provided by htmlspcialchars call - escape & " ' < > chars Enterprise_PageCache_Helper_Url::restoreSid($content, htmlspecialchars($sidCookieValue, ENT_QUOTES)); if ($isProcessed) { return $content; } else { Mage::register('cached_page_content', $content); Mage::register('cached_page_containers', $containers); Mage::app()->getRequest() ->setModuleName('pagecache') ->setControllerName('request') ->setActionName('process') ->isStraight(true); // restore original routing info $routingInfo = array( 'aliases' => $this->getMetadata('routing_aliases'), 'requested_route' => $this->getMetadata('routing_requested_route'), 'requested_controller' => $this->getMetadata('routing_requested_controller'), 'requested_action' => $this->getMetadata('routing_requested_action') ); Mage::app()->getRequest()->setRoutingInfo($routingInfo); return false; } } 


 /** * Process Containers * * @param $content * @return array */ protected function _processContainers(&$content) { $placeholders = array(); preg_match_all( Enterprise_PageCache_Model_Container_Placeholder::HTML_NAME_PATTERN, $content, $placeholders, PREG_PATTERN_ORDER ); $placeholders = array_unique($placeholders[1]); $containers = array(); foreach ($placeholders as $definition) { $placeholder = new Enterprise_PageCache_Model_Container_Placeholder($definition); $container = $placeholder->getContainerClass(); if (!$container) { continue; } $container = new $container($placeholder); $container->setProcessor($this); if (!$container->applyWithoutApp($content)) { $containers[] = $container; } else { preg_match($placeholder->getPattern(), $content, $matches); if (array_key_exists(1,$matches)) { $containers = array_merge($this->_processContainers($matches[1]), $containers); $content = preg_replace($placeholder->getPattern(), str_replace('$', '\\$', $matches[1]), $content); } } } return $containers; } 


As can be seen from the code of these methods, only blocks that have placeholders are processed. If there is no placeholder for your block, its contents will not change. Therefore, if you want to display dynamically changing blocks, then you need to create placeholders for them.

Placeholder containers are first processed by the applyWithoutApp method. This method attempts to replace the block contents in the cached page with the content we need. An example of the applyWithoutApp method abstract for all containers of the class Enterprise_PageCache_Model_Container_Abstract:

 /** * Generate placeholder content before application was initialized and apply to page content if possible * * @param string $content * @return bool */ public function applyWithoutApp(&$content) { $cacheId = $this->_getCacheId(); if ($cacheId === false) { $this->_applyToContent($content, ''); return true; } $block = $this->_loadCache($cacheId); if ($block === false) { return false; } $block = Enterprise_PageCache_Helper_Url::replaceUenc($block); $this->_applyToContent($content, $block); return true; } 


From the code it is clear that if you want to change the block dynamically, you need to define the container _getCacheId method based on the logic of your block. If at least one container was not processed (applyWithoutApp returned false), then it must be rendered (see the _processContent method of the Enterprise_PageCache_Model_Processor class), and Magento will be initialized (Mage :: app () will execute). In this case, the request is processed by the Enterprise_PageCache_RequestController controller by the action process. Process action code:

 /** * Request processing action */ public function processAction() { $processor = Mage::getSingleton('enterprise_pagecache/processor'); $content = Mage::registry('cached_page_content'); $containers = Mage::registry('cached_page_containers'); $cacheInstance = Enterprise_PageCache_Model_Cache::getCacheInstance(); foreach ($containers as $container) { $container->applyInApp($content); } $this->getResponse()->appendBody($content); // save session cookie lifetime info $cacheId = $processor->getSessionInfoCacheId(); $sessionInfo = $cacheInstance->load($cacheId); if ($sessionInfo) { $sessionInfo = unserialize($sessionInfo); } else { $sessionInfo = array(); } $session = Mage::getSingleton('core/session'); $cookieName = $session->getSessionName(); $cookieInfo = array( 'lifetime' => $session->getCookie()->getLifetime(), 'path' => $session->getCookie()->getPath(), 'domain' => $session->getCookie()->getDomain(), 'secure' => $session->getCookie()->isSecure(), 'httponly' => $session->getCookie()->getHttponly(), ); if (!isset($sessionInfo[$cookieName]) || $sessionInfo[$cookieName] != $cookieInfo) { $sessionInfo[$cookieName] = $cookieInfo; // customer cookies have to be refreshed as well as the session cookie $sessionInfo[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER] = $cookieInfo; $sessionInfo[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER_GROUP] = $cookieInfo; $sessionInfo[Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER_LOGGED_IN] = $cookieInfo; $sessionInfo[Enterprise_PageCache_Model_Cookie::CUSTOMER_SEGMENT_IDS] = $cookieInfo; $sessionInfo[Enterprise_PageCache_Model_Cookie::COOKIE_MESSAGE] = $cookieInfo; $sessionInfo = serialize($sessionInfo); $cacheInstance->save($sessionInfo, $cacheId, array(Enterprise_PageCache_Model_Processor::CACHE_TAG)); } } 


For each unprocessed container, the applyInApp method is run, which must render the block and replace the contents of the placeholder in the content. The code of the applyInApp method is abstract for containers of the class Enterprise_PageCache_Model_Container_Abstract:

 /** * Generate and apply container content in controller after application is initialized * * @param string $content * @return bool */ public function applyInApp(&$content) { $blockContent = $this->_renderBlock(); if ($blockContent === false) { return false; } if (Mage::getStoreConfig(Enterprise_PageCache_Model_Processor::XML_PATH_CACHE_DEBUG)) { $debugBlock = new Enterprise_PageCache_Block_Debug(); $debugBlock->setDynamicBlockContent($blockContent); $debugBlock->setTags($this->_getPlaceHolderBlock()->getCacheTags()); $debugBlock->setType($this->_placeholder->getName()); $this->_applyToContent($content, $debugBlock->toHtml()); } else { $this->_applyToContent($content, $blockContent); } $subprocessor = $this->_processor->getSubprocessor(); if ($subprocessor) { $contentWithoutNestedBlocks = $subprocessor->replaceContentToPlaceholderReplacer($blockContent); $this->saveCache($contentWithoutNestedBlocks); } return true; } 


Here, as we see, the content is obtained by the _renderBlock method of the container class. This method is usually unique for each container and contains some special logic. In the abstract class Enterprise_PageCache_Model_Container_Abstract for containers, this method returns false. It should return the HTML content of the block.

However, there is a problem with the rendering of containers. For the container block, the parent could not be rendered, and the controller is executed by another. This means that some data inside the block may not be present and this case must be considered (for example, you get the product in this way: $ product = Mage :: registry ('current_product'), or you set a property by the parent block: $ this-> getChild ('block_alias') -> setProduct ($ _ product)).

But, as we remember, FPC saves all the information necessary for rendering. Therefore, if you passed the information in the getCacheKeyInfo method of your block to be saved, you can set it during FPC rendering. This should be done in the container _renderBlock method. You can get a block instance in it using the _getPlaceHolderBlock method:

 /** * Get Placeholder Block * * @return Mage_Core_Block_Abstract */ protected function _getPlaceHolderBlock() { if (null === $this->_placeholderBlock) { $blockName = $this->_placeholder->getAttribute('block'); $this->_placeholderBlock = new $blockName; $this->_placeholderBlock->setTemplate($this->_placeholder->getAttribute('template')); $this->_placeholderBlock->setLayout(Mage::app()->getLayout()); $this->_placeholderBlock->setSkipRenderTag(true); } return $this->_placeholderBlock; } 


As you can see, we get an instance of the block class with the template set. An example of the _renderBlock method:

 /** * Render block content from placeholder * * @return string|false */ protected function _renderBlock() { /** * @var $block Turnkeye_Popup_Block_Popup */ $block = $this->_getPlaceHolderBlock(); $placeholder = $this->_placeholder; $serializedParameters = array('handles', 'popup_ids'); foreach ($serializedParameters as $parameter) { $value = unserialize($placeholder->getAttribute($parameter)); $block->setDataUsingMethod($parameter, $value); } return $block->toHtml(); } 


Here, as can be seen from the code, we passed the parameters handles and popup_ids to the block. In the block itself, they must be received like this:

 public function getPopupIds() { if (!$this->hasData('popup_ids')) { $popupIds = ... ... $this->setData('popup_ids', $popupIds); } return $this->getData('popup_ids'); } 


I hope this article opened the veil of secrecy over the FPC functionality, and you learned how the FPC mechanism works from the inside.

The cache is really efficient and works great, reducing the load on the server and ensuring fast operation of the online store with high attendance.

FPC , Magento Enterprise FPC.

Source: https://habr.com/ru/post/214645/


All Articles