Magento开发系列之五 模型和ORM基础

模型层级的应用在MVC框架中是一个非常重要的部分,它包含应用程序所需的数据。在Magento中,Model起的作用甚至还要更大,因为其中还包含业务逻辑,在其它的MVC框架中,业务逻辑通常是放在Controller或Helper方法中的。

注:在调试过程中如果没有出现我们所提到的报错,请开启报错,步骤如下:
a> 打开根目录下的index.php文件并取消如下注释:

#ini_set('display_errors', 1);

b> 打开.htaccess文件并在最后添加

SetEnv MAGE_IS_DEVELOPER_MODE “true”

MVC模型

首先我们来看看PHP传统的MVC模型,如果说MVC的定义不够清楚,那么模型的定义就更加模糊了。在PHP开发者大规模采用MVC框架前,数据是通过原生的SQL语句或SQL抽象层来获取的。这时开发者需要书写SQL语句并考虑为哪些对象建模。

如今很多人已不太愿意去书写原生SQL语句了,但很多PHP框架依然是在大量使用SQL。Model是提供抽象层的对象,但事实上程序员还是书写SQL语句或者通过抽象方法来读写数据以调用SQL。

也有一些框架避开SQL的使用而采用ORM(Object Relational Mapping)来完成相关操作。这种方法开发者通过对象设定属性,在调用对象中的save方法时,数据会被自动写入数据库。

我们再来看看Magento的模型,Magento是支持ORM的,同时Zend框架中的SQL抽象也可用。Magento中大多数的数据通过其内置的模型或自开发的模型来处理。Magento中Model的概念也是超级灵活、高度抽象的。

Magento中大多数的模型可以被划分为两类,一种是类似ActiveRecord的一个对象一张表的模型,另一种就是大名鼎鼎的EAV(Entity Attribute Value)模型。每个模型都有一个模型集合,集合(Collection)是用于包含一系列Magento模型实例的PHP对象。Magento开发团队在PHP标准库接口中加入了IteratorAggregate和Countable来让每个Model类型获取它自己的集合类型。如果你不太了解PHP标准库,可以把模型集合看作带有方法的数组。

Magento模型中没有代码去连接数据库,而是通过模型资源类来与数据库服务器进行交互(读或写adapter对象)。通过分离逻辑模型与数据库交互代码,理论上就可以在保持模型不变的情况下写一个新的资源类来操作不同类型的数据库。

首先我们要创建一个基本的Magento模型,按照传统我们来做一个weblog博客建模,步骤如下:

  1. 创建一个Weblog模块
  2. 为我们的Model创建一个数据表
  3. 在配置文件中添加一个Blogpost模型
  4. 在配置文件中为Blogpost模型添加模型资源信息
  5. 在配置文件中为Blogpost模型添加一个读适配器(Adapter)
  6. 在配置文件中为Blogpost模型添加一个写适配器
  7. 为Blogpost模型添加一个PHP类文件
  8. 为Blogpost资源模型添加一个PHP类文件
  9. 实例化该模型

那下面我们就按这个步骤来进行操作吧,通过前面的学习你应该对创建Weblog模块已经相当熟悉了,不过考虑到有些朋友没有从头开始看,这里再赘述一下,首先创建如下目录

app/code/local/Alanhou/Weblog/Block
app/code/local/Alanhou/Weblog/controllers
app/code/local/Alanhou/Weblog/etc
app/code/local/Alanhou/Weblog/Helper
app/code/local/Alanhou/Weblog/Model
app/code/local/Alanhou/Weblog/sql

创建app/code/local/Alanhou/Weblog/etc/config.xml文件并加入如下代码

<config>
	<modules>
		<Alanhou_Weblog>
			<version>0.1.0</version>
		</Alanhou_Weblog>
	</modules>
</config>

接着创建app/etc/modules/Alanhou_Weblog.xml文件并加入如下代码

<config>
	<modules>
		<Alanhou_Weblog>
			<active>true</active>
			<codePool>local</codePool>
		</Alanhou_Weblog>
	</modules>
</config>

执行以下步骤

  1. 清除缓存
  2. 在后台中,点击System > Configuration > Advanced > Advanced
  3. 在Disable modules output面板中查看Alanhou_Weblog是否出现

