你现在的位置:首页 > PHP网站建设知识库 > magento > 正文

Magento开发说明:magento模型与ORM基础

模型层的实现是任何一个MVC框架的重要组成部分。它用来实现应用程序的数据,并且大部分应用程序在没有数据的情况下都是一堆废柴。相对于其他PHP MVC框架,magento模型在系统中扮演了一个更为重要的角色,因为它包含了通常应用于控制器和助手方法中的业务逻辑。

传统的PHP MVC模型

如果说MVC架构的定义有些模糊,那么模型的定义就更为模糊了。早在MVC模式被PHP开发者普遍接受之前,数据的交互通常是使用原始的SQL语句或者SQL抽象进行。开发者必须很多数据库查询语句,而不用考虑在模型化哪个对象。
此处省略三段关于传统PHP MVC模型层以及ORM的探讨,直接进入正题。

Magento 模型

毫无疑问Magento实现了ORM模式。尽管Zend Framework的SQL抽象层能够正常使用,大部分的数据交互依然是通过内置的Magento模型,以及用户自己构建的模型完成。Magento系统拥有一个高度灵活,高度抽象的模型层。
Magento模型解剖

绝大部分Magento模型可以被分为两类。基础的,ActiveRecord,或者说是“一张表,一个对象”的模型;另外一种是Entity Attribute Value(EAV)模型。每个模型都包含一个模型收集(Model Collection)。收集(Collections)是用来同时操作多个Magento模型实例的对象。Magento团队通过实现PHP的IteratorAggregate标准库接口和Countable,从而允许每个模型类型拥有自己的收集类型。如果你对PHP标准库不是很熟悉,可以将模型收集想象成拥有方法可以使用的数组。
Magento模型不包含任何连接数据库的代码。取而代之,每个模型使用两个modelResource类(一个读取,一个写入),它们通过read and write adapter objects与数据库进行交互。通过解耦模型与数据库交互代码,理论上可以通过构建新的资源类来满足任意不同的数据库平台,并且保持模型的完整性。

创建一个基础的Magento模型

下面我们开始创建一个基础的Magento模型,我们以简单的weblog博客为例,构建一个模型,总的分为以下几步。

创建“Weblog”模块
为模型创建一张表,模型命名为Blogpost
添加模型信息到配置文件
添加模型资源信息到配置文件
添加Read Adapter信息到配置文件
添加Write Adapter信息到配置文件
为Blogpost模型添加PHP类文件
foo

初始化模型

创建 Weblog 模块

通过之前几章的学习,创建一个新的空模块应该没有问题啦,这里我们跳过这些细节,假设你已经创建了一个名为Weblog的空模块。完成之后,我们为Index控制器设置路由规则。这里依然假设我们的Package命名为Magentotutorial。
在Magentotutorial/Weblog/etc/config.xml文件中,加入一下路由规则,

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

然后添加以下代码到Index控制器中,该文件位于Magentotutorial/Weblog/controller/IndexController.php。

class Magentotutorial_Weblog_IndexController extends Mage_Core_Controller_Front_Action {
    public function testModelAction() {
        echo 'Setup!';
    }
}

清空Magento缓存,根据你的安装路径,访问类似下面的地址,
http://example.com/weblog/index/testModel

创建数据库表

Magento系统能够自动创建和更改数据库模式,这里为了演示,我们先手动为模型创建一个表。使用命令行或你最喜欢的MySQL GUI工具,创建下表,

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

然后填充一些数据到表中,

INSERT INTO `blog_posts` VALUES (1,'My New Title','This is a blog post','2010-07-01 00:00:00','2010-07-02 23:12:30');

创建模型及其配置文件
创建Weblog的模型及其配置文件需要以下五步完成,
在模块中启用模型
在模块中启用模型资源(Model Resources)
在模型资源中添加实体“entity”,对于简单的模型来说,该实体即表名
为模型资源指定读取适配器(Read Adapter)
为模型资源指定写入适配器(Writer adapter)
在Magento中实例化一个模型,可以使用如下语法,
$model = Mage::getModel('weblog/blogpost');

