magento开发文档

开始之前,首先声明下,Magento开发者手册由Alan Storm发表在Magento官方网站上。总共分八个部分,由浅入深的介绍了Magento的MVC架构及Magento中使用的比较特殊的EAV模型。

虽然英文文档读起来没有问题,但是真想看一遍能有一定深入的了解,还是中文看着比较舒服。并且在网上搜索了下,大部分都是Magento的模板开发手册以及没有纠错的原文翻译(因为版本问题,Magento官方网站上的一些例子已经无法正常运行),所以决定把这写文章翻译成中文,一来对于自己更深入的把握程序有好处,二来对于想学习Magento的朋友们有个帮助。

需要点到的一个地方,翻译不易,请尊重作者Alan Storm的劳动,同时也请尊重我的劳动,转载请注明出自本站,并注明作者英文地址。十分感谢!废话到此为止。

翻译名词对照:

Modules->模块

Controller->控制器

Model->模型

Magento是这个星球上最强大的购物车网店平台。当然,你应该已经对此毫无疑问了。不过,你可能还不知道,Magento同样是一个面向对象的PHP框架。你可以配合Magento购物车程序强大的功能,开发动态WEB应用程序。

这是Magento中文开发手册的开篇,我们会在整个手册中介绍绝大部分Magento的开发框架特性。不要想在这片文章中立刻掌握所有的特性。这仅仅是个开始,但是足够让你在同行中鹤立鸡群了。

在这片文章中,你将了解到:

  • Magento模块(Magento Modules)代码组织形式
  • 配置型MVC架构
  • Magento控制器(Magento Controllers)
  • 基于URI的模型实例化(Context-based URI Model Loading)
  • Magento模型(Magento Models)
  • Magento助手(Magento Helpers)
  • Magento布局(Magento Layouts)
  • 事件监听(Observers)
  • Magento类重写(Class Overrides)
  • 总结

开始之前,你可以试着看下Magento MVC模式的一个图形化直观体现。Magento_MVC.pdf

Magento模块中的代码组织形式

Magento通过将代码放入独立的模块进行组织。在一个典型的PHP MVC应用中,所有的控制器会被放在一个文件夹中,所有的模型会被放在另外一个文件夹里,等等。而在Magento中,文件是基于功能进行分组的,这种分组后的代码块叫做模块。

Magento的代码:

举例来说,如果你想寻找Magento中关于付款的功能,你仅仅需要找到下面代码中的文件夹,就能获取所有的控制器,模型,助手,Blocks等。

app/code/core/Mage/Checkout

如果你想寻找Magento中关于Google Checkout的功能,也仅仅需要找到如下文件夹,即可获取所有你想要的信息。

app/code/core/Mage/GoogleCheckout

你的代码:

如果你想扩展Magento,千万不要想当然的去修改core文件夹中的文件,也不要将你自己的控制器,模型,助手或者Blocks放在Core文件夹中。所有对于Magento的扩展,都将在local文件夹中进行。

app/code/local/<Package>/<Modulename>

Package(也可称为命名空间,当然这不是PHP手册中提到的命名空间)是唯一的命名,通过Package来标识你的公司,组织或个人。通过Package,世界范围内的Magento社区在创建模块扩展时,能够使用他们自己的Package名称,以避免与其他开发者有命名冲突。

创建一个新的模块时,你需要告诉Magento新模块的相关信息。可以通过添加一个XML文件在下面的目录中。

app/etc/modules

在这个目录中有两类xml文件,第一种用来开启独立的模块,以下列方式命名:

Packagename_Modulename.xml

第二种文件用来从一个Package中开启多个模块,以下列方式命名:

Packagename_All.xml

配置型MVC系统

Magento是一个配置型MVC(Configuration-based MVC)系统。另外一种MVC系统则是大部分PHP框架使用的,约定性MVC(convertion-based MVC)。

在约定型MVC系统中,如果你添加一个控制器,或者一个模型,只需要根据约定的内容,创建这个文件以及类即可,系统会自动识别它。

而在配置型MVC系统中,比如Magento,除了需要添加相应的文件及类之外,还需要明确的告诉系统该类的存在。在Magento中,每个模块都有一个config.xml文件。这个文件中包含了一个模块相关的配置信息。在运行时,所有模块的配置文件,都会被加载到一个巨大的配置文件树中(后面的文章会介绍如何查看这个配置树)。

比如,想在模块中使用模型。你需要添加类似下面的代码,来告诉Magento你会在这个模块中使用这个模型。