模块添加成功后我们来创建一个Action方法名为testModel的index控制器,首先在config.xml里添加router即加入如下代码:

	<frontend>
		<routers>
			<weblog>
				<use>standard</use>
				<args>
					<module>Alanhou_Weblog</module>
					<frontName>weblog</frontName>
				</args>
			</weblog>
		</routers>
	</frontend>

在app/code/local/Alanhou/Weblog/controllers/IndexController.php中加入如下代码:

<?php
class Alanhou_Weblog_IndexController extends Mage_Core_Controller_Front_Action{
	public function testModelAction(){
		echo 'Setup!';
	}
}

清除缓存并访问http://localhost/magento/weblog/index/testModel页面上会输出Setup!

Magento拥有一套可自动创建和修改数据库的系统,不过现在我们先手动为这个模型创建数据表,在phpMyAdmin或任何数据库客户端中进入数据库执行如下语句创建数据表blog_posts

CREATE TABLE <code>blog_posts</code> (
  <code>blogpost_id</code> int(11) NOT NULL auto_increment,
  <code>title</code> text,
  <code>post</code> text,
  <code>date</code> datetime default NULL,
  <code>timestamp</code> timestamp NOT NULL default CURRENT_TIMESTAMP,
  PRIMARY KEY  (<code>blogpost_id</code>)
)

接着执行如下语句手动插入一条数据

INSERT INTO <code>blog_posts</code> VALUES (1,'My New Title','This is a blog post','2010-08-08 00:00:00','2010-08-08 23:12:30');

blog_posts数据表

配置模型文件我们需要在配置文件中

  1. 在模块中开启Model
  2. 在模块中开启Model资源
  3. 为Model资源添加一个entity数据表配置

在实例化一个模型时需要进行类似下面这样的调用

$model = Mage::getModel('weblog/blogpost');

在传入的URI中前半部分是Model分组名(weblog),按照惯例它是我们模块名的小写形式,如果想更加保险些,可以加上命名空间,同样也需要使用小写;第二部分则是模块名称的小写形式(blogpost)。
在config.xml的config标签内添加如下代码

<global>
	<models>
		<weblog>
			<class>Alanhou_Weblog_Model</class>
			<resourceModel>weblog_resource</resourceModel>
		</weblog>
	</models>
</global>

外部的<weblog />标签是组名,和模块名相同,<class  />标签中内容为weblog分组中所有模型的基础名称,或称为类前缀。<resourceModel />标签指定weblog分组模型所使用的资源模型,还有更多的内容,但这里我们只要知道这个分组名接resource即可。

我们的操作还没完成,不过先清除缓存并通过如下代码尝试实例化blogpost模型:

public function testModelAction(){
	// echo 'Setup!';
	$blogpost = Mage::getModel('weblog/blogpost');
	echo get_class($blogpost);
}

重新加载页面就会出现类似下面的报错

Warning: include(Alanhou\Weblog\Model\Blogpost.php): failed to open stream: No such file or directory

可以看到这里尝试实例一个Mage_Weblog_Model_Blogpost类,Magento通过__autoload来包含这个模型,但无法找到文件,下面我们来创建app/code/local/Alanhou/Weblog/Model/Blogpost.php并加入如下代码:

<?php class Alanhou_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract{
   	protected function _construct(){
   		$this->_init('weblog/blogpost');
	}
}

重新加载页面,提示信息应该会变成刚刚创建的类名,即Alanhou_Weblog_Model_Blogpost

Alanhou_Weblog_Model_Blogpost

所有与数据库交互的基础模型都继承Mage_Core_Model_Abstract类,这个抽象类会强制应用一个_construct方法(注意:这不是构造方法__construct);该方法会调用与Mage::getModel调用URI相符类的_init方法。

上面我们创建好了模型,下一步就是建立模型资源,模型资源中的代码将会操作数据库,本例中我们在配置文件中包含了

<resourceModel>weblog_resource</resourceModel>

<resourceModel />中的值会被用来实例化Model资源类,虽然这里没有调用该类,但任何weblog分组中的模型要和数据库进行交互时,都会调用如下方法来获取模型资源

