Magento:如果小部件参数发生变化,则从块/全页缓存中清除小部件


Magento: Flush widget from block / fullpage cache if widget parameters change

我们商店的首页上有一个小部件。

商店使用FPC和块缓存。

如果管理员在后台更改小部件设置,我们如何更新小部件内容?

是否可以在CacheKey中使用Widget的配置数据?

或者我们必须将缓存寿命设置得很小?

编辑:我自己的答案使用缓存生存期。只有在编辑小部件实例时,才能有针对性地从FPC中清除缓存块的方法是什么?

EE全页缓存和小部件实例缓存

(无需重写,缓存寿命无限制)

概述

第一步是创建前端小部件本身,但提供一些完整页面缓存可以使用的附加信息
第二步是处理小部件的整个页面缓存中的打孔
第三步是仅当该小部件在管理区域中发生更改时,才自动清理该小部件的缓存。

第一步:创建小部件

首先,为您的模块创建etc/widget.xml文件:

<widgets>
    <netzarbeiter_test type="netzarbeiter_test/widget_test">
        <name>FPC Holepunch Cache Test</name>
        <description>Dummy test widget</description>
        <parameters>
            <!-- This is the important parameter here: -->
            <unique_id>
                <required>1</required>
            </unique_id>
            <example_text>
                <visible>1</visible>
                <label>Example Text Parameter</label>
                <type>text</type>
            </example_text>
        </parameters>
    </netzarbeiter_test>
</widgets>

请注意参数<unique_id>。我们不提供输入类型或值,它将通过Mage_Widget_Block_Adminhtml_Widget_Options::_addField()方法自动填充生成的值:

    // Excerpt from Mage_Widget_Block_Adminhtml_Widget_Options::_addField()
    if ($values = $this->getWidgetValues()) {
        $data['value'] = (isset($values[$fieldName]) ? $values[$fieldName] : '');
    }
    else {
        $data['value'] = $parameter->getValue();
        //prepare unique id value
        if ($fieldName == 'unique_id' && $data['value'] == '') {
            $data['value'] = md5(microtime(1));
        }
    }

因为有了这个小gem,我们不必重写Mage_Widget_Model_Widget_Instance类来将小部件id注入到生成的布局xml中。

接下来,创建小部件类本身。除了unique_id用于cacheKeyInfo方法之外,这只是一个实现Mage_Widget_Block_Interface接口的常规块实例。

class Netzarbeiter_Test_Block_Widget_Test
    extends Mage_Core_Block_Abstract
    implements Mage_Widget_Block_Interface
{
    public function getCacheKeyInfo()
    {
        $info = parent::getCacheKeyInfo();
        if ($id = $this->getData('unique_id')) {
            // Because the array key is a string, it will be added to the FPC placeholder
            // parameters. That is how the FPC container can access it (see below).
            $info['unique_id'] =  (string) $id;
        }
        return $info;
    }
    protected function _toHtml()
    {
        // The FPC was completely cleared (or not created yet), 
        // recreate the widget parameter cache
        if (! $this->getFullPageCacheEnvironment() && $this->getUniqueId()) {
            $id = Netzarbeiter_Test_Model_Fpc_Container_Widget_Test::CACHE_PREFIX . $this->getUniqueId() . '_params';
            Enterprise_PageCache_Model_Cache::getCacheInstance()->save(serialize($this->getData()), $id);
        }
        // Just some dummy output to display the text parameter and the render time
        $time = now();
        $textParam = $this->escapeHtml($this->getExampleText());
        return <<<EOF
<p><b>Render Time:</b> {$time}<br/>
<b>Example Text:</b> {$textParam}<br/></p>
EOF;
    }
}