getmodel()方法里的URI的第一部分叫做模型组名(Model Group Name)。考虑到Magento为类使用__autoload方法,所以该模型组名必须是模块的小写形式。该URI的第二部分是你的模型名的小写形式。
接着,我们开始添加模型的配置代码到模块的config.xml文件中。

<global>
    <!-- ... -->
    <models>
        <weblog>
            <class>Magentotutorial_Weblog_Model</class>
            <!--
                need to create our own resource, cant just
                use core_mysql4
            -->
            <resourceModel>weblog_mysql4</resourceModel>
        </weblog>
    </models>
    <!-- ... -->
</global>

最外层的<weblog />标签是模型组名,应该匹配模块名。<class />中的值是weblog组中所有的模型都拥有的BASE名。<resourceModel />标签指定weblog组中的模型应该使用哪种模型资源,这里我们先记得它是由模型组名加“mysql4”。
现在让我们清理下Magento缓存,尝试下实例化这个blogpost模型。在testModelAction()中,添加如下代码。
public function testModelAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    echo get_class($blogpost);
}

刷新页面之后,你会看到系统抛出了异常,大概如下,
include(Magentotutorial/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory

由于在上面那段代码中,试图引用‘weblog/blogpost’模型,Magento会实例化下面这个类,
Magentotutorial_Weblog_Model_Blogpost

但是此时我们还没有创建这个文件。所以系统会抛出上面的异常。下面我们来创建该类,文件路径位于,
File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php

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

刷新页面之后,异常就被该类名所取代了。所有的基础模型都必须扩展Mage_Core_Model_Abstract类。这个抽象类强制你必须实现一个名为_construct的方法。此方法会调用该类的_init方法,并需要传递在getModel()方法中的参数。

全局配置和模型资源

到此为止,我们已经成功设置了自定义的模型。接着,我们需要设置它的模型资源。模型资源包含与数据库交互的代码。在上一小节中,我们在配置文件中添加了如下代码,
<resourceModel>weblog_mysql4</resourceModel>

在<resourceModel />中的值会实例化一个模型资源类。尽管你从不需要手动调用它,当任何在weblog组中的模型需要与数据库交互时,Magento会调用以下方法获取模型资源,
Mage::getResourceModel('weblog/blogpost');

重申一次,weblog是模型组名,blogpost是模型名。Mage::getResourceModel方法使用weblog/blogpost URI来检查全局配置文件,并获取<resourceModel>中的值(在这里,是weblog_mysql4)。然后,下列URI地址的模型类将会被实例化。
weblog_mysql4/blogpost

资源模型的配置与模型的配置在XML配置文件中的相同节点呢,下面我们在<models>节点中添加下列代码,
<global>
    <!-- ... -->
    <models>
        <!-- ... -->
        <weblog_mysql4>
            <class>Magentotutorial_Weblog_Model_Mysql4</class>
        </weblog_mysql4>
    </models>
</global>

这里设置的<weblog_mysql4 />标签,就是刚刚在<resourceModel />标签中设置的值。<class />节点中的值是使用的资源模型的基础命名,它的命名方式大概如下
Packagename_Modulename_Model_Mysql4

现在,我们成功配置了资源模型,来试着从模型数据中读取一些信息吧。稍稍添加一些代码到testModelAction()方法中。
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);
}

清空Magento缓存,在浏览器中打开如下地址,
http://example.com/weblog/index/testModel/id/1

好吧,又一次看到系统抛出了异常,大概如下,
Warning: include(Magentotutorial/Weblog/Model/Mysql4/Blogpost.php) [function.include]: failed to open stream: No such file ….

上面我们提到过,当与数据库交互时,会实例化资源模型类,这里系统提示我们需要为该模型添加一个模型资源类。(译者注:本文提到过,Magento的模型本身与数据库连接及交互是相互独立的,所以在模型没有与数据库交互之前,例如在本篇第一次使用getModel()方法时,系统不会抛出关于实例化模型以外的异常。)每个模型都有模型资源类,添加该类到下列路径的文件中,
File: app/code/local/Magentotutorial/Weblog/Model/Mysql4/Blogpost.php