Mage::getResourceModel('weblog/blogpost');

再强调一下,weblog是分组名,blogpost是模型名称,Mage::getResourceModel方法会使用weblog/blogpost来查看全局配置并获取<resourceModel>标签中的值(本例中即为weblog_resource)。接下来模型类会实例化如下URI:

weblog_resource/blogpost

因而,资源模型和常规模型一样都在config.xml中同处配置,因此,我们在config.xml中进行如下修改

<global>
    <!-- ... -->
    <models>
        <!-- ... -->
        <weblog_resource>
            <class>Alanhou_Weblog_Model_Resource</class>
        </weblog_resource>
    </models>
</global>

这里我们从resourceModel标签中获取了值weblog_resource并作为标签添加到配置文件中,class标签中的值通过如下格式获取:

Packagename_Modulename_Model_Resource

下面我们修改testModel方法并重置加载页面

public function testModelAction(){
	$params = $this->getRequest()->getParams();
	$blogpost = Mage::getModel('weblog/blogpost');
	echo ("Loading the blogpost with an ID of ".$params['id']);
	$blogpost->load($params['id']);
	$data = $blogpost->getData();
	var_dump($data);
}

加载http://localhost/magento/weblog/index/testModel/id/1页面,会出现类似如下报错:

Warning: include(Alanhou\Weblog\Model\Resource\Blogpost.php): failed to open stream: No such file or directory

因此我们需要为模型添加一个资源类,每个模型都有自己的资源类,下面我就创建app/code/local/Alanhou/Weblog/Model/Resource/Blogpost.php文件并加入如下代码:

<?php class Alanhou_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{
 	protected function _construct(){
 		$this->_init('weblog/blogpost','blogpost_id');
	}
}

同样的_init方法中前半段即个URL用于指定模型,而后半部分可以是任意可唯一标识数据表列的字段,大多数情况下我们会使用主键,刷新缓存,重新加载页面,会出现如下报错:

Can't retrieve entity config: weblog/blogpost

在为weblog/blogpost建模时,我们在告诉Magento模块分组是weblog,Entity是blogpost,例中继承了Mage_Core_Model_Resource_Db_Abstract,因而entity对应的是一张数据表,我们前面创建的数据表名为blog_post,所以要对config.xml进行如下修改来添加entity(models标签内):

<weblog_resource>
	<class>Alanhou_Weblog_Model_Resource</class>
	<entities>
		<blogpost>

<table>blog_posts</table>

		</blogpost>
	</entities>
</weblog_resource>

这样就成功添加了<entities />代码段也就为模型指定了数据表,清除缓存重新加载页面,如果一切正常的话将会在浏览器中输出如下数组:

Loading the blogpost with an ID of 1
array(5) {
["blogpost_id"]=> string(1) "1"
["title"]=> string(12) "My New Title"
["post"]=> string(19) "This is a blog post"
["date"]=> string(19) "2010-08-08 00:00:00"
["timestamp"]=> string(19) "2010-08-08 23:12:30" }

这样就成功的从数据库中读取了数据并完成了Model的配置。

Magento中所有的模型都继承自Varien_Object类,该类是Magento系统库的一部分,但不属于任何core模块,可以在lib/Varien/Object.php中找到这个对象。模型将数据存储在一个类型为protected的_data数组中,Varien_object类提供了一些可获取数据的方法,比如前面用到的getData()将返回一个带有键值对的数组,也可以传一个键值来获取指定字段:

$model->getData();
$model->getData('title');

该类中还有一个getOrigData方法,它将返回对象起初被实例化时的模型数据:

$model->getOrigData();
$model->getOrigData('title');

Vairen_Object还通过PHP的魔术方法__call组成了一些特殊方法,可以通过以get, set, unset或has开头的方法来get, set, unset或检查一个属性是否存在,这些关键词后采用驼峰法即首字母大写:

$model->getBlogpostId();
$model->setBlogpostId(25);
$model->unsetBlogpostId();
if($model->hasBlogpostId()){...}

因此,建议所有数据表中的列名采用小写或下划线分隔单词的方式命名。

Magento中的增删改查

