Magento开发系列之一 基础知识

代码模块化

Magento采用Model-View-Controller(MVC)架构,Controller, Model都会放在单独的文件夹里,文件会根据功能进行分组,这种分组在Magento中称为模块(module)。

magento

在Magento中通常一个模块会包含 Controllers, Models, Helpers, Blocks等目录,比如app/code/core/Mage/Checkout模板下的文件夹结构:


想要修改或继承Magento的代码时,不应在系统core代码上进行修改,而应在local或community代码池内新建一个模块

1 app/code/local/Modulename

代码包(Package,或称为命名空间 – Namespace)用于区分开发代码的公司和组织,进而避免在进行代码分享时出现相互覆盖的情况。新建好模块后,还应在app/etc/modules内添加一个XML文件用于告知Magento你新建模块的位置。这个XML文件可用于指定单一模块,命名格式为Packagename_Modulename.xml,也可用于指定命名空间内的多个模块,命名格式为Packagename_All.xml,比如在app/etc/modules内在Mage_All.xml文件。但是并不推荐在单个文件中指定多个模块,因为这样就失去了分开定义模块的意义。

基于配置的MVC

Magento是一套基于配置文件(configuration-based)的MVC系统,有别于传统的基于惯例(convention-based)的MVC系统。在基于惯例的MVC中,添加一个控制器或模型,只需创建一个文件或类,系统会自动运行。


而在像Magento这样的基于配置文件的MVC系统中,除了添加新文件或类外,还需要告知系统所创建的类或类群的名称,就是通过每个模块中的config.xml文件。
比如要在自己写的模块中使用一个模型,就需要在config.xml中添加一些代码告诉Magento你要使用模型以及基类的名称

1 <models>
2      <packagename>
3           <class>Packagename_Modulename_Model</class>
4     <packagename>
5 </models>

Helpers, Blocks, Routes for your Controllers, Event Handlers等也同理,几乎在Magento系统中添加任何东西都需要在配置文件中做类似修改。

控制器

在任何PHP系统中,主入口文件仍然是PHP文件,因而Magento的主入口文件也不例外,具体的说就是index.php这个文件。但绝不要修改index.php文件,在MVC系统中,index.php用于

  1. 检测URL地址
  2. 基于既定规则,将URL解析成控制器类和Action方法(这步称为Routing)
  3. 实例化控制器类并调用Action方法(这步称为dispatching)

也就是说在Magento或其它的MVC系统入口是控制器文件中的一个方法,那么http://example.com/catalog/category/view/id/25这个URL会被解析为

Front Name: catalog

URL中的第一段称为前台名称(front name),这会告知Magento在哪个模块去查找控制器,本例中前台名称就是catalog,对应的路径是app/code/core/Mage/Catalog

Controller Name:category

紧接着的部分告知Magento应使用哪个控制器,每个模块中都有一个controllers文件夹用于存放该模块的控制器文件,本例中对应的文件是

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

其中的内容类似:

1 class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
2 {
3 }

所有Magento前台的应用都继承Mage_Core_Controller_Front_Action这个类

Action Name:view

URL的第三部分为动作名,本例中的view用于创建动作方法,具体对应viewAction

1 class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
2 {
3     public function viewAction()
4     {
5         //main entry point
6     }
7 }

熟悉Zend框架的朋友对于这种命名规则一定也不会陌生。

Paramater/Value – id/25

在动作名后面的部分会被作为GET方法传入的键值对,本例中的id/25代表GET变量名为id,值为25。前面也提到如果要模块使用控制器的话,需在config文件中进行添加,下面就是在Catalog模块中应用控制器的代码

1 <frontend>
2     <routers>
3         <catalog>
4             <use>standard</use>
5             <args>
6                 <module>Mage_Catalog</module>
7                 <frontName>catalog</frontName>
8             </args>
9         </catalog>
10     </routers>
11 </frontend

可能你现在还不理解上面各标签的具体意义,不过不用担心,后面会详细的说明。注意catalog这段是在URL中链接到指定模块的前台名。通常Magento的core模块中会使用与模块名相同的前台名称,但并非强制要这样做。

 多路由(Routers)

前面所说的routing是针对Magento的cart应用(常称为前台frontend),当Magento在URL未发现有关的Controller/Action时,会再次尝试一套针对Admin应用的Routing规则,如果再次失败,则会使用一个名为Mage_Cms_IndexController的特殊控制器。
CMS控制器查找Magento的内容管理系统,来确定是否有什么内容可以加载,如果还找不到,则会返回404页面。

基于环境的URI模型载入

我们已经了解到Action方法的入口,下一步就是实例化类进行具体操作了,Magento有一套特别的方法来实例化Models, Helpers和Blocks,也就是全局的Mage类中的静态工厂方法,例如:

1 Mage::getModel('catalog/product');
2 Mage::helper('catalog/product');

