Magento开发者基础知识

在这一章节里,我们将会了解一些magento开发的基础概念,其中包括magento的结构,并且我们将领会Magento极其灵活的模块化结构。

magento是一个极其灵活和强大的系统,但另一方面,这也加大了开发的复杂性,目前一个全新安装的magento大约有30000个文件,大约120万行代码。

也许你会被这强大的系统所吓到,但你别担心,这一章里我们将会介绍一些基本的概念和用于扩展magento的工具,在接下来的章节里,我们将进行深层次的了解magento的model和数据集合。

magento文件结构

相对传统的MVC模式的应用而言,magento的文件结构略有区别。我们首先来了解下它的文件目录及其功能:
  • app:这个目录是magento的核心文件夹,由3部分构成。
    • code:这里包含3个文件夹:core,conmmunity,local,在接下来的章节里会一一介绍。
    • design:magento的模板和布局文件都存放在这里。
    • locale:翻译文件以及e-mail模板文件存放位置。
  • js:在magento中使用的javascript库。
  • media:产品和cms页所使用的媒体文件。
  • lib:这里包含第三方的类库,比如Zend、PEAR,magento开发的库也在这个文件夹中。
  • skin:CSS、javascript文件。
  • var:一些临时数据,比如cache、session以及一些错误文件。
每个magento的文件结构可能看起来像下面这样:


我们来简单看下这些目录的含义。
  • Block:存放连接controller和view之间的逻辑代码。
  • controllers:存放网站的控制action。
  • Controller:存放一些抽象类和controllers中继承的类。
  • etc:存放系统配置文件,比如config.xml、system.xml等等。
  • Helper:存放一些辅助类,一般来说,这些文件包含一些公共的函数和模块以及一些供其它模块使用的类。•
  • Model:提供用于控制器和数据之间的交互的模块。
  • sql:存放包括数据库初始化或数据库的升级文件脚本。
在接下来的章节中,你将对这些模块有一个更清晰的认识。由于Magento大量使用工厂方法和工厂名,因此,这些目录结构相当重要。
模块化结构
Mageno由大量小的模块构成,每个在整个应用程序中具有自己的作用。这样做的好处在于可以轻松的禁用和启用这些模块,也可以方便的添加和修改每个模块的方函数和方法。

自动加载类

Magento是由大约30000个文件构成的庞大框架,当应用程序在启用时,加载每一个单独的文件会使速度相当的慢,因此magento在需要使用某一个文件时会使用自己加载类加载这个方法所使用的类。
什么是自动加载类呢?在PHP5中有这样一个魔术方法__autoload(),当实例化一个类时,这个方法会被自动调用,在这个方法里,可以将要使用的文件或类加载进来。
我们来看一下在magento中位于app/Mage.php的引导文件的代码,