一提到数据库免不了会用到增删改查(英文称为CRUD,即Create, Read, Update, Delete),这些都是数据库中的基本功能,Magento的模型通过load, save和delete方法来实现这些功能。前面我们已经用到了load方法,在传入一个参数时,load方法会返回模型资源里指定列与传入值相匹配的记录。

$blogpost->load(1);

save方法可以对数据库进行INSERT插入新记录和UPDATE更新已有记录的操作,在我们的Index控制器添加如下方法:

public function createNewPostAction(){
	$blogpost = Mage::getModel('weblog/blogpost');
	$blogpost->setTitle('Code Post!');
	$blogpost->setPost('This post was created from code!');
	$blogpost->save();
	echo 'post with ID '.$blogpost->getId().' created';
}

访问http://localhost/magento/weblog/index/createNewPost页面,些时在数据表中就会新增一行内容
blog_post_insert

我们同样在Index控制器中再添加一个方法用于编辑数据表内容:

public function editFirstPostAction(){
	$blogpost = Mage::getModel('weblog/blogpost');
	$blogpost->load(1);
	$blogpost->setTitle("The First Post");
	$blogpost->save();
	echo 'post edited';
}

访问http://localhost/magento/weblog/index/editFirstPost完成对数据表的修改

通过代码领回数据表

同样地我们也可以创建一个方法来删除行:

public function deleteFirstPostAction(){
	$blogpost = Mage::getModel('weblog/blogpost');
	$blogpost->load(1);
	$blogpost->delete();
	echo 'post removed';
}

访问http://localhost/magento/weblog/index/deleteFirstPost即可完成对行的删除。

一个模型固然非常有用,但有时我们想要获取一系列的模型,每个模型类型都有一个特殊的集合对象,这些对象采用PHP的IteratorAggregate和Countable接口,也就是说可以传递给count函数并在foreach函数中使用。

关于集合我们会在后面进行全面的探讨,这里先讨论一下基本设置和使用。在Index控制器中添加如下方法

public function showAllBlogPostsAction(){
	$posts = Mage::getModel('weblog/blogpost')->getCollection();
	foreach($posts as $blogpost){
		echo '<h3>'.$blogpost->getTitle().'</h3>';
		echo nl2br($blogpost->getPost());
	}
}

访问http://localhost/magento/weblog/index/showAllBlogPosts页面,会提示如下报错:

Warning: include(Alanhou\Weblog\Model\Resource\Blogpost\Collection.php): failed to open stream: No such file or directory

这表示我们还需要添加一个类文件来定义Blogpost集合,每个模型都有一个protected类型的属性_resourceCollectionName,它包含一个用于标识集合的URI

protected '_resourceCollectionName' => string 'weblog/blogpost_collection'

默认情况下它与我们用于标识资源模型的URI相同,只是在最后面添加一段_collection。Magento把Collection看作资源的一部分,所以以上URI组合成如下类名

Alanhou_Weblog_Model_Resource_Blogpost_Collection

接下来在app/code/local/Alanhou/Weblog/Model/Resource/Blogpost/Collection.php中添加如下类

<?php class Alanhou_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract{
  	protected function _construct(){
  		$this->_init('weblog/blogpost');
	}
}

和其它类一样,我们需要使用传入weblog.blogpost来init我们的集合。重新访问http://localhost/magento/weblog/index/showAllBlogPosts就会得到文章信息。

通过代码遍历出post内容

Magento如何获取数据

根据前面的学习,我们已经成功获取产品实体及其特定的一些属性,下面就该看看如何获取直实的值了。为简化起见,这里仅介绍如何获取产品的名称属性。
那我们如何知道某一属性值存储在哪张表中呢?幸好Magento遵循一套命名系统来对表格过行命名,如果您查看Magento数据库的表格的话,会发现很多以catalog_product_entity为前缀的表格:

  • catalog_product_entity
  • catalog_product_entity_datetime
  • catalog_product_entity_decimal
  • catalog_product_entity_int
  • catalog_product_entity_text
  • catalog_product_entity_varchar
  • catalog_product_entity_gallery
  • catalog_product_entity_media_gallery
  • catalog_product_entity_tier_price