0102030405<models>    <packagename>    <class>Packagename_Modulename_Model</class>    </packagename><models>

当然,这种配置不仅限于模型,对于控制器,助手,Blocks,路由,事件句柄等都需要在该模块的config.xml中进行相关的配置。

Magento控制器(Magento Controllers)

在任何PHP系统当中,核心文件肯定是PHP文件。Magento也不例外,index.php是Magento的核心文件。

不过,永远不要编辑index.php中的任何代码。在MVC系统中,index.php的左右大概有以下几项:

  • 检测URL地址。
  • 根据路由规则,将访问的URL地址分发到控制器类中的方法。
  • 初始化控制器,并调用相应的动作方法。这一步骤叫做分发。Dispatching。

这意味着Magento(或任何MVC系统)每一个有效的entry point都是控制器文件中的一个方法。一起来看下面这个URL:

http://example.com/catalog/category/view/id/25

上述域名后URL地址可以被分拆为以下几个部分。

Front Name – catalog

该URL的第一部分被称为Front Name。它用来指示Magento应该在哪个模块中寻找URL中的控制器。在这个例子中,catalog就是Front Name,对应于catalog模块。

Controller Name – Category

第二部分指示Magento应该匹配的控制器。每个拥有控制器的模块都包含一个‘controllers’的文件夹,用来存放该模块下的所有控制器。上述URL地址,匹配了下面这个控制器文件。

app/code/core/Mage/Catalog/controllers/CategoryController.php

其中的类定义格式大概为:

010203classMage_Catalog_CategoryController extendsMage_Core_Controller_Front_Action{}

在Magento中,所有的控制器都继承自Mage_Core_controller_Front_Action类。

Action Name – view

第三部分是一个action方法的名称。在此URL中,view便是一个action方法的名字。

0102030405classMage_Catalog_CategoryController extendsMage_Core_Controller_Front_Action {    publicfunctionviewAction() {     }}

Paramater/Value – id/25

任何位于action方法名之后的路径,都会被认为是key/value形式传递的GET变量。那么在我们的例子当中,’id/25′表示有一个值为25的$_GET['id']变量。

如前所述,如果你想让自定义模块使用控制器,你必须对它进行配置。下面是在模块中开启控制器的代码。

0102030405060708091011<frontend>    <routers>        <catalog>            <use>standard</use>            <args>                <module>Mage_Catalog</module>                <frontName>catalog</frontName>            </args>        </catalog>    </routers></frontend>

现在不清楚上述内容都是什么意思还没关系,但是注意<frontName>catalog</frontName>。这是用来关联模块与URL地址中frontname的。Magento核心代码选择将一个模块的名字与frontname一致,但这不是强制规定的。

Multiple Routers

上面提到的路由规则主要是针对Magento购物车程序(即你所能看到的前端)。如果Magento在URL中无法匹配到正确的控制器/动作,它会尝试使用针对Admin程序(后台管理端)的另一套路由规则。如果依旧无法正确匹配,它会使用一个特殊的控制器Mage_Cms_IndexController。

CMS控制器会检查Magento内容管理系统中是否有内容需要输出,如果有内容输出,则读取该内容,如果找不到,则输出404页面。

例如,Magento默认的首页就是在使用CMS控制器。

Context-Based URI 模型读取

目前为止,我们已经建立了一个控制器以及一个方法,到实例化一个类做点什么的时候了。Magento提供了一种特殊的方式去实例化模型,助手以及Blocks,即使用Mage全局类提供的静态工厂方法。例如,

0102Mage::getModel('catalog/product');Mage::helper('catalog/product');

‘catalog/product’字符串被称为Grouped Class Name。通常叫做URI。Grouped Class Name的第一部分用来指示该类存在于哪个模块当中。第二部分用来决定哪个类将被调用。

那么,上述例子中,‘catalog’对应于app/code/core/Mage/Catalog模块,也就意味着我们的类名将以Mage_Catalog开头,然后根据调用的类型,将product类名加入到最后一部分。即,

0102030405Mage::getModel('catalog/product');Mage_Catalog_Model_Product; Mage::helper('catalog/product');Mage_Catalog_Helper_Product;Magento 模型

和现在的多数框架一样,Magento也提供ORM支持。ORM让你能够专注于数据,而非无尽的SQL语句。例如,

0102030405$model= Mage::getModel('catalog/product')->load(27);$price= $model->getPrice();$price+= 5;$model->setPrice($price)->setSku('SK1231414');$model->save();