Mage::register('original_include_path', get_include_path());
if (defined('COMPILER_INCLUDE_PATH')) {
$appPath = COMPILER_INCLUDE_PATH;
set_include_path($appPath . PS .
Mage::registry('original_include_path'));
include_once "Mage_Core_functions.php";
include_once "Varien_Autoload.php";
} else {
/**
* 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";
}
Varien_Autoload::register();
这个引导文件定义了文件包含的路径以及初始化Varien(译者注:magento开发的一种类型)的自动加载类。我们来看下这个Varien中的autoload函数做了些什么。

/**
* Load class source code
*
* @param string $class
*/
public function autoload($class)
{
if ($this->_collectClasses) {
$this->_arrLoadedClasses[self::$_scope][] = $class;
}
if ($this->_isIncludePathDefined) {
Magento Fundamentals for Developers
[ 32 ]
$classFile = COMPILER_INCLUDE_PATH .
DIRECTORY_SEPARATOR . $class;
} else {
$classFile = str_replace(' ', DIRECTORY_SEPARATOR,
ucwords(str_replace('_', ' ', $class)));
}
$classFile.= '.php';
//echo $classFile;die();
return include $classFile;
}
这个自动加载类中仅使用了一个工厂方法所提供的$class参数的别名,这个别名用来匹配类名,然后将这个类包含进来。
前面我们已经提到,由于Magento的工厂结构需要使用类名来确定路径,因此magento的目录结构非常重要,在接下来的章节里我们可以看到我们的模块都遵守这一原则。

代码池

前面已经提到,在app/code目录里存放我们的应用程序代码,目前的magento版本主要有以下三个文件目录:

  • core:这个目录是magento的核心代码,提供了应用程序运行的基本代码。对于开发者来说,任何情况下都不要修改core文件夹里的内容,因为这可能导致不能正常工作,并且不能正常升级。
  • community:这个目录是第三方模块存放位置,通过magento connect下载安装的模块也存在这个位置。
  • local:开发者自行开发的模块或扩展、修改核心模块的代码都应当存放在这个目录下。

这些目录用来区别模块的来源以及用来确定这些模块被加载的顺序。如果我们再看下我们的引导文件app/Mage.php,我们可以看到代码池被加载的顺序:

$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';

也就是说,在类被请求时,magento首先会查找local,然后是community,最后才是core,以及对lib目录的引用。

虽然可以把core里的文件复制到local里进行修改,但我们不建议你这样做。

路由和数据请求过程

在我们了解更多magento的其它部分前,我们得先了解下magento中的各个部分之间是怎样交互和工作的,我们也应该了解magento从web服务器上请求的整个过程。

像其它的PHP程序一样,magento为每一个请求只提供了一个单一入口index.php,这个文件用来加载Mage.php这个引导文件,主要有以下几步:

1.        web服务器在接收到请求后,magento中的引导文件Mage.php将会被实例化。

2.        前端控制器被实例化,并且magento在这个过程中实例化路由。

3.        Magento遍历整个路由,取得访问的方法。

4.        实例化匹配到的控制器和方法。

路由对于magento来说非常重要,Router对象被前端控制器用来匹配URL中的控制器和方法。Magento自带有3个Router对象:

  •   Mage_Core_Controller_Varien_Rounter_Admin
  •   Mage_Core_Controller_Varien_Router_Standard
  •   Mage_Core_Controller_Varien_Router_Default

控制器接下来会加载并且渲染整个页面的布局,在这个过程中,也就把需要使用的blocks\models\templates加载进来。

我们来分析下对于下面这一条请求的路由:http://localhost/catalog/category/view/id/10

首先,magento的路由看起来应该是由3部分构成/前端名/控制器名/方法名。

这个路由中,我们可以把它分为3个部分。

前端名:catalog

控制器名:category

方法名:view

如果我们看下magento中的router类(Mage_Core_Controller_Varien_Router_Standard中),我们可以看到match()方法如下:

public function match(Zend_Controller_Request_Http $request)
{
…
$path = trim($request->getPathInfo(), '/');
if ($path) {
$p = explode('/', $path);
} else {
$p = explode('/', $this->_getDefaultPath());
}
…
}

从这个方法中,我们可以看到,router首先是试图把URI解析成一个数组,在上面这个实例的路由中,这个路由将会被解析成像下面这样的代码片段:

$p = Array
(
[0] => catalog
[1] => category
[2] => view
)

这个函数中接下来将会检查请求的模块是否已经定义,如果不存在,magento将试图取数组中的第一个元素作为模块名的基准,如果模块名没有定义,这个函数将返回false,接下来我们来看下代码片段:

// get module name
if ($request->getModuleName()) {
$module = $request->getModuleName();
} else {
if (!empty($p[0])) {
$module = $p[0];
} else {
$module = $this->getFront()->getDefault('module');
$request->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, '');
}
}
if (!$module) {
if (Mage::app()->getStore()->isAdmin()) {
$module = 'admin';
} else {
return false;
}
}

接下来将会根据匹配的模块去匹配控制器和方法,代码片段如下:

foreach ($modules as $realModule) {
$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->_validate
ControllerClassName($realModule, $controller);
if (!$controllerClassName) {
continue;
}
// instantiate controller class
$controllerInstance = Mage::getControllerInstance
($controllerClassName,
$request, $front->getResponse());
if (!$controllerInstance->hasAction($action)) {
continue;
}
$found = true;
break;
}
...

这些代码也许看起来很痛苦,我们把它分解来看,这个代码块第一部分是查找是否存在请求中的控制器名字,如果没有,则在$p数组(上文解析获得的)中查找第二个元素,同样的方法名也按照这样的方法查找。