class Magentotutorial_Weblog_Model_Mysql4_Blogpost extends Mage_Core_Model_Mysql4_Abstract{
    protected function _construct()
    {
        $this->_init('weblog/blogpost', 'blogpost_id');
    }
}

可以看到,_init方法的第一个参数依旧是模型组名/模型名。参数二是数据库字段,可以是任意唯一字段,大多数情况下,参数二可以指定为主键。清空缓存,刷新页面,页面中会显示如下内容,
Loading the blogpost with an ID of 1
array
empty

没有异常?可是也没有正常读取到数据!接着该做些什么呢?每一个模型组都拥有一个读取适配器和写入适配器。Magento允许模型使用默认的适配器,也可以使用开发者自己开发的适配器。无论使用哪一种,我们需要告诉Magento系统关于适配器的配置。这里,我们在配置文件中添加一个新的tag节点,<resources />到<global />节点中。
<global>
    <!-- ... -->
    <resources>
        <weblog_write>
            <connection>
                <use>core_write</use>
            </connection>
        </weblog_write>
        <weblog_read>
            <connection>
                <use>core_read</use>
            </connection>
        </weblog_read>
    </resources>
</global>

这里我们在<resources />中添加了两个子节点。一个用来写入,另一个用来读取。标签命名(<weblog_write />和<weblog_read />)根据上面定义的模型组名。完成改配置文件之后,清空Magento缓存,再次刷新页面,然后…
Can’t retrieve entity config: weblog/blogpost

又一次出现异常了!一起理清下思路,在使用模型URI weblog/blogpost时,Magento系统被告知我们想使用模型组weblog,以及blogpost实体。在扩展Mage_Core_Model_Mysql4_Abstract的简单模型中,实体相对应一张表。这里,该表即我们上面创建的blog_post表,添加该实体到配置文件中。
<models>
    <!-- ... --->
    <weblog_mysql4>
        <class>Magentotutorial_Weblog_Model_Mysql4</class>
        <entities>
            <blogpost>
                <table>blog_posts</table>
            </blogpost>
        </entities>
    </weblog_mysql4>
</models>

在配置文件中的resource模型节点中,添加新的<entities />节点。现在,在配置文件中终于出现了以刚才创建的表名命名的节点,从而为该模型指定相关的数据库表。
清空Magento缓存,刷新页面,OK…
Loading the blogpost with an ID of 2
Loading the blogpost with an ID of 1
array
‘blogpost_id’ => string ’1′ (length=1)
‘title’ => string ‘My New Title’ (length=12)
‘post’ => string ‘This is a blog post’ (length=19)
‘date’ => string ’2009-07-01 00:00:00′ (length=19)
‘timestamp’ => string ’2009-07-02 16:12:30′ (length=19)

好啦!经过这么长一个过程我们终于成功从数据库中读取到了数据,更为重要的是,我们完成了一个崭新的Magento模型的配置!
基础的Magento模型操作
Magento模型都继承自Varien_Object类。该类是Magento系统核心库中的一部分,而非Magento核心模块。可以在下列路径找到该对象。
lib/Varien/Object.php

Magento模型将数据保存在一个protected的_data属性中。Varien_Object类提供给我们很多方法,可以使用这些方法读取这些数据。你已经使用过了getData()方法,该方法返回一个包含字段/值的数组。你也可以通过传递字段名作为该方法的参数来获取相应字段的值
$model->getData();
$model->getData('title');

还有一个getOrigData方法,which will return the Model data as it was when the object was initially populated, (working with the protected _origData method).这段就不翻译了。
$model->getOrigData();
$model->getOrigData('title');

