MAgento开发文档(一):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 Moudules代码组织形式
  • 配置mvc架构
  • magento控制器(Manfento Controllers)
  • 基于URI的模型实例化(Context-based URI Model Loading)
  • magento模型(magento models)
  • Magento助手(Magento Helpers)
  • mangento布局(mangento layouts)
  • 事件监听(Observers)
  • magento类重写(Class Overrides)
  • 总结

Magento模块中的代码组织形式

magento通过将代码放入独立模块进行组织.在一个典型php mvc应用中,所有的控制器会被放在一个文件夹中,所有的模型会被放在另外一个文件夹里,等等,而在magento中,文件是基础功能进行分组的,这种分组后代码佳作模块

magento的代码:

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

app/code/core/Mage/Checkout

如果你想寻找magento中关于gooole checkout的概念,也仅仅需要找到如下文件夹,即可获取所有你想要的信息

app/code/core/Mage/GooleCheckout

你的代码:

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

app/code/local/<packge>/<Modulename>

packge(也可称为命名空间,当然这不是php手册中提到的命名空间)是唯一的命名,通过Packge来表示你的公司组织活个人.通过packge,世界范围内的mangento社区在创建模块拓展时,能够使用他们自己的packge名称,以避免与其他开发者有命名冲突

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

app/etc/modules

在这个目录中有俩类xml文件,第一种用来开启独立,以下列命名:

Packagename_Modulename.xml

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

Packgename_ALL.xml

配置mvc系统

magento是一个配置型mvc(Configuration-based MVC)系统.另外一种MVC系统则是大部分php框架使用的约定性mvc(convertion-based mvc)在约定型mvc系统中,如果你添加一个控制器,或者一个模型,只需要根据约定的内容,创建这个文件即可,系统会自动识别它

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

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

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

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

magento控制器(Magento Controllers)

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

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

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

这意味着mangento(或任何mvc系统)每一个entry point(入口点)都是控制器文件中的一个方法.一起来看下面这个url

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

上述域名和url地址可以被拆分为以下几个部分.

front name-catlog

该url的第一部分被称为fornt name.他用来只是magento一个在那个模块中寻找url中的控制器.在这个例子中,catlog就是frontname,对应catlog模块

controller name - category

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

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

其中的格式大概定义为

class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
{
}

在magento中所有的控制器都继承自Mage_Core_controller_Front_Action类

Action-Name-view

第三部分是action的名称.在此url中view便是一个action方法的名字

class Mage_Catalog_categoryController extends Mage_Core_controller_Front_Action{
    public function viewAction(){
    
    }
}

paramater/Value-id/25

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

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

<frontend>
    <routers>
        <catlog>
            <use>standard</use>
            <args>
                <module>Mage_Catlog</module>
                <frontName>catlog</frontName>
            </args>
        </catlog>
    </routers>
</frontend>

<frontName>catlog</frontName>这是用来关联模块与url地址中frontname的.magento核心代码选择将一个模块的名字与frontname一致,但这不是强制规定

多路由

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

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

上下文模型读取

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

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

'catlog/prodict'字符串被称为Grouped Class Name(分组的类名).通常叫做URI.Grouped Class Name的第一部分用来只该类放在哪个模块中.第二部分用来决定哪个类将被调用.

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

Mage::getModel('catalog/product');
Mage_catalog_model_product;

Mage::helper('catalog/product');
Mange_Catalog_Helper_Product;

magento模型

和现在的多数框架一样,Magento也提供ORM支持,ORM能让你专注数据,而不是无尽的sql语句

$model=Mage::getModel('catalog/product')->load(27);
$proce=$model->getProce();
$price+=5;
$model->setPrice($price)->setSku('SK123123');
$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($prive)->setSku('Sk123123');

能够使用这种方法,最主要的原因是所有的set方法都会返回一个模型的实例.你会经常在magento的核心代码中看到此类的调用方法和形式.magento的orm系统还包含一种通过collections接口查询多个对象的方式.下列会读取系统中所有5美元的产品

$product_Collection=Mage::getModel('catalog/product')
->getCollection()->addAttributetoSelect('*')->addFieldToFilter('prive','5.00');

这里吗有一次看到了链接调用的方法形式

foreach($products_collection as $product){
    echo $product->getName();
}

在上面例子中,你看注意到了addAttributeToSelect方法.这里单独提到次方法,是因为它代表了magento模型中的一个类别.magento拥有俩种形式的模型对象.一个是传统的一个对象一张表的active record模型.当你实例化这些模型的时候,所有的属性都会被自动选取。

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

magento helpers 助手

magento的助手包含一系列实用的方法,通过这些方法可以对对象及便利做日常性的操作,例如

$helper=Mage::helper('catlog');

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

$helper=Mage::helper('catlog/data')

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

$translated_output=$helper->__('Magento is Great');
if($helper->isModuleOutputEnabled(){

}

Magento Layout布局

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

  • 传递那两到视图中
  • 系统将会自动读取默认的外层布局
  • 接着讲视图读取到外层布局居中

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

public function galleryAction() {
    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/Blcok/Html/Head.php使用与其对应的page/html/head.phtml模板文件

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

默认的,当你调用

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

magento会读取网站的布局框架,这些结构blocks用来输出head,body以及设定单栏或多栏的布局.另外还有一些内容blcoks负责实际输出head,body以及设定单栏或多栏的布局,另外还有一些内容blocks负责实际输出像导航,产品分类

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

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

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

public function indexAction(){
    $block=$this->getLayout()->createBlcok('adminhtml/system_Account_edit');
    $this->getLayout()->getBlock('content')->append($block)
}

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

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

<catalog_category_default>
    <reference name="left">
        <block type="catlog/navigation" name="vatlog.leftnav"
        after="currency" template="catlog/navigation/left.phtml"/>
    </reference>
</catalog_category_default>

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

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

$this->getChildHtml('foobar');

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

例如在xml布局文件中

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

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

Observers观察者

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

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

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

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

class Packagename_Mymodule_Model_Observer{
    public function iSpyWithMyLittleEye($observer){
        $data=$observer->getData();
    }
}

Class Overrides类的复写

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

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

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

这是工厂模式.magento的类复写系统运作模式大概就是这样

magento如果有对应请求catalog/product,不要实例化Mage_catalog_Model_Product,让PackagenameModulename_Model_Foobazproduct接手

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

class Packagename_Modulename_Model_Foobazproduct extends Mage_Catlog_Model_Product{
    
}

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

class Packagename_Modulename_Model_foobazproduct extends Mage_Catalog_Model_Product{
    public function validate(){
        //添加一些自定义的功能
        return $this;
    }
}

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

<modules>
    <modulename>
        <class>Packagename_Modulename_Model</class>
    </modulename>
    <catalog>
        <rewrite>
            <product>Packagename_Modulename_Model_Foobazproduct></product>
        </rewrite>
    </catalog>
</modules>
Last modification:April 20, 2022
如果觉得我的文章对你有用,请随意赞赏