虽然有了这么多表格,我们似乎还是不知道通过查询哪张表来获取产品名称属性。其实在前面的学习中我们已经给出了答案,就是在前面提到的eav_attribute表中有一列backend_type。Magento的EAV系统会按照属性的后台类型来将属性存储在不同的表格中。比如想要获后台中产品名称类型的话,可以运行如下查询语句:

SELECT * FROM <code>eav_attribute</code>
WHERE <code>entity_type_id</code> =4 AND <code>attribute_code</code> = 'name';

大家应该很轻易地看出其后台类型为varchar,那么这一属性值就存放在catalog_product_entity_varchar表中,下面就让我们一起来看看这张表的结构:
catalog_product_entity_varchar表结构
how-to-retrieve-data-on-magento表由六列组成:

  • value_id:此项属性值为唯一标识符和主键
  • entity_type_id:该值为实体类型ID
  • attribute_id:这是一个外键(foreign key),与eav_entity表中的值相对应
  • store_id:与店铺视图(storeview)属性值相匹配的一个外键
  • entity_id:该外键对应相应类型的实体表,本例中对应catalog_product_entity表
  • value:该项包含我们所需要获取的值

现在我们已经了解了所有需获取产品信息的表格,就可以创建如下查询语句:

SELECT p.entity_id AS product_id, var.value AS product_name, p.sku AS
product_sku
FROM catalog_product_entity p, eav_attribute eav, catalog_product_
entity_varchar var
WHERE p.entity_type_id = eav.entity_type_id
AND var.entity_id = p.entity_id
AND eav.attribute_code = 'name'
AND eav.attribute_id = var.attribute_id

执行该语句后,我们会获得一个包含product_id, product_name和product_sku三列内容的表格。可以看到,通过原生的SQL语句,需要书写5行SQL查询语句来获取产品名称和SKU这两列的内容。所以如果没有ORM系统,运维Magento将是一个艰巨的任务,有了ORM系统,通常您都不需要通过原生的SQL语句来与Magento数据库打交道。
既然如此,那让我们一起来看看通过Magento的ORM系统又是如何获取产品信息的:

  1. 第一步是实例化一个产品集合:
    $collection = Mage::getModel('catalog/product')->getCollection();
  2. 然后指定需要获取的产品名属性
    $collection->addAttributeToSelect('name');
  3. 通过name对该集合进行排序
    $collection->setOrder('name', 'asc');

     

  4. 让Magento加载该集合
    $collection->load();
  5. 最终获取到的结果是一个通过name排序的所有店铺商品的集合,可以通过运行如下语句来查看真正需要用到的SQL查询语句
    echo $collection->getSelect()->__toString();

仅仅通过3行代码就可以从Magento店铺中获取产品名称的数据,并通过name对产品进行排序。实际由Magento生成的查询语句如下:

SELECT <code>e</code>.*. IF( at_name.value_id &gt; 0, at_name.value, at_name_default.
value ) AS <code>name</code>
FROM <code>catalog_product_entity</code> AS <code>e</code>
LEFT JOIN <code>catalog_product_entity_varchar</code> AS <code>at_name_default</code> ON
(<code>at_name_default</code>.<code>entity_id</code> = <code>e</code>.<code>entity_id</code>)
AND (<code>at_name_default</code>.<code>attribute_id</code> = '65')
AND <code>at_name_default</code>.<code>store_id</code> =0
LEFT JOIN <code>catalog_product_entity_varchar</code> AS <code>at_name</code> ON ( <code>at_
name</code>.<code>entity_id</code> = <code>e</code>.<code>entity_id</code> )
AND (<code>at_name</code>.<code>attribute_id</code> = '65')
AND (<code>at_name</code>.<code>store_id</code> =1)
ORDER BY <code>name</code> ASC

可以看出ORM系统和EAV模型非常的强大,使得开发者具备极强的灵活性,同时使用方法也很简便。下一节我们一起来学习一下有关Magento集合的相关知识。

Magento的魔术方法

Magento的ORM系统运用了魔术方法,更确切地说是PHP强大的getter和setter功能,__call()方法。Magento的这些方法用于set, unset, 检查或获取数据。
当我们调用了一个在相应类中不存在的方法时,PHP会在父类中查找是否存在该方法的声明。如果在父类中也查找不到函数,最终会使用__call()方法进行查找,Magento(或者说PHP)会调用该魔术方法,然后会传输请求的方法名和相应的参数。
这里,Product模型中没有定义__call()函数,但它从Magento所有模型都会继承的Varien_Object类中获得该方法。Mage_Catalog_Model_Product类继承的流程图如下:
Mage_Catalog_Model_Product继承流程图
%

