Sari la continut

Publicat de

Echipa LEADLION

Data publicarii

Imagine reprezentativa pentru Customer session and Full Page Cache, Magento 2

Customer session and Full Page Cache, Magento 2

Public and private content

As Magento 2 documentation pointed out, there are two types of content, public and private:

  • Public, which can display to many customers

Public content is stored in your cache storage (file system, database, or Redis), or by Varnish. Examples of public content includes the header, footer, and category listing.

  • Private, which is not stored in the Magento server cache; instead, it’s stored on the client only.

Examples of private content include the wishlist, shopping cart, customer name, and addresses. Private content should be limited to a small portion of the total content on a page.

What’s happening with the customer session in the public pages ?

In the generateXml() method from magento/framework/View/Layout.php the layout is generated. This process is accompanied by the Magento\Customer\Model\Layout\DepersonalizePlugin.php which, in the afterGenerateXml method, performs the cleaning of the customer session.

Magento\Customer\Model\Layout\DepersonalizePlugin.php

public function afterGenerateXml(\Magento\Framework\View\LayoutInterface $subject, $result)
{
 if ($this->depersonalizeChecker->checkIfDepersonalize($subject)) {
 $this->visitor->setSkipRequestLogging(true);
 $this->visitor->unsetData();
 $this->session->clearStorage();
 $this->customerSession->clearStorage();
 $this->session->setData(\Magento\Framework\Data\Form\FormKey::FORM_KEY, $this->formKey);
 $this->customerSession->setCustomerGroupId($this->customerGroupId);
 $this->customerSession->setCustomer($this->customerFactory->create()->setGroupId($this->customerGroupId));
 }
 return $result;
}

As you can see, before cleaning the session and data storage, it is performing a check to see if the layout needs to be depersonalized:

if ($this->depersonalizeChecker->checkIfDepersonalize($subject)) {…}

This check if performed in the Magento\PageCache\Model\DepersonalizeChecker.php:

Magento\PageCache\Model\DepersonalizeChecker.php

public function checkIfDepersonalize(\Magento\Framework\View\LayoutInterface $subject)
{
 return ($this->moduleManager->isEnabled('Magento_PageCache')
 && $this->cacheConfig->isEnabled()
 && !$this->request->isAjax()
 && ($this->request->isGet() || $this->request->isHead())
 && $subject->isCacheable());
}

So, here is the place where my customer session has been cleared, after I activated the production mode.

Don’t use cacheable=‘false’ !

You have the choice to declare blocks as non-cacheable by setting the cacheable attribute to false in the layout XML files.

<block class="Class" name="blockname" cacheable="false" />

But, it’s not a good practice because a single element with cacheable='false' attribute will disable the Full Page Cache functionality for the whole page and time loading will be increased drastically. It’s time to prove it.

We’ll start from magento/framework/View/Layout.php with function renderElement. Here, the elements from layout are rendered and returned as string output, also an event being dispatched: core_layout_render_element.

magento/framework/View/Layout.php

public function renderElement($name, $useCache = true)
{
 ……….
 $this->_eventManager->dispatch(
 'core_layout_render_element',
 ['element_name' => $name, 'layout' => $this, 'transport' => $this->_renderingOutput]
 );
 ……
}

This event is listened in the module-page-cache by the Magento\PageCache\Observer\ProcessLayoutRenderElement observer.

module-page-cache/etc/events.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
  <event name="core_layout_render_element">
 <observer name="pagecache" instance="Magento\PageCache\Observer\ProcessLayoutRenderElement" />
 </event>
……………….
</config>

Now, in the ProcessLayoutRenderElement observer, before render the elements from Full Page Cache, is performed a check to see if the Full Page Cache is enabled and the block is cacheable.

module-page-cache/Observer/ProcessLayoutRenderElement

public function execute(\Magento\Framework\Event\Observer $observer)
{
 $event = $observer->getEvent();
 /** @var \Magento\Framework\View\Layout $layout */
 $layout = $event->getLayout();
 if ($this->isFullPageCacheEnabled() && $layout->isCacheable()) {
//render elements from FPC
}
…..
}

