Magento – Saving Data Directly into Magento Cache

Magento uses many cache types out of the box; being very convenient for both administrators and developers, due to its decentralized design that provides specialized caching for different purposes.

When using Magento, its architecture will manage and determine the best cache type and policy to use for every particular situation; providing a more powerful solution compared with other frameworks that only provide single cache storage.

Magento is also flexible enough to allow you as a developer to implement and utilize a custom cache for any specific purpose. The process is very simple for any developer; in the following examples let’s suppose that we are working with an existent module called Astralweb_Cache. The basic procedure for using Magento cache would include the following procedures:

  1. Create a cache XML file

Create a new file in AstralWeb/Cache/etc/cache.xml with the following content:

<?xml version=”1.0″?>

<config xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=”urn:magento:framework:Cache/etc/cache.xsd”>

    <type name=”astralweb_cache” translate=”label,description” instance=”AstralWeb\Cache\Model\Cache\AstralWebCache”>

        <label>AstralWeb Cache</label>

        <description>AstralWeb Custom Cache</description>

    </type>

</config>

In which:

name: This is the unique and internal cache code to be used for identifying the cache. 

instance: The PHP class will be responsible for constructing the custom cache type.

  1. Create the class AstralWen\Cache\Model\Cache\AstralWebCache

<?php

namespace AstralWeb\Cache\Model\Cache;

use Magento\Framework\App\Cache\Type\FrontendPool;

use Magento\Framework\Cache\Frontend\Decorator\TagScope;

/**

 * System / Cache Management / Cache “Label”

 */

class AstralWebCache extends TagScope

{

    /**

     * The unique and internal code to be used for identifying the cache.

     */

    const TYPE_IDENTIFIER = ‘astralweb_cache’;

    /**

     * The tag name used for identifying the cache scope

     */

    const CACHE_TAG = ‘ASTRALWEB_CACHE_TAG’;

    /**

     * @param FrontendPool $cacheFrontendPool

     */

    public function __construct(FrontendPool $cacheFrontendPool)

    {

        parent::__construct(

            $cacheFrontendPool->get(self::TYPE_IDENTIFIER),

            self::CACHE_TAG

        );

    }

}

After that you will be able to see AstralWeb Cache in System → Cache Management, meaning that the cache has been successfully created.

  1. Using the custom cache

After creating the custom cache the most common way to insert data into it is by injection into almost any class that requires inserting data.

use Magento\Framework\App\CacheInterface;

use Magento\Framework\Serialize\SerializerInterface;

use Magento\Framework\App\Cache\TypeListInterface 

use AstralWeb\Cache\Model\Cache\AstralWebCache;

….

….

….

protected $_cache;

protected $_serializer;

protected $_cacheTypeList;

/**

 * @param CacheInterface $_cache

 * @param SerializerInterface $_serializer

 */

public function __construct(

CacheInterface $cache, 

SerializerInterface $serializer,

CacheTypeList $cacheTypeList

)

{

    $this->_cache = $cache;

    $this->_serializer = $serializer;

    $this->_cacheTypeList = $cacheTypeList;

}

….

….

….

  1. Saving data into the custom cache

The following example illustrates how to insert custom data into the cache. You should notice that the data, or object, to be saved is serialized into a string.

public function saveToCache($dataToSave)

{

    $cacheKey  = AstralWebCache::TYPE_IDENTIFIER;

    $cacheTag  = AstralWebCache::CACHE_TAG;

    $storeData = $this->_cache->save(

        $this->_serializer->serialize($dataToSave), // Serialization

        $cacheKey,

        [$cacheTag],

        86400 // lifetime of the cached data (seconds)

    );

}

  1. Reading data from the custom cache

Reading data from custom cache is exactly the opposite process. You should note that the data or object will be unserialized.

public function readFromCache()

{

    $cacheKey  = AstralWebCache::TYPE_IDENTIFIER;

    $data = $this->_serializer->unserialize($this->_cache->load($cacheKey));

}

Please note that the data from the custom cache will be accessible until it reaches lifetime and before the Administrator flushes the cache. Also, if the saved data has been re-saved with different data, it will not be retrieved correctly until the cache is flushed.

  1. Invalidating the custom cache

If after re-saving the cache with different data you would like to display the cache as “invalid” to the Administrator you need to execute the following code.

public function invalidateCache()

{

    $this->_cacheTypeList->invalidate(AstralWebCache::TYPE_IDENTIFIER);

}

  1. Flushing the custom cache type

You can flush the cache manually on System → Cache Management, but you can also flush the cache programmatically as this:

public function flushCache()

{

    $this->_cacheTypeList->cleanType(AstralWebCache::TYPE_IDENTIFIER);

}

The only disadvantage of using Magento cache for your own cache is that you will not be able to configure it according to your needs, or you might not be able to reduce the abstraction layers you would like for simplifying the process even more.