catalog/product称作一个类群名称,也常被称作URI,前面部分catalog用于查找类位于哪个模块中,后面部分product用于指定要加载的类,所以本例中两段代码都会被解析到app/code/core/Mage/Catalog这个模块,也就是类名称会以Mage_Catalog开头。然后Product会被加到类名称的最后

1 Mage::getModel('catalog/product');
2 Mage_Catalog_Model_Product
3  
4 Mage::helper('catalog/product');
5 Mage_Catalog_Helper_Product

这些规则由模块中的config文件来限定,在自己创建模块时,会使用你自己的类群来执行Mage::getModel(‘myspecialprefix/modelname’);

实例化时不强制使用类群名称,但后面我们会讲到这样做会有诸多好处。

Magento的模型

和大多数框架一样,Magento提供对象关系映射(ORM)系统。ORM把你从书写复杂的SQL语句中解放出来,仅通过PHP代码就可以操作数据库,比如:

1 $model = Mage::getModel('catalog/product')->load(27);
2 $price = $model->getPrice();
3 $price += 5;
4 $model->setPrice($price)->setSku('SK83293432');
5 $model->save();

 

上例中我们调用了getPrice和setPrice方法,但在Mage_Catalog_Model_Product类中并没有这些方法,这是因为ORM使用了PHP中的魔术方法__call来实现getters和setters。调用$product->getPrice()方法会get模型属性price,调用$product->setPrice()方法会set模型属性price。以上的分析建立在没有名称为getPrice或setPrice的方法的基础上,如果有这两个方法就不会执行魔术方法。感兴趣的朋友可以查看一下Varien_Object类,所有的模型都继承这个类。

如果想要获取所有的模型数据,可以调用$product->getData()方法,将会返回一个包含所有属性的数组。同时也可以连接多个set方法:

1 $model->setPrice($price)->setSku('SK83293432');

那是因为每个set方法返回一个模型的实例,这种使用在Magento的代码中随处可见。Magento的ORM还可以通过一个Collections接口来查询多个对象,以下代码将获取一个包含成本价为$5的产品集合

1 $products_collection = Mage::getModel('catalog/product')
2 ->getCollection()
3 ->addAttributeToSelect('*')
4 ->addFieldToFilter('price','5.00');

这里同样用到了连接,Collections使用PHP的标准库来实例化包含属性数组的对象

1 foreach($products_collection as $product)
2 {
3     echo $product->getName();
4 }

你可能会奇怪addAttributeToSelect方法有什么作用,Magento有两类Model对象,一种是传统的一个对象对应一张数据表的模型,在实例化这种模型时,会选取所有的属性。另一种则是Entity Attribute Value (EAV) 模型,EAV模型中数据散布在数据库中的不同表格内,这样产品属性会非常灵活,每次添加一个属性都无需修改schema。在创建EAV对象集合时,Magento会查询有限的列,所以需要使用addAttributeToSelect来获取指定的列,或者通过addAttributeToSelect(*)来查询所有的列。

Helpers

Magento的Helper类包含操作对象和变量的工具方法,比如:

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

你可能已经注意到这里并没有包含类群的第二部分,每个模块都一个默认的Data帮助类,所以上面的代码相当于

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

通常Helpers继承Mage_Core_Helper_Abstract类,也就默认获取到几个有用的方法

1 $translated_output = $helper->__('Magento is Great'); //gettext style translations
2 if($helper->isModuleOutputEnabled()): //is output for this module on or off?

 Layouts

上面我们谈到了Controllers, Models以及Helpers,在一个典型的PHP MVC系统中,在操作完模型后,就会

  • 为view设定一些变量
  • 系统会加载默认的外部HTML布局
  • 系统加载外部布局内的view

不过在查看典型的Magento的控制器时,并没有发现任何如下内容

1 /**
2  * View product gallery action
3  */
4 public function galleryAction()
5 {
6     if (!$this->_initProduct()) {
7         if (isset($_GET['store']) && !$this->getResponse()->isRedirect()) {
8             $this->_redirect('');
9         } elseif (!$this->getResponse()->isRedirect()) {
10             $this->_forward('noRoute');
11         }
12         return;
13     }
14     $this->loadLayout();
15     $this->renderLayout();
16 }

取而代之的是以下两个调用

1 $this->loadLayout();
2 $this->renderLayout();

从这我们已经可以看出Magento中的V有别于通常所见到的的MVC,需要通过代码指明渲染布局。布局本身也是有区别的,Magento的布局是一个包含Block对象的集合。每个Block对象会去渲染一段特定的HTML,每个Block对象是PHP代码的混合,包含.phtml模板文件中的PHP代码。Blocks对象用于与Magento系统进行交互获取Models中的数据,而phtml模板文件会生成页面所需的html代码。
例如页面头部Block app/code/core/Mage/Page/Block/Html/Head.php使用到了page/html/head.phtml文件。
也可以这么认为,Block类是一个小型的控制器,.phtml就是MVC中的view。

1 $this->loadLayout();
2 $this->renderLayout();