如果我们进一步了解循环中的代码,我们可以看到,magento用控制器名、模块名、方法各、来匹配这个控制器类的名称:

$controllerClassName =$this->_validateControllerClassName($realModu

le, $controller);

这个函数不仅构造一个完整的类名,而且还验证这个类是否存在。在我们这个实例中,将会返回Mage_Catalog_CatagoryController

由于我们已经有了一个正确的类名,我们可以实例化控制器对象。如果你足够仔细的话,你也许发现了我们还没有对方法做任何事,是的,在接下来的内容里我们将会了解到。

我们实例化的Category控制器的下面有一个叫做hasAction()的方法,事实上,这个方去用来调用PHP中的is_callable()函数,在我们这个实例中,这个方法用来检查我们实例化的对象中是否有这个方法名,也就是viewAction()。

使用这些精心设计的匹配过程和foreach循环使得多个模块可以使用相同的前端名称。

我们用下面的图来展示整个过程:


了解了这些过后,我们发现http://localhost/catalog/category/view/id/10还是一个不够直观的URL,好的是,通过magento的rewrite功能,我们可以使用类似http://localhost/books.html的地址。

我们在接下来的内容里不妨多了解下magento的rewrite系统,了解magento怎样从URL中取得控制器名称和方法名。从Varien/Front.php文件中,我们可以看到控制器的dispatch函数里,magento调用了下面的方法:

Mage::getModel('core/url_rewrite')->rewrite();

在真正了解rewrite功能的内部工作原理之前,我们来看下core/url_rewrite模块的结构:

Array (

["url_rewrite_id"] =>"10"

["store_id"] => "1"

["category_id"] =>"10"

["product_id"] => NULL

["id_path"] =>"category/10"

["request_path"] =>"books.html"

["target_path"] =>"catalog/category/view/id/10"

["is_system"] => "1"

["options"] => NULL

["description"] => NULL

)

我们可以看到,这个rewrite模块由几个元素构成,但是我们只对两个元素感兴趣:request_path、target_path。简言之,rewrite模块就是把请求的路径信息用来匹配真实的路径信息。

Magento的MVC模式

也许你对于一些传统的MVC模式的框架有一定的了解,比如CAKEPHP,你应该知道,如果你要添加一个新的模块或者我们说的控制器,你需要创建这个文件或者类,这个MVC框架会自动去加载它。

Magento具有另一种形式的配置文件,也就是说,如果我们增加了自己的模块,我们不仅要创建这个文件或者类,还需要明确的要配置文件中进行声明。

每个magento的模块都有一个配置文件:config.xml,这个文件位于etc文件夹下,这个文件还包含这个模块需要使用的相关配置,比如说,我们想要添加一个新的包含model的模块,我们需要在config.xml的一个节点里声明,让magento知道在什么地方去寻找model。比如:

<global>
…
<models>
<group_classname>
<class>Namespace_Modulename_Model</class>
<group_classname>
</models>
...
</global>

尽管这看起来给我们增加了额外的工作内容,这也给了我们极大的灵活性,比如说,我们可以用rewrite节点来重写其它的类:

<global>
…
<models>
<group_classname>
<rewrite>
<modulename>Namespace_Modulename_Model</modulename>
</rewrite>
<group_classname>
</models>
...
</global>

Magento接下来将会加载所有的config.xml文件,并且在程序运行时把这些文件整合在一起,创建一个程序配置树。

另外,模块可能还包含有system.xml文件,这个文件主要用来做后台的配置,这也可以被终端用户用来配置系统。我们可以从下面的代码块了解:

<config>
<sections>
<section_name translate="label">
<label>Section Description</label>
<tab>general</tab>
<frontend_type>text</frontend_type>
<sort_order>1000</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<groups>
<group_name translate="label">
<label>Demo Of Config Fields</label>
<frontend_type>text</frontend_type>
<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>
<fields>
<field_name translate="label comment">
<label>Enabled</label>
<comment>
<![CDATA[Comments can contain
<strong>HTML</strong>]]>
</comment>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</
source_model>
<sort_order>10</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</field_name>
</fields>
</group_name>
</groups>
</section_name>
</sections>
</config>