However, the above approach will allow you to provide a faster response by using Magento internal cache mechanism. Instead of fetching from the databases, the requested data will be returned from the cache.

A Simple Explanation of Cache

If this is the first article you read about Cache, congratulations, you are lucky you find this simple and straightforward information about it, more specifically about Web caching.

Cache can be implemented by Software or Hardware, it doesn’t matter, it is an optimized storage for delivering fast content between you PC and the Internet content you are browsing. 

When you visit your favorite page, most of the content is not coming from the server, it is coming from the cache, because the cache is already stored from the remote server the first time you visit the page.  

If the page loads from the server every time you visit your page, it will cause bottlenecks somewhere in the “road”, especially if the page is visited at the same time by many people.  This is why it is more likely you will have the impression that your favorite page loads faster. Nobody likes slow websites, and this is why cache is necessary.

During every connection between a device and a server more than one cache can be used during the connectivity, even your browser implements a cache, sometimes called proxy.

But how does a basic cache work? Assuming the most basic scenario, a cache might works as follows:

When you visit a page for the first time, the response time will always be the slowest one, because the response is being served directly by the server; however a copy of the response data is stored in the cache.

After the first time the page is visited the client request will be served primarily from the cache. If there is data needed to be updated it will be brought directly from the server and a copy of it will be stored in cache. This approach will provide a faster response and reduce server traffic.

However, you might be asking yourself some questions: how does the cache determine when to refresh a specific content?  How to assure that the data display is always the most accurate data?

The above is being determined by cache policies; that are basically rules-algorithms that manage a cache; usually being a combination of several rules as for example: last written time, last access time, less access data, etc. Only the browser itself utilices a combination of very complex policies, aiming to provide faster page loads.  

As a general conclusion, it can be assumed that your devices accessing and requesting info from the Internet utilize a cache mechanism. Most of the time you don’t need to take it, it is being implemented out of the box; however in some complex scenarios this cache implementation and/or policies would require to be adjusted in order to provide a better customer experience; being this task more complex every day since technology trends are evolving constantly.

Configuring Cron Job Groups for Magento

There are many articles on the web regarding how to configure cron job groups in Magento; however, it is very hard to express the concepts by using just words; leading to confusion among developers when dealing with such an important task on any merchant website, for this reason this article is aiming to explain in a more graphical way the key concepts of setting a cron job in Magento

Assuming that the one more cron jobs are already defined under a cron job group, the next step is to create the cron_groups.xml file for that specific cron group. 

We might take the following configuration as example:

<group id=”astralweb”>

   <schedule_generate_every>10</schedule_generate_every>

   <schedule_ahead_for>40</schedule_ahead_for>

   <schedule_lifetime>2</schedule_lifetime>

   <history_cleanup_every>10</history_cleanup_every>

   <history_success_lifetime>60</history_success_lifetime>

   <history_failure_lifetime>600</history_failure_lifetime>

   <use_separate_process>1</use_separate_process>

</group>

The group id is the name of the group to be configures, “astralweb” in this case; the following couple of lines are the most confusing for most of developers.

<schedule_generate_every>10</schedule_generate_every>

<schedule_ahead_for>40</schedule_ahead_for>

Schedule generate every: In minutes, specifying how often the cron jobs will be triggered. Meaning in this case that every 10 minutes a new job will be executed.

Schedule ahead for: In minutes, specifying the max range in time to run/schedule the max amount of cron jobs.

What does all of these mean? The above description can be considered as a division as follows:

By following the same example we can check how many consecutive jobs (of a single job) we can run:

In the timeline, this batch can be represented as follows:

What happened here?

  • The first cron job is executed immediately.
  • Jobs 2,3,4 are scheduled to be run.
  • 1 executed + 3 scheduled = 4 jobs in total.
  • This will be the same behavior for all jobs running in the same group.

However, Magento will wait for the last job of this batch (job 4) before creating another batch, so in timeline the behavior would be something like this:

After executing the last job (job 4), a new batch will be created as follows:

After watching the timelines above you must be sure that “schedule ahead for” is always greater than “schedule generate every”, otherwise, the cron job will run only once, since the ratio would not be enough for scheduling more than one job.   

Finally, after explaining the relationship between “schedule generate every” and “schedule ahead for”, the the rest of the settings are self explanatory:

<schedule_lifetime>2</schedule_lifetime>

<history_cleanup_every>10</history_cleanup_every>

<history_success_lifetime>60</history_success_lifetime>

<history_failure_lifetime>600</history_failure_lifetime>

<use_separate_process>1</use_separate_process>

Schedule Lifetime: In minutes. This is the time for the job to start running. Meaning in this case that our cron jobs will have 1:59 minutes to start running when the time reaches. If after this time the job has not started to run it will be considered as missed.

History Cleanup Every: In minutes; configuring the time the cron job will be recorded in the database.

