在magento事件观察器中以编程方式更新布局



我正在尝试更改产品详细信息页面的块(product.info)的模板(view.phtml),为此,我正在观察一个事件(controller_action_layout_generate_blocks_before),在进行必要的检查后,我正在尝试以以下方式更改块(product.info)的模板:

$layout = $observer->getEvent()->getLayout();
$layout->getUpdate()->addUpdate('
<reference name="product.info">
<action method="setTemplate">
<template>customlayout/product/view.phtml</template>
</action>                                                          
</reference>');
$layout->getUpdate()->load();
$layout->generateXml();

如果我放置"<remove name='product.info'/>",它将被删除,但当尝试执行上述操作时,它不起作用
编辑:
要求将模板(产品详细信息)动态切换到针对当前产品的选定模板(在CustomModule中)。

正如Ben所说,我不知道你为什么要把它放在观测器上,但你的问题是loadLayout的序列。

您可以使用检查加载的布局xml

Mage::log(Mage::getSingleton('core/layout')->getUpdate()->asString());

很确定你的<action method="setTemplate"><template>customelayout/product/view.phtml</template>已经被其他setTemplate覆盖了,这就是你的模板没有显示的原因。

Mage_Core_Controller_Varien_Action
public function loadLayout($handles=null, $generateBlocks=true, $generateXml=true)
{
// if handles were specified in arguments load them first
if (false!==$handles && ''!==$handles) {
$this->getLayout()->getUpdate()->addHandle($handles ? $handles : 'default');
}
// add default layout handles for this action
$this->addActionLayoutHandles();
$this->loadLayoutUpdates(); //in here: $this->getLayout()->getUpdate()->load();
if (!$generateXml) {
return $this;
}
//event: controller_action_layout_generate_xml_before
$this->generateLayoutXml(); //in here: $this->getLayout()->generateXml();
if (!$generateBlocks) {
return $this;
}
//event: controller_action_layout_generate_blocks_before, your observer is located here
$this->generateLayoutBlocks(); //in here: $this->getLayout()->generateBlocks();
$this->_isLayoutLoaded = true;
return $this;
}

因此,您将使用事件controller_action_layout_generate_blocks_before修改xml。

这意味着你需要做的是:

//add the update
$layout->getUpdate()->addUpdate('<reference name="product.info"><action method="setTemplate"><template>customelayout/product/view.phtml</template></action></reference>');
//then generate the xml
$layout->generateXml();

导致您出现问题的原因是:

$layout->getUpdate()->load();

后再次调用

$layout->getUpdate()->addUpdate('<reference name="product.info"><action method="setTemplate"><template>customelayout/product/view.phtml</template></action></reference>');

尽管最好使用事件:controller_action_layout_generate_xml_before。这样您就不需要生成两次xml了。

如果您想从观察者更改块的模板,您应该

  1. 监听controller_action_layout_generate_blocks_after事件

  2. 使用PHP操作布局

通过侦听generate-after事件,您可以确保通过基于文件的LayoutUpdateXML字符串指定的每个操作方法都将首先被调用,并且您的模板更改将"获胜"。

我建议使用PHP代码,因为LayoutUpdateXML系统是一种特定于领域的语言,其目的是为布局更新提供一组有限的功能,而不必编写一行PHP。如果您已经在使用PHP观察器,那么通过PHP操作布局是有意义的。

像这样的代码应该能得到你想要的东西(同样,从观察者方法之后的)

$controller   = $observer->getAction();
//limit to the product view page 
if($controller->getFullActionName() != 'catalog_product_view')
{
return;
}
$layout       = $controller->getLayout();
$product_info = $layout->getBlock('product.info');
if(!$product_info)
{
Mage::log('Could not find product.info block');
return;
}
$product_info->setTemplate('customelayout/product/view.phtml');

你到底为什么这样做?

最好使用local.xml布局文件或为自定义模块声明的布局文件:

<?xml version="1.0" encoding="UTF-8"?>
<layout>
<catalog_product_view>
<reference name="product.info">
<action method="setTemplate">
<tpl>customelayout/product/view.phtml</tpl>
</action>
</reference>
</catalog_product_view>
</layout>

仅供参考,当块名称为<remove/>ed时,任何包含remove指令的渲染范围都不会实例化具有该名称的块。

另一个解决方案,也就是说,在我看来,Magento的精神中更多的是声明我们自己的句柄。

1.声明controller_action_layout_load_before的观察员

在模块config.xml中,在节点config>frontend>events下放入以下代码:

<controller_action_layout_load_before>
<observers>
<stackoverflow_set_handle>
<class>stackoverflow_module/observer</class>
<method>setHandle</method>
</stackoverflow_set_handle>
</observers>
</controller_action_layout_load_before>

2.定义你的观察者

class Stackoverflow_Module_Model_Observer
{
public function setHandle(Varien_Event_Observer $observer)
{
$fullActionName = $observer->getEvent()->getAction()->getFullActionName();
if (/* Any condition you may want to modify the layout */) {
Mage::app()->getLayout()->getUpdate()->addHandle('MY_HANDLE_' . $fullActionName);
}
}

3.创建布局xml文件

一旦完成,您就可以在布局更新文件中使用任何fullActionName作为二级节点,前缀为MY_HANDLE_。

只有当句柄存在时,这些指令才会被触发,因此基本上对于您在观察器中设置的任何条件都是如此。

<?xml version="1.0"?>
<layout version="0.1.0">
<MY_HANDLE_catalogsearch_result_index>
<reference name="left">
<remove name="catalogsearch.leftnav" />
</reference>
</MY_HANDLE_catalogsearch_result_index>
<MY_HANDLE_catalog_product_view>
<!-- Do anything you want -->
</MY_HANDLE_catalog_product_view>
</layout>

遗言

当然,您可以在观察者中测试$fullActionName,以便更具体地添加句柄,还可以构建一个不基于fullActionName的动态句柄。

仅供参考,这是Magento管理许多布局变化的方式:

  • STORE_default>与当前存储动态构建
  • THEME_fronend_enterprise_enterprise>使用当前主题动态构建
  • PRODUCT_TYPE_simple>使用当前产品类型动态构建
  • PRODUCT_16>使用当前产品id动态构建
  • customer_logged_out>仅在客户登录时出现
  • 以及其他

要查看它们,您可以暂时将其放在index.php:的末尾

var_dump(Mage::app()->getLayout()->getUpdate()->getHandles());

我本来打算评论JBreton的奇妙答案,但我的特定用例(将我带到这个线程)略有不同。(此外,我是一个如此潜伏的人,还没有足够的声誉发表评论。)

接受的答案和其他修改PHP代码布局的建议对我来说不起作用,即使在尝试观察了各种事件之后也是如此,所以我想我会在JBreton方面发布一个窃取/支持/示例答案。我的用例是根据特定条件以编程方式从checkout_cart_index布局中移除块(核心和自定义模块块)。使用自定义布局句柄的方法也适用于ADDING块,因为它只是"激活"一个新句柄,Magento将从主题中的标准布局XML文件处理该句柄。

JBreton的方法是我尝试过的所有方法中最好的。它在当前和未来需求方面更有意义。尤其是在设计者和模板构建者不是应该在PHP代码中四处窥探的人的情况下。模板人员了解XML,并且应该非常熟悉Magento的布局XML系统。因此,使用自定义句柄在特定的编程条件下修改布局是比在PHP中通过字符串添加XML更好的方法。

再次。。。这不是我自己想出的解决方案。。。我从JBreton上面的回答中窃取了这一点,并提供了示例代码,我的替身可以在他们的情况下使用这些代码作为额外的起点。请注意,这里并没有包含我的所有模块代码(尤其是app/modulesXML文件、模型类等)

我的模块配置文件:

app/code/local/Blahblah/GroupCode/etc/config.xml

<config>
... other config XML too ...
<frontend>
<events>
<controller_action_layout_load_before>
<observers>
<blahblah_groupcode_checkout_cart_index>
<type>singleton</type>
<class>Blahblah_Groupcode_Model_Ghost</class>
<method>checkout_cart_prepare</method>
</blahblah_groupcode_checkout_cart_index>
</observers>
</controller_action_layout_load_before>
</events>
</frontend>
</config>

类中的观测者方法:

app/code/local/Blahblah/GroupCode/Model/Observer.php

<?php
public function checkout_cart_prepare(Varien_Event_Observer $observer)
{
// this is the only action this function cares to work on
$fullActionName = 'checkout_cart_index';
... some boring prerequiste code ...
// find out if checkout is permitted
$checkoutPermitted = $this->_ghost_checkoutPermitted();
if(!$checkoutPermitted)
{
// add a custom handle used in our layout update xml file
Mage::app()->getLayout()->getUpdate()->addHandle($fullActionName . '_disable_checkout');
}
return $this;
}

包含在主题文件中的布局更新:

app/design/PACKAGE/THEME/etc/theme.xml

<?xml version="1.0"?>
<theme>
<parent>...</parent>
<layout>
<updates>
<!-- Adding references to updates in separate layout XML files. -->
<blahblah_checkout_cart_index>
<file>blahblah--checkout_cart_index.xml</file>
</blahblah_checkout_cart_index>
... other update references too ...
</updates>
</layout>
</theme>

布局更新定义文件:

app/design/PACKAGE/THEME/layout/blahblah--checkout_cart_index.xml

<layouts>
<checkout_cart_index_disable_checkout>
<reference name="content">
<block type="core/template" name="checkout.disabled" as="checkout.disabled" before="-" template="checkout/disabled-message.phtml" />
<remove name="checkout.cart.top_methods" />
<remove name="checkout.cart.methods" />
</reference>
</checkout_cart_index_disable_checkout>
... other layout updates too ...
</layouts>

(是的,我的模块中还有其他代码可以监视签出过程事件,以确保有人不会使用手动URL路径偷偷进入。还有其他检查可以真正"禁用"签出。我只是展示了如何通过观察者以编程方式修改布局的示例。)

最新更新