我们把这些节点分开来看:

  •   Section_name:用来唯一确定我们的配置块名称,这个节点内的内容也是用来声明字段内容和分组。
  •   Group:就像这个单词的意思一样,这个节点内的内容是展示在手风琴样式的内容区域里的内容。
  •   Label:定义field、section、group的标题。
  •   Tab:用来定义section应该放在哪个位置。
  •   Frontend_type:用来声明我们选项以什么样的方式呈现,magento目前提供了以下几种内容供选择:
    •   Button
    •   Checkbox
    •   Checkboxes
    •   Date
    •   File
    •   Hidden
    •   Image
    •   Label
    •   Link
    •   Multiline
    •   Multselect
    •   Password
    •   Radio
    •   Select
    •   Submit
    •   Textarea
    •   Text
    •   Time
  •   Sort_order:用来声明位于field、group或者section中的位置
  •   Source_model:提供比如select类型的选项,magento已经在Mage/Adminhtml/Model/System/Config/Source下提供了几种有用的类,我们可以找到下面的类:
    •   YesNo
    •   Country
    •   Currency
    • category
    •   AllRegions
    • Language

仅仅使用XML文件,我们就可以完成复杂的配置,并且我们不需要担心设置模板来操作字段或验证数据。

Magento也提供了几个表单验证在字段,当我们想要验证数据时,使用<validate>标记可以完成,目前有以下几个验证的类型:

•validate-email

•validate-length

•validate-url

•validate-select

•validate-password

像magento其它的部分一样,我们可以继承source_model、frontend_type以及validator,甚至可以创建新的。在接下来的章节里学习到这些内容。译者注:为了更好的解释,在下面的内容里model\view\layout\controller均不进行翻译。

Models

Magento使用ORM的模式,尽管我们可以使用Zend_Db来接入数据库,但是我们在大多数时间下使用models来访问数据,magento提供了两种模式来访问数据库:

l  Simple models:这种模式下,使用一个对象对应一张表的方式,也就是说,我们的所以属性都应当在数据库表中有一一对应的字段。

l  EAV模型:这个模型使用动态的属性来描述实体。

Magento把model分为两个层面,一个层面是操纵业务逻辑,一个层面是操纵数据交互。这种设计模式使得magento可以在不修改model的内部逻辑的情况下使用多种数据库平台。

在接下来的章节里,我们将更详细的介绍magento的ORM模型。

Views

View层是magento真正同其它MVC框架有区别的地方,不像其它传统的MVC框架,view层被分为下面的三个部分:

l  Layouts:由XML文件构成,这里面定义了各个block之间的关系和属性,并且在这个XML文件中定义了使用的模板文件,magento中的每一个模块有自己的layout文件。

l  Blocks:在magento中这个部分负责了一些逻辑代码,用来减小控制器的负担。

l  Template:是一些PHTML文件,这些文件是HTML、PHP的混合代码。

接下来我们来剖析一个layout文件,使用catalog为例:

<layout version="0.1.0">
<default>
<reference name="left">
<block type="core/template" name="left.permanent.callout"
template="callouts/left_col.phtml">
<action method="setImgSrc">
<src>images/media/col_left_callout.jpg</src></action>
<action method="setImgAlt" translate="alt"
module="catalog"><alt>
Our customer service is available 24/7.
Call us at (555) 555-0123.</alt></action>
<action method="setLinkUrl">
<url>checkout/cart</url></action>
</block>
</reference>
<reference name="right">
<block type="catalog/product_compare_sidebar"
before="cart_sidebar" name="catalog.compare.sidebar"
template="catalog/product/compare/sidebar.phtml"/>
<block type="core/template" name="right.permanent.callout"
template="callouts/right_col.phtml">
<action method="setImgSrc">
<src>images/media/col_right_callout.jpg</src></action>
<action method="setImgAlt" translate="alt"
module="catalog"><alt>
Visit our site and save A LOT!</alt></action>
</block>
</reference>
<reference name="footer_links">
<action method="addLink" translate="label title"
module="catalog" ifconfig="catalog/seo/site_map">
<label>Site Map</label><url
helper="catalog/map/getCategoryUrl" />
<title>Site Map</title></action>
</reference>
<block type="catalog/product_price_template"
name="catalog_product_price_template" />
</default>

