当我们给magento开发插件的时候,必然会碰到要改变系统的原来的行为的情况。而magento作为一种插件机制的灵活的系统,直接修改核心代码是极为不妥的方式。而有时magento的事件机制并不能解决问题的时候,重写原来的文件的需求就产生了。在此打算总结下magento中的各种重写。

magento model的重写

实现方法:新的model继承你要重写的model,然后配置文件配置model的rewrite。例子如下: class Zone_Test_Model_Quote extends Mage_Sales_Model_Quote { //这里写一些自定义的方法或者覆盖父类的一些方法来实现自己的逻辑 } <!--model rewrite xml配置--> <?xml version="1.0"?> <config> <!-- 这里省略其他配置... --> <global> <models> <sales> <rewrite> <quote>Zone_Test_Model_Quote</quote> </rewrite> </sales> </models> </global> </config>

这样当我们用Mage::getModel(‘sales/quote’)的时候实例化的就不是Mage_Sales_Model_Quote这个类的对象,而是我们重写后的类的对象了。

magento model重写的原理:

追踪Mage::getModel()这静态方法会发现如下代码:

//file: app/code/core/Mage/Core/Model/Config.php
//function: getGroupedClassName
if (empty($groupRootNode)) {
$groupRootNode = 'global/'.$groupType.'s';</pre>
}
$classArr = explode('/', trim($classId));
$group = $classArr[0];
$class = !empty($classArr[1]) ? $classArr[1] : null;

if (isset($this->_classNameCache[$groupRootNode][$group][$class])) {
return $this->_classNameCache[$groupRootNode][$group][$class];
}

$config = $this->_xml->global->{$groupType.'s'}->{$group};

// First - check maybe the entity class was rewritten
$className = null;
if (isset($config->rewrite->$class)) {
$className = (string)$config->rewrite->$class;//这里就是重写的关键
}

通过上面的代码,我们不难看出,原理就是获取类名的时候先查找是否有rewrite的节点,有就直接用rewrite的类名去实例化对象。

magento Block的重写

关于Block的重写因为block的实例化和model的实例化共用的一段代码,所以方法和配置也差不多,唯一的不同是block重写的配置节点为global/blocks,而model的是global/models。

magento controller的重写

实现方法:和model、block的重写方法差不多,新建自己的controller,然后配置下配置下模块的配置文件,让系统实例化 controller的时候能找到对应的类名。我们举个例子,重写checkout模块的cart 这个controller。代码如下:

 
//首先新建自己的controller在自己的模块controllers目录里面
class Zone_Test_CartController extends Mage_Checkout_CartController
{
//这里新加自己的函数或者重写覆盖父类的同名函数
}


<!--然后就是配置文件:-->
<?xml version="1.0"?>
<config>
<!-- 这里省略其他配置... -->
<frontend>
<routers>
<checkout>
<args>
<modules>
<Zone_Test before="Mage_Checkout">Zone_Test</Zone_Test>
</modules>
</args>
</checkout>
</routers>
</frontend>
</config>

这样配置好后,当我们在浏览器上输入http://youhost/checkout/cart的时候实际上执行的是我们自己的模块里面的Zone_Test_CartController这里类的对象的indexAction方法。

magento 重写实现原理:

magento在路由过程中会通过url获取到frontName为“checkout”和controller为“cart”,然后通过路由器的 getModuleByFrontName方法获取到一个数组,这数组就是我们配置的modules,而且是排序过的,排序按配置节点的before或者 after来排序的。我们这个例子就array(‘Zonet_Test’, ‘Mage_Checkout’),然后遍历这个数组通过数组里面的值和cart去取到对应的controller类 Zone_Test_CartController,实例化然后执行对应的action方法。相关代码如下:

//file:app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
//function:match

public function match(Zend_Controller_Request_Http $request)
{
...
/**
* Searching router args by module name from route using it as key
*/
$modules = $this->getModuleByFrontName($module);//获取真实模块的数组,也就是合并模块路由配置里面的module和modules。
...
/**
* Going through modules to find appropriate controller
*/
$found = false;
foreach ($modules as $realModule) {//这个循环里按配置的顺序去拼接controller类
$request->setRouteName($this->getRouteByFrontName($module));

// get controller name
if ($request->getControllerName()) {
$controller = $request->getControllerName();
} else {
if (!empty($p[1])) {
$controller = $p[1];
} else {
$controller = $front->getDefault('controller');
$request->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,ltrim($request->getOriginalPathInfo(), '/'));
}
}

// get action name
if (empty($action)) {
if ($request->getActionName()) {
$action = $request->getActionName();
} else {
$action = !empty($p[2]) ? $p[2] : $front->getDefault('action');
}
}

//checking if this place should be secure
$this->_checkShouldBeSecure($request, '/'.$module.'/'.$controller.'/'.$action);

$controllerClassName = $this->_validateControllerClassName($realModule, $controller);
if (!$controllerClassName) {
continue;
}

// instantiate controller class
$controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse());

if (!$controllerInstance->hasAction($action)) {
continue;
}

$found = true;
break;
}

}

magento 抽象类的rewrite

对于抽象类的重写和model的重写有点不一样,因为抽象类是直接用来继承的,并没用到方法工厂,所以只能用其他方式去重写了。方法就是复制同样的文件结构到local代码池里面,然后对整个类复制过去,新增或者修改其中的方法来实现重写。原理就是magento的自动加载优先加载local的代码。代码如下:


//file:app/Mage.php
/**
* Set include path
*/
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'local';
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'community';
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'core';
$paths[] = BP . DS . 'lib';