History Success Lifetime: In minutes; configuring the time the cron job will be recorded in the database after a successful execution.

History Failure Lifetime: In minutes; configuring the time the cron job will be recorded in the database after a failed execution.

Use separate process: 1 for running in a separate PHP process; 0 otherwise. In theory, running in separate processes would avoid the need to accidentally consume unresponsive or idle processes.

As we can see the key for running a successful cron group is the correct relationship between “schedule generate every” and “schedule ahead for”; by fully understanding how affects the cron behavior the developers would reduce any issue caused by misunderstandings on setting any particular cron group.

Magento – Bypassing Fastly cache on specific page

The most important feature of any CDN/Cache server/provider is to reduce loading speed by delivering the requested content to a particular user, in a closer location; instead of serving the content from the original server, at a larger distance.

This is exactly one of the main goals of Fastly, that is specially optimized to be used with Magento; however there might be certain requirements, on a certain page, in which you are forced to bring all the newest data from the original server (remote server), for ensuring only the new data is served. Unfortunately, disabling Fastly Cache/CDN is not an option, because the server speed will be downgraded; so how to serve non-cacheable data only without affecting performance? 

Luckily, Magento and Fastly provide a way for bypassing Fastly cache for only specific pages. This means that Fastly will still work normally for the whole site except for the pages you decide not to cache.

The above is easily configured in

Stores->Configuration->Advanced->System->Full Page Cache

If Fastly is configured in your server you should see “Fastly CDN” after expanding “Full Page Cache”. Then on Fastly Configuration->Custom VCL Snippets, click on Create Custom Snippet and create a similar snippet as follows:

Set the Name, Type and Priority as your own discretion. The key field is the VCL, in which in this example we set it as follows:

if (req.url ~ “redeximp/import/redimport”) {  return (pass);}

The above snippet means that in case the user is accessing the web path redeximp/import/redimport Fastly cache should be bypassed (skipped). 

Finally, after saving, in Automatic Upload & Service Activation click on Upload VCL to Fastly for applying the changes.

And that will be all; you will have more control on how Faslty behaves in your store, providing more tools for providing a better developer/user experience.

Magento Page Builder – Fix Warning on Save

Since version 2.4.3, Magento includes the Page Builder module in both Commerce and Community Editions, empowering merchants to create rich content by themselves or allowing them to quickly design quick mockups for testing before escalating to any development team.

By making Page Builder open to all editions Magento is encouraging developers to extend the Page Builder functions, sometimes forcing developers to explore deeper into the code when the extended function or behavior differs from the original.

It might be possible that after working with Page Builder the following message is displayed after saving some element that contains a Page Builder content:

This message of “Temporarily allowed to save HTML value that contains restricted elements. Allowed HTML tags are:…” represents a warning message, not an error. The Page Builder is working normally, but the validator is telling us to be careful with content that is not expected by the validator, located in Magento\Cms\Model\Wysiwyg\Validator in the following line

$this->config->isSetFlag(self::CONFIG_PATH_THROW_EXCEPTION);

try {

   $this->validator->validate($content);

} catch (ValidationException $exception) {

How to fix the warning message?

Step 1:

Log the $content variable, check what kind of html content is saving and take note of all the div and attributes elements being used.

Step 2:

On your module vendor/module/etc/di.xml create a Virtual Type called DefaultWYSIWYGValidator and provide all the allowed tags and attributes used by your Magento app:

<virtualType name=”DefaultWYSIWYGValidator” type=”Magento\Framework\Validator\HTML\ConfigurableWYSIWYGValidator”>

   <arguments>

       <argument name=”allowedTags” xsi:type=”array”>

           <item name=”div” xsi:type=”string”>div</item>

           <item name=”a” xsi:type=”string”>a</item>

           …

           <item name=”b” xsi:type=”string”>b</item>

       </argument>

       <argument name=”allowedAttributes” xsi:type=”array”>

           <item name=”class” xsi:type=”string”>class</item>

           …

           <item name=”id” xsi:type=”string”>id</item>

       </argument>

       <argument name=“attributesAllowedByTags” xsi:type=”array”>

           <item name=”div” xsi:type=”array”>

               <item name=”data-content-type” xsi:type=”string”>data-content-type</item>

              …

               <item name=”data-content” xsi:type=”string”>data-content</item>

           </item>

           <item name=”ul” xsi:type=”array”>

               …

       <argument name=”attributeValidators” xsi:type=”array”>

           <item name=”style” xsi:type=”object”>Magento\Framework\Validator\HTML\StyleAttributeValidator</item>

       </argument>

   </arguments>

</virtualType>

Step 3:

Refresh cache

bin/magento cache:flush

After this the warning message will not be displayed anymore because the validator is already recognizing all the possible html elements introduced by the new changes. Improving the general experience of using Page Builder.