在上面这个例子中,我们调用了“getPrice”和“setPrice”方法。然而,在Mage_Catalog_Model_Product类中并没有此方法。那为什么上面这个例子能够使用这些方法呢?因为Magento的ORM系统中使用了PHP的_get和_set魔术方法。

调用$product->getPrice()会获取模型属性price,而调用$product->setPrice()会设置price属性。当然,所有的这些都假设模型类没有getPrice和setPrice方法。如果它们存在于模型类中,PHP魔术方法会被忽略。如果你有兴趣知道这是如何实现的,可以参考Varien_Object类,所有的模型类都继承自该类。

如果你想获取模型当中所有的数据,可以直接调用$product->getData()方法,它会返回包含所有字段的一个数组。

你可能已经注意到上例中的方法存在使用->符号链接的形式:

$model->setPrice($price)->setSku(‘SK12312542′);

能够使用这种方式调用方法,最主要的原因是所有的set方法都会返回一个模型的实例。 你会经常在Magento的核心代码中看到此类调用方法的形式。

Magento的ORM系统中还包含一种通过Collections接口查询多个对象的方式。下例会读取系统中所有5美元的产品。

01020304$product_collection= Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect('*')->addFieldToFilter('price','5.00');

这里我们又一次看到了链接调用方法的形式。Collections use the PHP Standard Library to implement Objects that have array like properties.(这句超出理解范围)。

01020304foreach($products_collectionas$product){    echo$product->getName();}

在上面的一个例子当中,你可能注意到了addAttributeToSelect方法。这里单独提到此方法,是因为它代表了Magento模型中的一个类别。Magento拥有两种形式的模型对象。一种是传统的“一个对象,一张表”的Active Record模型。当你实例化这些模型的时候,所有的属性都会被自动选取。

Magento中第二种模型叫做Entity Attribute Value(EAV)模型。这种模型会按照一定的规律将数据分散存储在数据库不同的表中。EAV模型的高级特性,让Magento不用在增加一种产品属性的时候改变数据库模型(一般的购物车系统在增加新的属性时,有两种方式,一种是增加数据库字段,一种是使用预留的空字段。),从而保证了Magento系统的高度扩展性。当创建一个EAV模型的collection时,Magento会conservative in它会查询的字段数,所有你可以使用addAttributeToSelect来制定你想获取的列,或者使用addAttributeToSelect(*)来获取所有列。

Magento Helpers 助手

Magento的助手类包含一系列实用的方法,通过这些方法可以对对象及变量做日常性的操作。例如,

01$helper= Mage::helper('catalog');

是否注意到这里舍弃了Grouped Class Name的第二部分?每个模块都有一个默认的data助手类。下面的语句与上面的作用是相同的,即默认使用模块下的data助手类。

01$helper= Mage::helper('catalog/data');

大部分的助手类继承自Mage_Core_Helper_Abstract,默认提供了很多使用的方法。

01020304$translated_output= $helper->__('Magento is Great');if($helper->isModuleOutputEnabled()) { }Magento Layout 布局

目前为止,我们已经介绍了控制器,模型以及助手。在典型的PHP MVC系统当中,在操作模型之后,一般会

  • 传递变量到视图中。
  • 系统会自动读取默认的外层布局
  • 接着将视图读取到外层布局中

不过,如果你仔细观察Magento 控制器动作方法,你不会看到这些步骤,

01020304050607080910111213publicfunctiongalleryAction() {    if(!$this->_initProduct()) {        if(isset($_GET['store']) && !$this->getResponse()->isRedirect()) {            $this->redirect('');        } elseif(!$this->getResponse()->isRedirect()) {            $this->_forward('noRoute');        }        return;    }     $this->loadLayout();    $this->renderLayout();}

不同于典型PHP MVC形式的是,控制器动作方法,以两个输出布局的方法结束。所以说,Magento MVC系统中的V视图部分可能与你经常使用的大相径庭。因为,你必须在控制器中明确的输出布局。

并且,Magento的布局本身也区别与你经常使用的MVC系统。Magento布局是一个包含嵌套或者树状的Block对象的对象。每一个Block对象输出一部分HTML,输出HTML的环节包含两个部分,PHP代码组成的Block以及.phtml模板文件。

Blocks对象负责与Magento系统交互并从模型中获取数据,而phtml模板文件则为页面生成必须的HTML代码。

例如,页面头部Block文件app/code/core/Mage/Page/Block/Html/Head.php使用与其对应的page/html/head.phtml模板文件。

换种方式说的话,Blocks类就像迷你控制器,而.phtml文件就是视图文件。

默认的,当你调用,