这个Layout文件由下面的一些XML标记构成:

l  Handle:每个页面有几个不同的handle,layout用这些handle来告诉magento哪些block需要被呈现,最常用的handle有:default\frontname_controller_action,default这个handle主要用来加载一些全局的block,比如说增加css、js。

l  Reference:这个标记主要用来修改已经存在的标记,比如,我们上面的代码里的<reference name="left">

l  Block:这个标记用来声明我们实际上需要使用的block,每个block都有下面的属性:

n  Type:由两部分构成,这个用来指定真正的block的类,比如catalog/product_list真正的类是;Mage_Catalog_Block_Product_List

n  Name:提供一个名称供其它模块调用

n  Before/after:用来指定这个block相对其它block的位置。

n  Template:这个属性用工指定该block使用.phtml模板文件,这个templae用来渲染该block

n  Action:一些前端的方法,比如;addjs和addCss

n  As:为这个block指定一个唯一的别名,比如,指定了该属性的block可以在模板文件中使用getChildHtml方法调用该block。

Block是magento为了减少加载控制器的代码的新概念,他们是数据同model直接交互的基础,如果需要,block获得model处理过的数据给view(视图)。

最后,我们来看一眼模板文件:

<div class="product-view">
...
<div class="product-name">
<h1><?php echo $_helper->productAttribute
($_product, $_product->getName(), 'name') ?></h1>
</div>
...
<?php echo $this->getReviewsSummaryHtml
($_product, false, true)?>
<?php echo $this->getChildHtml('alert_urls') ?>
<?php echo $this->getChildHtml('product_type_data') ?>
<?php echo $this->getTierPriceHtml() ?>
<?php echo $this->getChildHtml('extrahint') ?>
...
<?php if ($_product->getShortDescription()):?>
<div class="short-description">
<h2><?php echo $this->__('Quick Overview') ?></h2>
<div class="std"><?php echo $_helper->
productAttribute($_product, nl2br($_product->
getShortDescription()), 'short_description') ?></div>
</div>
<?php endif;?>
...
</div>
为了更系统的说明,我们使用下面的图表来展示magento中的block的关系。

Controllers

在magento中,控制器(controller)被设计得很小,仅包含少量的业务逻辑,这里的逻辑通常是受应用请求驱动。一个基本的magento控制器应该有下面的加载和渲染layout的方法:

public function viewAction()
{
$this->loadLayout();
$this->renderLayout();
}

控制器主要用于操作展示逻辑、从model中获取数据,并且处理这些数据传给view(视图)。

网站和店铺视图

Magento一个核心的功能就是支持多店铺和多网站,所有的视图可以用下图来表示:


类似产品、分类、属性以及配置都可以针对不同的视图来设置,也就是说,一个产品可以在两个不同的网站的设置不同的价格但其它的内容是一样的。

作为开发者,我们利用这些视图主要是在处理配置信息时,在magento的配置中,主要有以下几种视图可用:

l  Global:全局现图。

l  Website:主要通过域名来定义并且由一个或多个店铺构成,Website可以设置来用于分享客户信息或者完全分开。

l  Store:这个视图是用来管理产品和分类,并且对店铺进行分组,Store同样具有一个根目录,这也就允许我们在不同的店铺中具有不同的分类。

l  Store view:这个store view可以用来设置店铺的多语言。

这些配置的选项内容在3种视图下保存数据(global\website\store view),默认情况下,这些设置是针对global的,在system.xml文件中,我们可以明确的指定这些配置信息对哪个视图生效。

<field_name translate="label comment">
<label>Enabled</label>
<comment>
<![CDATA[Comments can contain <strong>HTML</strong>]]>
</comment>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>10</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</field_name>

工厂名和工厂方法

Magento使用工厂方法来实例化Model\Helper\Block类,工厂模式是一种可以使用别名来实例化真正在的类的模式。

Magento使用了下面的几种工厂模式:

l  Mage::getModel()

l  Mage::getResourceModel()

l  Mage::helper()

l  Mage::getSingleton()

l  Mage::getResourceSingleton()

l  Mage::getResourceHelper()

每个方法都使用类的别名作为参数来查找真正的类,并且对其进行实例化,比如说,如果我们想要实例化一个产品类,我们可以尝试使用下面的方法来实现:

$product = Mage :: getModel(‘catalog/product’);

你也许注意到了我们使用了groupname/modelname的参数,magento将把这个名称解析为Mage_Catalog_Model_Product,让我们来看一下这个getModel方法:

ublic static function getModel($modelClass = '', $arguments =
array())
{
return self::getConfig()->getModelInstance
($modelClass, $arguments);
}
getModel 调用了Mage_Core_Model_Config类中的 getModelInstance 方法。
class.
public function getModelInstance($modelClass='',
$constructArguments=array())
{
$className = $this->getModelClassName($modelClass);
if (class_exists($className)) {
Varien_Profiler::start('CORE::create_object_of::'.$className);
$obj = new $className($constructArguments);
Varien_Profiler::stop('CORE::create_object_of::'.$className);
return $obj;
} else {
return false;
}
}

这个类里的getModelInstance方法又调用了getModelClassName()方法,这个方法使用我们类的别名作为参数,然后它去验证这个类是否存在,如果这个类存在,则创建一个类的实例,并且返回给getModel()方法。

public function getGroupedClassName($groupType, $classId,
$groupRootNode=null)
{
if (empty($groupRootNode)) {
$groupRootNode = 'global/'.$groupType.'s';
}
$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};
$className = null;
if (isset($config->rewrite->$class)) {
$className = (string)$config->rewrite->$class;
} else {
if ($config->deprecatedNode) {
$deprecatedNode = $config->deprecatedNode;
$configOld = $this->_xml->global->
{$groupType.'s'}->$deprecatedNode;
if (isset($configOld->rewrite->$class)) {
$className = (string) $configOld->rewrite->$class;
}
}
}
if (empty($className)) {
if (!empty($config)) {
$className = $config->getClassName();
}
if (empty($className)) {
$className = 'mage_'.$group.'_'.$groupType;
}
if (!empty($class)) {
$className .= '_'.$class;
}
$className = uc_words($className);
}
$this->_classNameCache
[$groupRootNode][$group][$class] = $className;
return $className;
}

正如我们所看到的,getGroupedClassName才是真正做了所有工作的方法,它使用类的别名,并且创建一个按”_”分开的字符串的数组。

然后加载VarienSimplexml_element的实例,并且解析数组中的第一个值(group_name),如果这个类被rewriteen,将使用groupname来访问。

Magento也使用了uc_words()函数来将类别名中的首字母大写。

最后,getGroupedClassName()函数返回真实的类名给getModelInstance()函数,在我们实例中,返回Mage_Catalog_Model_Product。

用下面的图来表示整个调用过程:


事件和观察者

事件和观察者模式是magento中一种有趣的特色,这使得开发者可以在应用程序流中继承magento的某一部分。

为了给不同的板块提供更为灵活和容易的交互,magento实现了一种事件/观察者模式,这种模式使得magento的不同模块可以松散的耦合。

系统中主要有两部分,一部分是事件同对象和事件消息一同调度,一种是观察者一直监听所属的事件。


事件调度

事件使用Mage::dispatchEvent方法进行创建或者调度,magento核心开发团队早已经建立了几种事件。比如说,Mage_Core_Model_Abstract类在每次保存前调用两个protected方法:_beforeSave()和_afterSave()

protected function _beforeSave()
{
if (!$this->getId()) {
$this->isObjectNew(true);
}
Mage::dispatchEvent('model_save_before',
array('object'=>$this));
Mage::dispatchEvent($this->_eventPrefix.'_save_before',
$this->_getEventData());
return $this;
}
protected function _afterSave()
{
$this->cleanModelCache();
Mage::dispatchEvent('model_save_after',
array('object'=>$this));
Mage::dispatchEvent($this->_eventPrefix.'_save_after',
$this->_getEventData());
return $this;
}

每个函数激活一个通用的model_save_after事件和一个动态的、取决于对象类型的事件。这就给了我们一个极大的通过观察者模式来操作对象的能力。

Mage::dispatchEvent()方法使用了两个参数:第一个是事件名,第二是观察者所接收的数组数据。我们可以在这个数组中传递值或对象,我们可以很方便的操作对象。