The second condition, $layout->isCacheable(), calls the method isCacheable() from magento/framework/View/Layout.php, to check if there exist non-cacheable elements in the layout.

magento/framework/View/Layout.php

public function isCacheable()
{
 $this->build();
 $cacheableXml = !(bool)count($this->getXml()->xpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]'));
 return $this->cacheable && $cacheableXml;
}

If one of those cacheable='false' elements is found, the entire page will be labeled as NON-CACHEABLE and the entire functionality of Full Page Cache will be disabled.

Here’s the solution

In the following section, I’m going to present the solution and the important files that have been added to the module and I’ll skip the basic module configuration.

  1. Create Leadlion\CustomerInfo\CustomerData\CustomInfo.php which implements Magento\Customer\CustomerData\SectionSourceInterface. This class needs to implement the method getSectionData() and must return an array with data for the private block.
<?php
namespace Leadlion\CustomerInfo\CustomerData;

use Magento\Customer\CustomerData\SectionSourceInterface;

class CustomInfo implements SectionSourceInterface
{

  protected $session;


  public function __construct(
 \Magento\Customer\Model\Session $customerSession
 ) {
 $this->session = $customerSession;
 }

 public function getSectionData()
 {
 return [
 'customerId' => $this->session->getCustomerId()
 ];
 }
}
  1. Create Leadlion\CustomerInfo\etc\frontend\di.xml
<?xml version="1.0"?>


<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
 <type name="Magento\Customer\CustomerData\SectionPoolInterface">
 <arguments>
 <argument name="sectionSourceMap" xsi:type="array">
 <item name="custom-info" xsi:type="string">Leadlion\CustomerInfo\CustomerData\CustomInfo</item>
 </argument>
 </arguments>
 </type>
</config>
  1. Create an UI component which will be responsible for rendering blocks data on the storefront. Leadlion\CustomerInfo\view\frontend\web\js\view\custom-info.js
define([
  'uiComponent',
 'Magento_Customer/js/customer-data'
], function (Component, customerData) {
 'use strict';

 return Component.extend({
 initialize: function () {
 this._super();
 this.customInfo = customerData.get('custom-info');
 }
 });
});
  1. To render private content, create a block and a template to display user-agnostic data; this data is replaced with user-specific data by the UI component. Leadlion\CustomerInfo\view\frontend\templates\product\list.phtml
……………………….
<li data-bind="scope: 'customInfo'" data-role="custom-information">
 <p data-bind="text: customInfo().customerId"> </p>
</li>
<script type="text/x-magento-init">
{"[data-role=custom-information]": {"Magento_Ui/js/core/app": <?php /** *@escapeNotVerified */ echo $block->getJsLayout();?>}}
</script>

Leadlion\CustomerInfo\view\frontend\layout\catalog_category_view.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
 <body>
 <referenceBlock name="category.products.list" >
 <action method="setTemplate">
 <argument name="template" xsi:type="string">Leadlion_CustomerInfo::product/list.phtml</argument>
 </action>
 <arguments>
 <argument name="jsLayout" xsi:type="array">
 <item name="components" xsi:type="array">
 <item name="customInfo" xsi:type="array">
 <item name="component" xsi:type="string">Leadlion_CustomerInfo/js/view/custom-info</item>
 </item>
 </item>
 </argument>
 </arguments>
 </referenceBlock>
 </body>
</page>
  1. Invalidate private content. Specify actions that trigger cache invalidation for private content blocks in a sections.xml configuration file in the Leadlion\CustomerInfo\etc\frontend\section.xml.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
   <action name="catalog/category/view">
         <section name="custom-info"/>
     </action>
</config>

After all this work, finally, I cleared the cache and I saw my customer id displayed on the category page. Also, customer id has been saved in the browser Local Storage under mage-cache-storage:

"custom-info":{
 "customerId":"2",
 "data_id":1488332248
 }