0102$this->loadLayout();$this->renderLayout();

Magento will load up a Layout with a skeleton site structure(此段能够理解,但想不到最佳翻译,大概意思是Magento会读取网站的布局框架)。这些结构Blocks用来输出head,body以及设定单栏或多栏的布局。另外,还有一些内容Blocks负责实际输出像导航,产品分类等。

“结构”和“内容”Blocks在布局系统中是随意设置的。一般不会在代码中刻意添加代码,从而区分一个Block是结构还是内容,但是Blocks要么属于“结构”,要么属于“内容”。

为了添加一个内容Blocks到布局中,你需要告诉Magento系统

“Magento,快把这几个Blocks添加到内容Block 里”

或者

“Magento,把这边几个Blocks放到“左边栏”结构Block里”

这些可以通过控制器中的代码进行控制,

01020304publicfunctionindexAction() {    $block= $this->getLayout()->createBlock('adminhtml/system_account_edit');    $this->getLayout()->getBlock('content')->append($block);}

但是更常用的方式(至少在前台购物车应用中)是使用基于XML文件的布局系统。

在一款风格中,基于XML文件的布局 允许你删除正常输出的Blocks或者添加默认的skeleton区域(即Structure Blocks)。例如下面这个XML布局文件,

0102030405<catalog_category_default>    <referencename="left">        <blocktype="catalog/navigation"name="catalog.leftnav"after="currency"template="catalog/navigation/left.phtml"/>    </reference></catalog_category_default>

上面这段代码的作用是,在catalog模块的category控制器的默认动作方法中,将catalog/navigation Block插入到左边栏结构Block中,并使用catalog/navigation/left.phtml模板文件。

关于Blocks还有一个比较重要的特性。在模板文件中,你会看到很多类似下面的代码,

01$this->getChildHtml('order_items')

这是Block输出套嵌Block的方式。但是,只有在XML布局文件中明确声明一个Block包含另一个子Block时,才能在模板文件中通过getChildHtml()方法调用子Block的模板文件。

例如,在XML布局文件中,

01020304050607<catalog_category_default>    <referencename="left">        <blocktype="catalog/navigation"name="catalog.leftnav"after="currency"template="catalog/navigation/left.phtml">            <blocktype="core/template"name="foobar"template="foo/baz/bar.phtml"/>        </block>    </reference></catalog_category_default>

那么从catalog/navigation Block中,我们才可以调用$this->getChildHtml(‘foobar’);

Observers 观察者

和许多优秀的面向对象系统一样,Magento通过实现观察者模式给用户作为钩子。对于在页面请求时(模型存储,用户登录等)调用的特定动作方法,Magento会生成一个事件信号。

当创建新的模块时,你可以“监听”这些事件。比如说,你想在特定用户登录商店的时候发一封邮件到管理员信箱里,可以通过“监听”customer_login事件做到。

0102030405060708091011<events>    <customer_login>        <observers>            <unique_name>                <type>singleton</type>                <class>mymodule/observer</class>                <method>iSpyWithMyLittleEye</method>            </unique_name>        </observers>    </customer_login></events>

接下来是当用户登录时应该运行的代码:

010203040506070809classPackagename_Mymodule_Model_Observer{    publicfunctioniSpyWithMyLittleEye($observer)    {        $data= $observer->getData();        //code to check observer data for out user,        //and take some action goes here    }}Class Overrides 类的复写

最后,Magento系统还提供了类的复写功能,你可以通过自己的代码覆盖核心代码里的模型,助手,和Blocks类。

下面举些例子帮助你更容易理解这个功能。产品的模型类是Mage_Catalog_Model_Product.无论何时,下面的代码被调用时,就会生成一个Mage_Catalog_Model_Product对象。

01$product= Mage::getModel('catalog/product');

这是工厂模式。Magento的类复写系统运作模式大概是这样,

“Magento! 如果有请求对应catalog/product,不要实例化Mage_Catalog_Model_Product,让Packagename_Modulename_Model_Foobazproduct接手”

在模型中,类的覆盖方式及命名规则如下,

01020304classPackagename_Modulename_Model_Foobazproduct extendsMage_Catalog_Model_Product{ }

通过这种方式,你可以改变父类中方法的行为,并且完全继承父类的所有功能。

010203040506classPackagename_Modulename_Model_Foobazproduct extendsMage_Catalog_Model_Product{    publicfunctionvalidate() {        //添加一些自定义的验证功能    return$this;}

和之前所说的一样,复写同样需要在config.xml配置文件中进行配置。

010203040506070809101112<models>    <!-- tells the system this module has models -->    <modulename>        <class>Packagename_Modulename_Model</class>    </modulename>    <!-- does the override for catalog/product-->    <catalog>        <rewrite>            <product>Packagename_Modulename_Model_Foobazproduct</product>        </rewrite>    </catalog></models>

 

 

Magento中文手册(二):Magento配置

Magento 的配置文件如同该系统的心脏一般。它负责从总体上配置所有被请求的模块,模型,类,模板文件等。它是绝大部分PHP程序员不会接触到的一块抽象层,同时也 会增加一系列的开发成本,但是,所有这些都是值得的,因为Magento的配置文件允许你对系统的默认功能进行你无法想象的高度扩展。

为了学习Magento配置文件,我们将会在本章创建一个Magento模块,使其能够在浏览器中访问并显示Magento系统的配置文件。

本篇文章中包括的内容有,

  • 设置一个Magento模块的目录结构
  • 创建模块的配置文件
  • 我能在配置文件中找到什么有用信息?
  • 为什么配置文件对我如此重要?

设置Magento模块的目录结构

这一节我们将创建一个Magento模块。Magento模块是由PHP及XML文件组成,用来给系统扩展新的功能,或重写、扩展核心系统的行为。这可能意味着添加新的数据模型来跟踪销售信息,改变系统中类的行为,或添加完全新的功能。

Magento系统中绝大多数你将使用到的模块都具有相同的结构。如果你查看,

app/code/core/Mage

该目录下的每个文件夹都是Magento团队创建的一个单独的模块。同时,你创建的模块将会放在以下文件夹中,

app/code/local/Packagename

“Packagename” 应该是能够区别你代码的唯一命名。一般情况下是可以是你的公司名称,但你也可以随意命名。例如,微软公司开发的Magento模块,可能会使用以下命名,

app/code/local/Microsoft

在这里,我们使用”Magentotutorial”。那么首先,要创建模块,我们需要创建如下所示的目录结构,

app/code/local/Magentotutorial/Configviewer/Block

app/code/local/Magentotutorial/Configviewer/controllers

app/code/local/Magentotutorial/Configviewer/etc

app/code/local/Magentotutorial/Configviewer/Helper

app/code/local/Magentotutorial/Configviewer/Model

app/code/local/Magentotutorial/Configviewer/sql

一个模块并不一定需要上述所有的目录,但是先把他们给全部创建好是个不错的注意。接着,需要创建两个配置文件,一个是位于上述路径etc目录中的config.xml ,

app/code/local/Magentotutorial/Configviewer/etc/config.xml

第二个位于下面的路径,

app/etc/modules/Magentotutorial_configviewer.xml

config.xml文件中会包含下面的代码,先不用理解这些代表什么,继续看,下面会细细讲解。

01020304050607<config>    <modules>        <Magentotutorial_Configviewer>            <version>0.1.0</version>        </Magentotutorail_Configviewer>    </modules></config>

最后, Magentotutorial_configviewer.xml需要包含以下配置文件代码,

0102030405060708<config>    <modules>        <Magentotutorial_Configviewer>            <active>true</active>            <codePool>local</codePool.        </Magentotutorial_Configviewr>    </modules></config>

再然后?不需要再然后了,你现在已经创建了一个基本的模块,当然它还什么都不能干,但是Magento已经能够识别到此模块的存在。登录Magento后台,进行如下操作,

  • 清空Magento缓存
  • 访问System->Configuration->Advanced
  • 点击”Disable modules output”,大概在最下面的位置,能够找到你刚创建的模块

找到了?恭喜你,你的第一个Magento模块创建完毕。

创建、完善模块的配置文件

向上面说的一样,这个模块目前还什么都不能做,完成本章之后,这个模块会有如下功能,

  • 检查请求路径中是否包含”showConfig”字符串
  • 如果存在该字符串,将会显示Magento的配置文件,并停止请求的默认执行
  • 检查请求路径中是否包含”showConfigFormat”字符串,它能够定制配置文件输出格式

首先,我们将下列代码中的<global>部分添加到config.xml文件中,

01020304050607080910111213141516<modules>    ...</modules><global>    <events>        <controller_front_init_routers>            <observers>                <Magentototurial_configviewer_model_observer>                    <type>singleton</type>                    <class>Magentototurial_Configviewer_Model_Observer</class>                    <mothod>checkForConfigRequest</mothod>                </Magentototurial_configviewer_model_observer>            </observers>        </controller_front_init_routers>    </events></global>

然后在下面的路径中创建Ovserver.php文件,

Magentotutorial/Configviewer/Model/Observer.php

添加以下代码到该文件中,

010203040506070809101112131415161718192021222324252627282930classMagentotutorial_Configviewer_Model_Observer {    constFLAG_SHOW_CONFIG = 'showConfig';    constFLAG_SHOW_CONFIG_FORMAT = 'showConfigFormat';     private$request;     publicfunctioncheckForConfigRequest($observer) {        $this->request = $observer->getEvent()->getData('front')->getRequest();        if($this->request->{self::FLAG_SHOW_CONFIG} === 'true'){            $this->setHeader();            $this->outputConfig();        }    }     privatefunctionsetHeader() {        $format= isset($this->request->{self::FLAG_SHOW_CONFIG_FORMAT}) ?        $this->request->{self::FLAG_SHOW_CONFIG_FORMAT} : 'xml';        switch($format){            case'text':                header("Content-Type: text/plain");                break;            default:                header("Content-Type: text/xml");        }    }     privatefunctionoutputConfig() {        die(Mage::app()->getConfig()->getNode()->asXML());    }}

到此,我们的配置文件查看模块完成了,清理下Magento缓存,然后在浏览器打开存放此模块的Magento的任意地址,并加上“showConfig=true”字符串。

http://magento.example.com/?showConfig=true

我能在配置文件中找到什么有用信息?

打开上述页面之后,你会看到一个巨大的XML文件。它描述了当前运行的Magento系统的整个状态。你能够找到所有的模块,模型,类,时间监听者以及所有存在于Magento系统中的配置。

还记得本章创建模块时建立的config.xml配置文件吗?试着在浏览器中的xml文档中搜索该配置文件中包含的Configviewer_Model_Observer,你会发现刚刚创建的这个配置文件实际上也包含在这个xml文档中。总的来说Magento中所有模块的配置文件都会被解析并包含在这个全局配置文件当中。

为什么配置文件对我如此重要?

目前为止可能有些难以理解,但是这个配置文件是你学习Magento系统的关键。你添加的任何所有模块都能够在这个配置文件中找到,任何时候当你需要请求核心系统的功能时,Magento都会使用到配置文件。

举个例子,作为一个MVC程序员,你经常会使用到一些助手类,使用如下方式实例化,

01$helper_sales= newHelperSales();

而Magento是抽象化了这个过程,并非使用PHP的类声明方式。在Magento中,上面的助手类对象会使用如下方式实例化,

01$helper_sales= Mage::helper('sales');

通俗的说,这个静态方法将会,

  • 在配置文件找查找<helpers />节点
  • 在<helpers />节点内,查找<sales />节点
  • 在<sales />节点内,再查找<class />节点
  • 实例化在<class />节点中找到的类

这看起来貌似费了很大的事,关键的优点是,通过在配置文件中查询类名,我们可以重写Magento核心功能,而不需要改变或添加任何代码到核心库中。这种“元编程“的方式在PHP中使用的并不多,不过它可以让你明确的扩展你想扩展的任意一个地方。

 

Magento中文手册(三):Magento控制器

提到模型-视图-控制器这种MVC架构,要追溯到Smalltalk编程语言和Xerox Parc。从那个时候开始,就有许多系统将自己描述为MVC架构。这些系统虽然在某些地方有细微差别,但都实现了数据层,逻辑层和前段表现代码的分离。

大部分的PHP MVC框架都拥有以下基本特征,

  • 所有的URL请求会被前端控制器解析
  • 前端控制器会检查请求的URL路径,并从中获取一个控制器和动作名(这个过程叫做路由)
  • 实例化从URL中获取的控制器
  • 该控制器中,与从URL里获取的动作名一致的方法会被调用
  • 根据请求的变量,该方法被实例化或调用方法获取模型
  • 该动作方法从模型中获取到数据,这些获取的信息会被传递到视图中
  • 使用从数据结构中获取到的信息,视图输出HTML代码

相对于“一个PHP文件对应一个页面”来说,这种MVC架构是个巨大的进步。然后对于一些软件工程师来说,它依然是个丑陋的架构。他们经常抱怨说,

  • 前段控制器文件会在全局命名空间中运行
  • 约定型的架构没有配置型架构更具扩展性

比如说,

  • URL路由规则通常是无法扩展的
  • 控制器被绑定在特定的视图
  • 即使该系统拥有重写核心代码的功能,程序员依然只能在无尽的重构中编写代码

Magento团队创建了一个更为抽象的MVC模式,大概的运行过程是,

  • URL地址首先被一个PHP文件解析
  • 该PHP文件会根据解析情况实例化一个Magento应用
  • 这个Magento应用会实例化一个前端控制器对象
  • 接着,前端控制器实例化路由对象
  • 路由对象检查请求的URL地址,并作出相应的匹配
  • 如果匹配成功,相应的控制器和动作会被分发
  • 该控制器会被实例化,并且与动作同名的方法会被调用
  • 被调用的方法根据请求的类型,对相应的模型调用相应的方法以获取数据
  • 结束方法调用之后,控制器会实例化布局对象
  • 根据请求中包含的变量及系统属性(通常叫做句柄),布局对象会为该请求创建一系列的Block对象
  • 布局还会在相应的Block对象中调用输出方法,开始套嵌输出(Blocks之间的套嵌)
  • 每个Block都有相关联的模板文件,Blocks包含PHP逻辑代码,模板文件则负责生成HTML文件
  • Block从模型中获取相关数据,换句话说,控制器并不用来传递数据到视图中

我们会在整个教程中介绍上面的所有部分,本章主要涉及到到前端控制器->路由->动作控制器环节。

Hello World

又到Hello World了,你懂的。这一节主要任务是

  • 创建Hello World模块
  • 配置模块的路由规则
  • 为路由创建动作控制器

创建Hello World模块

首先,我们先为该模块创建好目录结构:

app/code/local/Magentotutorial/Helloworld/Block

app/code/local/Magentotutorial/Helloworld/controllers

app/code/local/Magentotutorial/Helloworld/etc

app/code/local/Magentotutorial/Helloworld/Helper

app/code/local/Magentotutorial/Helloworld/Model

app/code/local/Magentotutorial/Helloworld/sql

然后为这个模块创建配置文件,配置该配置文件路径位于,

app/code/local/Magentotutorial/Helloworld/etc/config.xml

并放入下列代码,

01020304050607<config>    <modules>        <Magentotutorial_Helloworld>            <version>0.1.0</version>        </Magentototurial_helloworld>    </modules></config>

和之前一样,需要创建一个文件激活该模块,该文件路径位于,

app/etc/modules/Magentotutorial_Helloworld.xml

放入如下代码,

0102030405060708<config>    <modules>        <Magentotutorial_Helloworld>            <active>true</active>            <codePool>local</codePool>        </Magentototurial_helloworld>    </modules></config>

最后,检查下我们是否成功建立Hello Wolrd模块并激活。

  • 清空Magento缓存
  • 在管理员界面中,点击System->Configuration->Advanced.
  • 打开”Disable Modules Output”
  • 大概在底部,会显示Magentotutorial_Helloworld模块

配置模块的路由规则

接着,我们开始配置路由规则,路由会将请求的URL地址分发到一个控制器和它的方法上。不像其它约定型的PHP MVC框架,在Magento中,你需要明确的在全局配置文件中配置路由规则,来告诉URL地址如何匹配对应的控制器和方法。

在config.xml文件中,加入下列代码,

0102030405060708091011<frontend>    <routers>        <helloworld>            <use>standard</use>            <args>                <module>Magentototurial_Helloworld</module>                <frontName>helloworld</frontName>            </args>        </helloworld>    </routers></frontend>

这块代码里边牵涉到很多术语,接下来一一解析。

<frontend>是什么?

该标签涉及到Magento的一个术语Area。可以将Areas视为一些独立的Magento应用。“frontend” Area是Magento购物车应用的前端表现。”admin” Area是后端管理员应用。”install” Area是用来安装Magento的应用。

为什么配置一个模块的路由要使用复数<routers>呢?

引用一句Phil Karlton关于计算机科学的著名论断:

There are only two hard things in Computer Science: cache invalidation and naming things

意思是说计算机科学中最棘手的两件事就是缓存验证和命名。和许多大型系统一样,Magento同样受到命名的困扰。在全局配置文件书中,可以看到许多这种甚至是丑陋的命名方式。<routers>便是其中之一。该标签通常会包括关于路由规则的配置信息,有时候又会包含实际的路由对象的配置信息。这种命名方式初看起来有些不爽,但是随着你对于Magento系统的深入学习,你会逐渐改变对它的看法。

<frontName>是什么?

当路由器解析URL时,会将URL分为以下几个部分,

http://example.com/frontName/actionControllerName/actionMethod/

通过在<frontName>标签中定义”helloworld”值,Magento就能够响应所有以下列URL访问的地址

http://example.com/helloworld/*

很多刚刚接触Magento的开发者都会将frontName与Magento的前端控制器对象搞混淆。实际上它们完全不是一回事。frontName只属于路由。

<helloworld>标签的作用?

该标签必须是当前模块名的小写形式。我们创建的模块是Helloworld,所以该标签应该是<helloworld>。可能你会注意到<frontName>标签中的值也于模块名一致。这个其实是一个不成为的规定,并非是必须一致的。在你的自定义模块中,最好使用模块名与命名空间的组合来命名,以避免命名冲突。

<module>Magentotutorial_Helloworld</module>

该标签的值必须是模块的全名,包含package/namespace名。该配置让系统能够正确定位到控制器文件。

创建动作控制器

完成配置文件之后,接下来需要做的就是创建控制器文件及类。在以下路径创建该类,

app/code/local/Magentotutorial/Helloworld/controllers/IndexController.php

包含以下内容,

0102030405classMagentotutorial_Helloworld_IndexController extendsMage_Core_Controller_Front_Action {    publicfunctionindexAction() {        echo'Hello Index';    }}

清空缓存,访问下列地址,

http://exmaple.com/helloworld/index/index

也可以访问以下地址,

http://exmaple.com/helloworld/index/index

http://exmaple.com/helloworld/

如果没有出错的话,页面中应该能显示”Hello World”。恭喜你,你已经成功搞定了第一个Magento控制器。

动作控制器文件路径

控制器应该放在模块的controllers文件夹中,系统会自动在这个路径中寻找控制器。

动作控制器命名方式

还记得在config.xml配置文件中的<module>标签吗?

01<module>Magentotutorial_Helloworld</module>

动作控制器的命名应该遵循以下规则,

  • 以配置文件<module>标签中的值起头(Magentotutorial_Helloworld)
  • 紧接着一个下划线(Magentotutorial_Helloworld_)
  • 再接着是该控制器的名字(Magentotutorial_Helloworld_Index)
  • 最后,加上Controller(Magentotutorial_Helloworld_IndexController)

所有的Magento控制器都是继承自Mage_Core_Controller_Front_Action类。

index/index路径

上文中提到过,Magento的URL地址按照以下规则进行路由解析,

http://example.com/frontName/actionControllerName/actionMethod/

那么在下面这个地址中,

http://example.com/helloworld/index/index

URI中的”helloworld”是frontName,后面两个index分别是调用的控制器及方法名,即调用helloworld模块中的IndexController控制器中的indexAction方法。

如果URL地址中缺少控制器及方法部分,Magento默认使用index,所以下面地址访问的页面是相同的。

http://example.com/helloworld/index

http://example.com/helloworld

如果访问的URL地址如下,

http://example.com/checkout/cart/add

Magento会做如下操作,

  • 在全局配置文件中找到使用frontName为checkout的模块(Mage_Checkout)
  • 继续查询cart控制器(Mage_Checkout_CartController)
  • 调用cart控制器下的addAction()方法

控制器路径

让我们试着添加一个非默认方法到控制器中,添加如下代码到IndexController.php

010203publicfunctiongoodbyeAction() {    echo'Goodbye World';}

然后访问下面的URL地址:

http://example.com/helloworld/index/goodbye

IndexController继承自Mage_Core_Controller_Front_Action类,有很多方法可以直接使用。例如,除上述URI中提到的三部分之外,其它部分会自动传给一个键值对数组。添加如下代码到IndexController中。

0102030405060708publicfunctionparamsAction() {    echo'<dl>';    foreach($this->getRequest()->getParams() as$key=> $value) {        echo'<dt><strong>Param: </strong>'.$key.'</dt>';        echo'<dt><strong>Value: </strong>'.$value.'</dt>';    }    echo'</dl>';}

完成之后访问下面的URL地址

http://example.com/helloworld/index/params?foo=bar&baz=eof

一切正常的话,页面中会显示该URL地址中的参数。最后,对于下面这个URL地址,系统应该如何响应呢?

http://example.com/helloworld/messages/goodbye

该URL中,控制器的名字是messages,所以我们需要创建一个MessagesController控制器,在以下路径创建该文件

app/code/local/Magentotutorial/Helloworld/controllers/MessagesController.php

接着在该控制器中添加goodbyeAction()方法

010203publicfunctiongoodbyeAction() {    echo'Another Goodbye';}

关于Magento控制器的介绍已经完成了。虽然看起来比其他的PHP MVC框架要复杂许多,它的高度可扩展性却允许你创建任何你想要的URL结构。

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注