Magento ORM与数据集合

集合(collection)和模型(model)对于Magento的开发来说就像是一日三餐一样不可或缺。接下来我们会讲解Magento的ORM系统(Object Relational Mapping-对象关系映射),并学习如何正确地使用数据集合以及EAV系统。Magento像大多的当代系统一样使用了对象关系映射(ORM)。
对象关系映射(Object-relational mapping,常缩写为ORM,O/RM或O/R mapping)是一种将类型相互不兼容的系统间面向对象编程语言数据进行相互转化的计算机软件。它创建一个”虚拟对象数据库”(virtual object database)以供编程语言在内部使用。
在后面的文章中,我们会着重讲解:

  • Magento模型
  • Magento数据模型解析
  • EAV和EAV模型
  • 如何使用SQL查询

我们还会使用一些代码来创建一个简易框架以供实验
首先下载https://github.com/amacgregor/mdg_imc文件然后上传到Magento的根目录,如果您在Linux上安装了GitHub的话(yum install git -y)的话,可以直接使用git clone https://github.com/amacgregor/mdg_imc.git将文件下载到当前目录,再拷贝整个shell文件夹到Magento根目录。
然后cd到Magento根目录,执行shell/imc.php,如果执行成功的话,会出现magento >字样。

Magento模型解析

前面已经讲到,Magento的数据模型用于操作和获取数据,数据层分为简单模型和EAV两种:

  • 简单模型:这些模型应用是简单的一个对象对应一张表的映射,也就是说对象属性与表结构内容一一对应。
  • EAV模型(Entity Attribute Value Models):这些模型用于描述一个动态数量属性的实体。

注:并非所有的Magento模型都通过ORM来进行继承。Observer就是例子,这种简易模型类并不映射某一特定数据库表或实体。
除此之外,每个模型类型由如下层组成:

  • 模型类:大部分业务逻辑都存放在这里。模型用于操作数据,但不会直接连接数据。
  • 资源模型类:资源模型(Resource Model)用于与数据库进行交互,实际控制增删改查(CRUD-Create, Read, Update, Delete)的操作。
  • 模型集合类:每个数据模型都有一个集合类,集合是一组包含多个Magento模型实例的对象。

Magento模型并不包含与数据库连接的逻辑,它们是并不了解数据库,事实上连接数据库的代码存放在资源模型层中。这使得Magento可以兼容多种类型的数据库和平台。尽管当前只正式支持MySQL,但却可以重写一个针对其它数据库的资源类,而无需修改其它模型逻辑代码。
模型层级关系
下面我们通过实例化一个产品对象并设置一些产品的属性来进行进一步的了解:

  1. 打开刚刚添加的Magento互动控制器(即前述的magento >界面)
    cd /var/www/html
    php shell/imc.php
  2. 第一步输入如下指令来创建一个新的产品对象实例
    $product = Mage::getModel(‘catalog/product’);
  3. 通过如下指令可查看这个产品类是否为空实例
    echo get_class($product);
  4. 如果运行正常的话您将得到如下内容:
    Magento_Catalog_Model_Product
  5. 如果我们想要深入了解该类的方法的话,可以运行如下指令:
    print_r(get_class_methods($product));

该指令将返回一个包含该类中所有方法的数组,下面让我们通过运行如下代码来修改产品价格和名称:

$product = Mage::getModel('catalog/product')->load(2);
$name = $product->getName() . '-TEST';
$price = $product->getPrice();
$product->setPrice($price + 15);
$product->setName($name);
$product->save();

上面第一行代码会实例化一个具体对象,然会从对象中获取name属性,之后设置价格和名称,最终保存对象。
如果您打开Magento产品类Mage_Catalog_Model_Product (app/code/core/Mage/Catalog/Model/product.php),首先就会看到该类中定义了getName()和getPrice()方法,却没有setPrice()和setName()方法。那么Magento使用什么方法定义产品对象setter和getter方法的呢?虽然定义了getPrice()和getName()方法,但却没有定义color或manufacturer等产品属性的getter和setter方法。那么下一节我们就一起来看看Magento的魔术方法