当我们现在通过管理界面添加小部件实例时,widget.xml中指定的unique_id参数将添加到布局xml中(为可读性添加的格式:

SELECT layout_update_id, handle, xml FROM core_layout_update;
+------------------+--------------------------+----------------------------------------------------------------------------------------------------+
| layout_update_id | handle                   | xml                                                                                                |
+------------------+--------------------------+----------------------------------------------------------------------------------------------------+
|               17 | catalog_category_layered | <reference name="left">
|                  |                          |     <block type="netzarbeiter_test/widget_test" name="2a3b55e13549e176709fc6c67a4a7bd8">
|                  |                          |         <action method="setData">
|                  |                          |             <name>unique_id</name>
|                  |                          |             <value>7fa2574145ef204fb6d179cfc604ac76</value>
|                  |                          |         </action>
|                  |                          |         <action method="setData">
|                  |                          |             <name>example_text</name>
|                  |                          |             <value>This is a String!</value>
|                  |                          |         </action>
|                  |                          |     </block>
|                  |                          | </reference>
+------------------+--------------------------+----------------------------------------------------------------------------------------------------+

这意味着,无论何时通过布局xml渲染创建块实例,块对象上的unique_id参数都是已知的。

第二步:全页缓存打孔

添加etc/cache.xml文件:

<config>
    <placeholders>
        <cms_block_widget>
            <block>netzarbeiter_test/widget_test</block>
            <placeholder>WIDGET_FPC_TEST</placeholder>
            <container>Netzarbeiter_Test_Model_Fpc_Container_Widget_Test</container>
        </cms_block_widget>
    </placeholders>
</config>

接下来,创建容器类:

class Netzarbeiter_Test_Model_Fpc_Container_Widget_Test
    extends Enterprise_PageCache_Model_Container_Abstract
{
    const CACHE_PREFIX = 'TEST_WIDGET_';
    protected function _getCacheId()
    {
        return self::CACHE_PREFIX . $this->_placeholder->getAttribute('unique_id');
    }
    protected function _renderBlock()
    {
        $block = $this->_getPlaceHolderBlock();
        // Set any parameters from the placeholder on the block as needed.
        // See observer below where the current parameters are cached.
        $id = $this->_getCacheId() . '_params';
        if ($parameters = Enterprise_PageCache_Model_Cache::getCacheInstance()->load($id)) {
            $block->addData(unserialize($parameters));
            // Set environment information on block (used in _toHtml,
            // the params cache is recreated when not set)
            $block->setFullPageCacheEnvironment(true);
        }
        Mage::dispatchEvent('render_block', array('block' => $block, 'placeholder' => $this->_placeholder));
        return $block->toHtml();
    }
}

这就是使我们的小部件成为完全缓存页面中的动态块所需要的全部内容。但到目前为止,小部件块被无限期缓存。我们仍然需要处理缓存刷新。

第三步:小部件保存时的缓存刷新

为所有Magento模型提供的特定于小部件实例的自动*_save_commit_after事件使用事件观察器。每次在管理界面中保存小部件实例时都会触发它。

<adminhtml>
    <events>
        <widget_widget_instance_save_commit_after>
            <observers>
                <netzarbeiter_test>
                    <model>netzarbeiter_test/observer</model>
                    <method>widgetWidgetInstanceSaveCommitAfter</method>
                </netzarbeiter_test>
            </observers>
        </widget_widget_instance_save_commit_after>
    </events>
</adminhtml>

难题的最后一块是观察者方法:

public function widgetWidgetInstanceSaveCommitAfter(Varien_Event_Observer $observer)
{
    /** @var $widget Mage_Widget_Model_Widget_Instance */
    $widget = $observer->getEvent()->getObject();
    $parameters = $widget->getWidgetParameters();
    $uniqueId = isset($parameters['unique_id']) ? $parameters['unique_id'] : '';
    if (strlen($uniqueId)) {
        $id = Netzarbeiter_Test_Model_Fpc_Container_Widget_Test::CACHE_PREFIX . $uniqueId;
        Enterprise_PageCache_Model_Cache::getCacheInstance()->remove($id);
        $id = Netzarbeiter_Test_Model_Fpc_Container_Widget_Test::CACHE_PREFIX . $uniqueId . '_params';
        Enterprise_PageCache_Model_Cache::getCacheInstance()->save(serialize($parameters), $id);
    }
}

摘要

现在,前端的实例在每次保存时都会自动更新,即使有激活的全页缓存也是如此。在所有其他时间,动态块将从高速缓存中提取。

EDIT:在代码中添加了缓存的小部件参数示例,因为正如Alex在下面的评论中指出的那样,只要FPC条目存在,占位符参数就不会刷新
使用高速缓存将小部件参数传递到块,可以避免重写它的小部件实例模型,并避免在动态渲染块时加载小部件实例。

您可以实现方法getCacheKeyInfo,并使用一些特定于块的值来生成唯一的哈希。

在Magento EE中,您可以看到这一点,即在块Enterprise_Banner_Block_Widget_Banner

尝试下一种方法:

  1. 覆盖模块中的Enterprise_PageCache_Model_Validator
  2. Mage_Widget_Block_Interface添加到此类中的protected $_dataChangeDependency数组中。

  3. 用下一种方式重写protected function _getObjectClasses($object)


/**
 * Get list of all classes related with object instance
 *
 * @param $object
 * @return array
 */
protected function _getObjectClasses($object)
{
    $classes = array();
    if (is_object($object)) {
        $classes[] = get_class($object);
        $classes = array_merge($classes, class_implements($object), class_parents    ($object));
    }
    return $classes;
}

我认为唯一的可能性是通过cache_lifetime参数来实现。因此,我们将其设置为大约30秒,每30秒就会根据当前配置刷新一次小部件。

我没有找到在参数更改时立即从缓存中删除该项的解决方案。

这就是我所做的:

  1. 首先,小部件块需要知道小部件实例的ID。为了做到这一点,我不得不在我的一个模块中重写Mage_Widget_Model_Widget_Instance::generateLayoutUpdateXml

    public function generateLayoutUpdateXml($blockReference, $templatePath = '')
    {
        $xml = parent::generateLayoutUpdateXml($blockReference, $templatePath);
        $injectXml = '<action method="setData"><name>widget_instance_id</name><value>' .     
              $this->getId() . '</value></action>';
        $xml = str_replace('</block>', $injectXml . '</block>', $xml);
        return $xml;
    }
    
  2. 在我的小部件块中,我将cachelifetime设置得很小,以避免双重缓存:EDIT:不需要,因为FPC禁用了块缓存

    public function getCacheLifetime()
    {
        return 1;
    }
    
  3. 在缓存信息中包括当前模板和小部件实例id。我发现那些带有字符串键的数组条目被映射到FPC占位符中的属性:

    public function getCacheKeyInfo()
    {
        $instanceId = $this->getWidgetInstanceId();
        return array(
            'MYBOX',
            Mage::app()->getStore()->getId(),
            (int)Mage::app()->getStore()->isCurrentlySecure(),
            Mage::getDesign()->getPackageName(),
            Mage::getDesign()->getTheme('template'),
            'widget_instance_id' => $instanceId,
            'template' => $this->getTemplate(),
        );
    }
    
  4. 现在我们必须为这个小部件创建一个自己的FPC占位符。这个占位符加载小部件实例的当前配置并呈现小部件

    class MyCompany_PageCache_Model_Container_WidgetInstance
        extends Enterprise_PageCache_Model_Container_Abstract
    {
        protected function _renderBlock()
        {
            $instanceId = $this->_placeholder->getAttribute('widget_instance_id');
            $widgetInstance = Mage::getModel('widget/widget_instance')->load($instanceId);
            if($widgetInstance->getId()) {
                Mage::logException( new Exception('Widget Instance '.$instanceId.' not         found') );
            }
            $data = $widgetInstance->getWidgetParameters();
            $block = $this->_placeholder->getAttribute('block');
            $name = $this->_placeholder->getAttribute('name');
            $template = $this->_placeholder->getAttribute('template');
            $block = new $block;
            $block->setTemplate($template);
            if ($name !== null) {
                $block->setNameInLayout($name);
            }
            $block->setLayout(Mage::app()->getLayout());
            $block->setData($data);
    
            return $block->toHtml();
        }
        }
    
  5. 我们必须为这个占位符添加cache.xml配置:

    <mycompany_mybox>
        <block>mycompany_mybox/widget</block>
        <placeholder>MYCOMPANY_MYBOX_WIDGET</placeholder>
        <container>MyCompany_PageCache_Model_Container_WidgetInstance</container>
        <cache_lifetime>30</cache_lifetime>
    </mycompany_mybox>
    
  6. 最后,小部件必须在后端保存一次,以便生成新的布局xml。