Varien_Object类通过PHP的魔术方法__call实现了一些特殊的方法。你可以通过get,set,unset以及has加上驼峰命名的字段名的方式,获取、设置、unset及查看任意存在的字段值。
$model->getBlogpostId();
$model->setBlogpostId(25);
$model->unsetBlogpostId();
if($model->hasBlogpostId()){...}

正因为如此,你可能会以小写字母及下划线来命名数据库字段。不过,最近版本的Magento已经舍弃了这种语法,转而实现PHP的数组连接(ArrayAccess)接口。
$id = $model->['blogpost_id'];
$model->['blogpost_id'] = 25;
//etc...

That said, you’re likely to see both techniques used throughout the Magento code base, as well as third party extensions.这段意思应该是说你可以在Magento或第三方扩展中看到上面两种语法格式。
Magento的CRUD操作
Magento模型通过load(),sava(),delete()方法,提供基础的Create,Read,Update和Delete功能。在上面的控制器方法中,我们已经使用了load()方法。当传递一个参数到load()方法中,该方法会返回与该参数相对应的id字段(在模型资源中设置)的一条记录。
$blogpost->load(1);

save()方法允许你插入新数据到模型中,或更新已经存在的数据。添加如下代码到控制器中。
public function createNewPostAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    $blogpost->setTitle('Code Post!');
    $blogpost->setPost('This post was created from code!');
    $blogpost->save();
    echo 'post created';
}

然后在浏览器中访问以下地址,
http://example.com/weblog/index/createNewPost

这时你会看到数据库表中新增了一条数据,然后在控制器中加入编辑功能。
public function editFirstPostAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    $blogpost->load(1);
    $blogpost->setTitle("The First post!");
    $blogpost->save();
    echo 'post edited';
}

最后,加入下列代码,实现删除功能。
public function deleteFirstPostAction() {
    $blogpost = Mage::getModel('weblog/blogpost');
    $blogpost->load(1);
    $blogpost->delete();
    echo 'post removed';
}

Magento的模型收集 Model Collections
对于单独一个模型的操作固然很有用,但是多数时候,我们会同时操作多个模型。比返回多个模型的一个多维嵌套数组更好的是,在Magento中,每个模型类型都有一个唯一的收集对象与其关联。这些对象实现了PHP IteratorAggregate和Countable接口,这意味着它们可以被传递到count函数,并使用for each结构循环出数据。
我们将在第八章具体介绍Magento的收集机制,现在我们先简要介绍下它的设置和使用。添加如下代码到控制器中,然后再浏览器中访问该地址。
public function showAllBlogPostsAction() {
    $posts = Mage::getModel('weblog/blogpost')->getCollection();
    foreach($posts as $blog_post){
        echo '<h3>'.$blog_post->getTitle().'</h3>';
        echo nl2br($blog_post->getPost());
    }
}

访问如下地址,
http://example.com/weblog/index/showAllBlogPosts

然后,是的,系统再一次抛出异常。
Warning: include(Magentotutorial/Weblog/Model/Mysql4/Blogpost/Collection.php) [function.include]: failed to open stream

看下上面的PHP代码,你就应该对系统抛出异常不会感到太惊讶了吧?我们需要添加一个类来定义Blogpost的模型收集。每个模型资源拥有一个_resourceCollectionName保护属性,它包含了用来识别收集的URI。
protected '_resourceCollectionName' => string 'weblog/blogpost_collection'

默认的,该URI也用来识别模型资源,以字符串”_collection”结尾。Magento将收集归为模型资源的一部分,所以该URI转换为类名之后如下,
Magentotutorial_Weblog_Model_Mysql4_Blogpost_Collection

添加下面的模型收集类到如下路径,
File: app/code/local/Magentotutorial/Weblog/Model/Mysql4/Blogpost/Collection.php

class Magentotutorial_Weblog_Model_Mysql4_Blogpost_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
    protected function _construct()
    {
        $this->_init('weblog/blogpost');
    }
}

和其他类一样,我们需要使用该模型的URI(weblog/blogpsot)来_init模型收集。最后,在浏览器中访问模型收集的地址,就能成功返回文章的数据信息了。