Magento MVC详解

模型-Models

Magento采用对象关系映射(Object Relational Mapping,简称ORM)方法,尽管还可以使用Zend_Db来直接连接数据库,但大多数的时候我们将使用模型来获取数据。要实现这一任务,Magento提供了如下两种模型:

  • 简易模型(Simple models):这种模型通过简单的一个对象对应一张表的映射方法,也就是说对象属性对应各字段以及表结构
  • EAV模型(Entity Attribute Value):此类模型用于能通过动态的属性数量来描述实体(entities)

Magento中模型层被分成两个部分:一个是处理业务逻辑的模型,另一个是处理数据交互的资源。这种设计使得Magento可以最终支持多个数据库平台,而无需修改模型内的任何逻辑。
Magento ORM采用PHP的魔术方法来动态连接对象属性。后面我们会更加深入地探讨模型、Magento ORM以及数据集合方法的内容。
注:Magento的模型不一定要与任何数据库中的表或EAV实体来关联,后面我们将要讲到的observer就是这方面的例子。

视图-Views

与其它的MVC应用相比,Magento的视图层可以说是独树一帜,不同于传统的MVC模型,Magento的视图层分为如下三个部分:

  • Layouts:布局文件(layout)是一些用于定义块结构、像name之类的属性以及我们可以使用的模板文件等的XML文件。每个Magento模块都有其自己的布局文件。
  • Blocks:Magento通过将大多数的逻辑转移到块(block)中来降低控制器的压力。
  • Templates:模板是一些包含HTML和PHP代码的phtml文件。

布局文件让Magento前端具有极大的灵活性,每个模块都有自己的XML布局文件,用于告诉Magento在哪里包含和处理各个页面请求。通过使用布局文件,我们可以在store中移动、添加或者删除块,完全无需修改对应XML以外的任何文件。

布局文件详解

下面我们来看看一个核心布局文件catalog.xml:

<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>

可以看出布局文件由如下三个主要XML节点(node)组成:

  • handle:每个页面请求都会包含多个句柄(handle),布局通过这些句柄来通知Magento对于每个页面加载并渲染哪些块。最常用的句柄为default和
    [frontname]_[controller]_[action]。default句柄在设置全局块时尤为重要,比如在每个页面的header块中加入CSS和JavaScript
  • reference:节点用于引用一个块,多用于指定一个嵌套块或修改一个已存在的块。上面的示例代码中,内部就指定了一个新的子块。
  • block:节点用于加载块,每个块节点可带有如下属性:
    • type:它是实际块类的一个标识符,例如catalog/product_list引用了Mage_Catalog_Block_Product_List
    • name:这是其它块用于引用当前块的名称。
    • before/after:这两个属性用于指定当前块相对于其它块的位置,它们两个还可通以使用破折号-作值来指定模块在顶部或底部显示。
    • template:这一属性用于指定渲染块的.phtml模板文件
    • action:每个块类型都有可影响前端功能的动作。比如page/html_head就可通过addJs和addCss这两个动作来添加CSS和JavaScript
    • as:用于指定我们在模块中调用块的唯一标识符,比如通过getChildHtml(‘block_name’)来调用子块。

块是Magento采用的一个新的概念,用于减少对控制器的加载。通常是一些直接连接模块的数据资源,在需要的时候可以操作这些数据并传递给视图。
此外,PHTML模板文件中可包含html和php标签,用于控制模型中数据的格式以及显示。下面让我们来看看产品视图模板文件中的一段代码:

<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>

下面是有关MVC结构的图表:
MVC块流程图

控制器-Controllers

Magento中的MVC控制器采用轻量级控制器设计,这种轻量级控制器包含较少的业务逻辑,多用于驱动应用的请求。一个基本的Magento控制器动作会加载并渲染布局:

public function viewAction()

{

	$this->loadLayout();

	$this->renderLayout();

}

这以后就通过块来处理显示逻辑、从模型中获取数据、准备数据并将数据发送给视图。