Skip to content

Published by

The LEADLION team

Published on

Featured image for Customer session and Full Page Cache, Magento 2
Magento 2

Customer session and Full Page Cache, Magento 2

Public and private content

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

  • Public, which can be displayed to many customers

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

  • Private, which is not stored in the Magento server cache; instead, it is 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 on public pages?

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

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 clearing the session and the data storage, it checks whether the layout needs to be depersonalised:

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

This check is performed in 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 this is the place where my customer session was being cleared after I switched to production mode.

Don’t use cacheable=‘false’!

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

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

This is not good practice, though: a single element with the cacheable='false' attribute disables Full Page Cache for the whole page, and loading time increases drastically. Time to prove it.

We will start from magento/framework/View/Layout.php and its renderElement function. This is where the layout elements are rendered and returned as string output, and an event is dispatched along the way: 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]
 );
 ……
}

In module-page-cache, this event is listened to 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>

Before the ProcessLayoutRenderElement observer renders the elements from Full Page Cache, it checks that 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 isCacheable() method in magento/framework/View/Layout.php to check whether the layout contains any non-cacheable elements.

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 any of those cacheable='false' elements is found, the entire page is labelled NON-CACHEABLE and Full Page Cache is disabled for it completely.

Here’s the solution

Below I present the solution and the important files added to the module, skipping the basic module configuration.

  1. Create Leadlion\CustomerInfo\CustomerData\CustomInfo.php, which implements Magento\Customer\CustomerData\SectionSourceInterface. This class needs to implement the getSectionData() method and must return an array with the 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 a UI component that will be responsible for rendering the block 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 that display user-agnostic data; the UI component then replaces this data with user-specific data. 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 the actions that trigger cache invalidation for private content blocks in a sections.xml configuration file, 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>

With all this in place, I cleared the cache and saw my customer ID displayed on the category page. The customer ID was also saved in the browser’s Local Storage under mage-cache-storage:

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