默认情况下调用以上代码时Magento会载入一个网站结构框架的Layout,结构框架中会包含html, head和body标签以及单列或多列的Layout,另外还会有一些导航所用的内容Block以及默认欢迎信息等。
结构和内容是在Layout系统中人为指定的,Block并不能在程序上判定这是一个结构还是内容,但有助理解上的区分。
在Layout中添加内容,需要告诉Magento

1 &quot;Hey, Magento, add these additional Blocks under the &quot;content&quot; Block of the skeleton&quot;

1 &quot;Hey, Magento, add these additional Blocks under the &quot;left column&quot; Block of the skeleton&quot;

程序上可以在控制器action中使用

1 public function indexAction()
2 {
3     $this->loadLayout();
4     $block = $this->getLayout()->createBlock('adminhtml/system_account_edit')
5     $this->getLayout()->getBlock('content')->append($block);
6     $this->renderLayout();
7 }

但是通常(至少在前端应用中如此)会在Layout系统中使用XML文件实现。主题中的Layout XML文件可在控制器的基础上删除通常会被渲染的Blocks或者在框架中添加Blocks。例如下面的Layout XML文件:

1 <catalog_category_default>
2     <reference name=&quot;left&quot;>
3         <block type=&quot;catalog/navigation&quot; name=&quot;catalog.leftnav&quot; after=&quot;currency&quot; template=&quot;catalog/navigation/left.phtml&quot;/>
4     </reference>
5 </catalog_category_default>

上面代码的意思是在catalog模块category控制器内的default Action,将left结构Block中插入一个catalog/navigation Block,作用的模板是catalog/navigation/left.phtml。关于Blocks的最后一个重要的事情,你会在模板中看到类似下面的代码:

1 $this->getChildHtml('order_items')

这是Block渲染内嵌Block的方法,但仅有在子Block在Layout XML文件中作为内嵌Block,该Block才能渲染这个子Block,也就是说left.phtml中的$this->getChildHtml()会返回空,但如果代码是下面这样:

1 <catalog_category_default>
2     <reference name=&quot;left&quot;>
3         <block type=&quot;catalog/navigation&quot; name=&quot;catalog.leftnav&quot; after=&quot;currency&quot; template=&quot;catalog/navigation/left.phtml&quot;>
4             <block type=&quot;core/template&quot; name=&quot;foobar&quot; template=&quot;foo/baz/bar.phtml&quot;/>
5         </block>
6     </reference>
7 </catalog_category_default>

在catalog/navigation Block中,就可以调用

1 $this->getChildHtml('foobar');

 Observers

Magento和所有良好的面向对象系统一样,为终端用户提供了Event/Observer。在页面请求中发生一些动作(如保存模型,用户登录)时,Magento会发出一个事件信号。

那么在创建自己的模块时,就可以监听这些事件,比如要在客户登录时获取邮箱,就应在config.xml进行相关配置来监听customer_login事件

1 <events>
2     <customer_login>
3         <observers>
4             <unique_name>
5                 <type>singleton</type>
6                 <class>mymodule/observer</class>
7                 <method>iSpyWithMyLittleEye</method>
8             </unique_name>
9         </observers>
10     </customer_login>
11 </events>

然后写一段在客户登录时运行的代码:

1 class Packagename_Mymodule_Model_Observer
2 {
3     public function iSpyWithMyLittleEye($observer)
4     {
5         $data = $observer->getData();
6         //code to check observer data for out user,
7         //and take some action goes here
8     }
9 }

类重载

最后,Magento系统允许将core模块中的Model, Helper和Block类替换成自己的代码,这有些类似于Ruby或Python中的Duck Typing或Monkey Patching。
为便于理解,这里举个例子。产品的模型类是Mage_Catalog_Model_Product,在调用下面这段代码时就会创建Mage_Catalog_Model_Product对象

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

Magento的类重载系统允许你和系统间进行下面的对话

1 &quot;Hey, whenever anyone asks for a catalog/product, instead of giving them a Mage_Catalog_Model_Product,
2 give them a Packagename_Modulename_Model_Foobazproduct instead&quot;.

如果你愿意,可以在Packagename_Modulename_Model_Foobazproduct类中继承原来的产品类

1 class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
2 {
3 }

这允许你修改该类中的任何方法,同时保存其它已有方法的功能

1 class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
2 {
3  public function validate()
4  {
5      //add custom validation functionality here
6      return $this;
7  }
8  
9 }

你可能已经猜到,这种重载(或者说重写)是在config.xml文件中实现的

1 <models>
2     <!-- does the override for catalog/product-->
3     <catalog>
4         <rewrite>
5             <product>Packagename_Modulename_Model_Foobazproduct</product>
6         </rewrite>
7     </catalog>
8 </models>

这是需要提一下的是,你所写的模块中的某一个类重载另一个模块中的某一个类时,并不会重载整个模块。这就保证了在对某些方法进行修改时不必担心该模块中其它的内容。

magento         麦进斗中国



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值