$appPath = implode(PS, $paths);
set_include_path($appPath . PS . Mage::registry('original_include_path'));
include_once "Mage/Core/functions.php";
include_once "Varien/Autoload.php";

magento url rewrite

这里不讨论服务器上面实现的urlrewrite。magento的url rewrite有两种方法:1、xml配置。2、系统自带的产品和目录的url rewrite,这里后台也可以自定义url rewrite。关于后台的url rewrite这里不多说,不清楚的自行查看magento的使用手册。这里主要讲下xml配置及其实现原理,还有就是简答讲下系统自带的基于数据库的 rewrite的原理。


基于xml配置的url rewrite的例子:
<!--模块配置文件:config.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<config>
<!-- 省略的其他配置...-->
<global>
<!-- 省略的其他配置... -->
<rewrite>
<myrewrite>
<from> <![CDATA[#/shopby/([\w+\+]+).html#]]></from>
<to><![CDATA[/catalogsearch/result/index/q/$1]]></to>
<complete/>
</myrewrite>
</rewrite>
</global>
</config>

这个例子就实现了/shopby/querytext.html到/catalogsearch/result/index/q/querytext的rewrite。

我们看看magento是怎么实现这个的。这个rewrite是发生在magento的前端控制器分发请求的时候,顺序在基于数据库的rewrite之后。原理很简单,就是查找各个模块配置文件里面global/rewrite节点取出rewrite配置,遍历这些配置通过正则替换把符合条件的from 替换成to,然后改变request对象的pathinfo。具体实现代码如下:

 
public function rewrite()
{
$request = $this->getRequest();
$config = Mage::getConfig()->getNode('global/rewrite');
if (!$config) {
return;
}

foreach ($config->children() as $rewrite) {
$from = (string)$rewrite->from;
$to = (string)$rewrite->to;
if (empty($from) || empty($to)) {
continue;
}

//这里实现了路由名到前端路由器的替换,把{routeName}替换成对应的frontName,不过一般来说很多模块的routeName和frontName配置成一样了,这个特性使用的不多。
$from = $this->_processRewriteUrl($from);
$to = $this->_processRewriteUrl($to);

//这里是替换的核心所在,通过这句代码也很清楚的知道from 和to该填什么内容。

$pathInfo = preg_replace($from, $to, $request->getPathInfo());

if (isset($rewrite->complete)) {
$request->setPathInfo($pathInfo);
} else {
$request->rewritePathInfo($pathInfo);
}
}
}

关于系统自带的关于产品和目录的url rewrite,前端控制器里面只调用了一句代码: Mage::getModel(‘core/url_rewrite’)->rewrite(),这是调用了核心模块的url rewrite model来处理的。这里面通过数据根据请求路径查找到目标路径,然后看数据库里面是配置的301还是302做相应的类型的跳转,这是通过php的 header函数实现的。剩下的类型就直接改变request 对象的pathinfo。这块magento有一个做的很好的地方,就是对于同一个产品或者目录url rewrite修改过后,原来的记录并不会删除,而是和新的rewrite相关联,这样老的url进来也能跳转的新的url上去,有利于seo。这块具体代码就不分析了,有空专门写个文章。有兴趣的自行翻看代码。

magento config path rewrite

magento的后台system配置,读取的时候一般按system.xml的节点路径来的。但是有的时候当我们不想使用这个路径的时候,配置时可以指定config_path的路径。这个常用于支付插件。因为获取支付类型的时候是通过固定的一个path取的,而我们模块自己的系统配置会用自己的路径,这时候config path就派上用场了。看下面的例子:

 <!--//file:system.xml 这是moneybookers模块的system.xml文件的一个片段-->
        <active translate="label">
           <label>Enabled</label>
           <frontend_type>select</frontend_type>
           <config_path>payment/moneybookers_gir/active</config_path>//这里重写了config path
           <source_model>adminhtml/system_config_source_yesno</source_model>
           <sort_order>1</sort_order>
           <show_in_default>1</show_in_default>
           <show_in_website>1</show_in_website>
           <show_in_store>1</show_in_store>
        </active>

实现原理就更简单了,保存store config的时候,检查下有没有confg_path有就按这个path来保存到数据库。代码如下:


//file: app/code/core/Mage/Adminhtml/Model/Config/Data.php
//functon: save

/**
* Look for custom defined field path
*/
if (is_object($fieldConfig)) {
$configPath = (string)$fieldConfig->config_path;
if (!empty($configPath) && strrpos($configPath, '/') > 0) {//这里开始对自定义的path进行处理
// Extend old data with specified section group
$groupPath = substr($configPath, 0, strrpos($configPath, '/'));
if (!isset($oldConfigAdditionalGroups[$groupPath])) {
$oldConfig = $this->extendConfig($groupPath, true, $oldConfig);
$oldConfigAdditionalGroups[$groupPath] = true;
}
$path = $configPath;
}
}

$inherit = !empty($fieldData['inherit']);

$dataObject->setPath($path)
->setValue($fieldData['value']);

magento template rewrite

当我们写插件的时候想改变原有系统的某个template的时候比如产品详细页面的模板,因为是插件我们没办法控制它的theme只能通过xml配置来重写template了,原理也很容易,就是利用block的配置的action 节点,通过action节点来设置template。例子如下:


<!-- file: mylayout.xml --->
<layout version="0.1.0">
<catalog_product_view>
<reference name="product.info">
<action method="setTemplate"><template>test/product/myselfview.phtml</template>
</action>
</reference>
</catalog_product_view>
</layout>

这样通过插件的layout配置就可以改变产品详细页面的template为自己插件的模板。

360magento提供专业的基于magento系统的电商网站开发服务,如有需求或相关咨询,请与我们联系