为了更好的理解事件系统,我们来看下dispatchEvent函数:
public static function dispatchEvent($name, array $data = array())
{
$result = self::app()->dispatchEvent($name, $data);
return $result;
}
这个函数事实上是app类里的一个dispatchEvent()方法的别名,我们可以在Mage_Core_Model_App类中找到这个方法:

public function dispatchEvent($eventName, $args)
{
foreach ($this->_events as $area=>$events) {
if (!isset($events[$eventName])) {
$eventConfig = $this->getConfig()->
getEventConfig($area, $eventName);
if (!$eventConfig) {
$this->_events[$area][$eventName] = false;
continue;
}
$observers = array();
foreach ($eventConfig->observers->
children() as $obsName=>$obsConfig) {
$observers[$obsName] = array(
'type' => (string)$obsConfig->type,
'model' => $obsConfig->class ?
(string)$obsConfig->
class : $obsConfig->getClassName(),
'method'=> (string)$obsConfig->method,
'args' => (array)$obsConfig->args,
);
}
$events[$eventName]['observers'] = $observers;
$this->_events
[$area][$eventName]['observers'] = $observers;
}
if (false===$events[$eventName]) {
continue;
} else {
$event = new Varien_Event($args);
$event->setName($eventName);
$observer = new Varien_Event_Observer();
}
foreach ($events[$eventName]
['observers'] as $obsName=>$obs) {
$observer->setData(array('event'=>$event));
Varien_Profiler::start('OBSERVER: '.$obsName);
switch ($obs['type']) {
case 'disabled':
break;
case 'object':
case 'model':
$method = $obs['method'];
$observer->addData($args);
$object = Mage::getModel($obs['model']);
$this->_callObserverMethod
($object, $method, $observer);
break;
default:
$method = $obs['method'];
$observer->addData($args);
$object = Mage::getSingleton($obs['model']);
$this->_callObserverMethod
($object, $method, $observer);
break;
}
Varien_Profiler::stop('OBSERVER: '.$obsName);
}
}
return $this;
}

个dispatchEvent方法真正的对event/observermodel做了所有工作。:

1.        获得Magento配置对象

2.        遍历观察者的所有子节点,检查是否已经对当前事件定义的观察者有定义。

3.        对每个可用的观察者而言,被调度的事件试图实例化观察者对象。

4.        最后,magento将会尝试调用同这个事件匹配的事件的观察者函数。

观察者绑定

现在,调度事件仅仅是一部分,我们还需要明确的告诉magento哪个观察者正在监听每个事件。毫无疑问,这些观察者在config.xml文件中定义,就像我们之前所说的,dispatchEvent函数从已经配置的对象中查找可用的观察者。我们来看下config.xml文件:

<events>
<event_name>
<observers>
<observer_identifier>
<class>module_name/observer</class>
<method>function_name</method>
</observer_identifier>
</observers>
</event_name>
</events>

<event>节点可以在每个配置块内声明(admin/global/frontend等等),我们也可以在这个节点下声明多个事件子节点,即event_name。这个标记用来匹配在dispatchEvent()函数中使用的事件名。

在event_name节点下,我们可以包含多个具有唯一名称的事件观察者。

<observer>节点有两个子节点,一个是class一个是method,我们来浏览一个简单的观察者类:

class Namespace_Modulename_Model_Observer
{
public function methodName(Varien_Event_Observer $observer)
{
//some code
}
}

有趣的是,这个类没有继承任何的类。

总结

在这一章里,我们了解很多对于magento重要的并且基础的内容,比如说架构、文件目录、路由系统、MVC模式、事件和观察者以及对公而忘私视图的配置。

也许这些内容在第一眼看来很有压力,但这只是冰山一角,关于magento我们还有更多需要学习的内容。这一章里我们主要是为了让开发者在意所有的这些部分:从对象的配置到事件和观察者模式。

Magento是一个灵活且强大的系统,它已经不仅仅是一个电子商务系统,magento的核心团队花费了大量的心血来使其成为一个强大的框架。

在接下来的章节里,我们不仅要回顾这些讲过的概念,而且还将这些概念应用到开发中去,我们将会建立一个自己的扩展。

 

 

感谢原书的作者Allan MacGregor提供了原版的资料,版权属于原书作者。文中翻译多有不恰当的地方,望读者指正。




















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值