您的位置:首页 > 其它

Magento模块开发手册(五)Magento模型和ORM基础

2015-10-11 12:25 711 查看

对于任何一个MVC 架构,模型(Model)层的实现都是占据了很大一部分。对于 Magento来说,模型占据了一个更加重要的位置,因为它常常包含了一 部分商业逻辑代码(可以说它对,也可以说它错)。这些代码在其他的MVC框架中往往出现在控制器或者帮助函数中。

传统的PHP MVC模型

本来 MVC的定义就不是很清晰,不同的人有不同的看法,而对于模型的定义争议就更多了。在 MVC模式被广泛采用之前,PHP程序员往往通过 SQL语句直接 操作数据库。也有些程序员通过一个SQL 抽象层来操作数据库(比如AdoDB)。程序员往往关注 SQL语句本身,而不是和数据相关的对象。

虽然直接操作 SQL的方式一直被病诟,但是很多 PHP框架还是以SQL 为中心的。模型层提供了一系列对象,抽象/封装了数据操作,但是程序员最终还是需为模型层对象写 SQL语句操作数据库。

还有一些框架回避了SQL,使用了对象关系映射(Object Relational Mapping,ORM)来解决这个问题。使用这个方法的话,程序员不用关注SQL,而只需要和对象打交道。我们可以操作一个对象的属性,当“Save” 方法被调用的时候,对象的属性会作为数据自动的被写入数据库。有些 ORM框架会根据数据表的信息自动推测对象的属性,也有框架要求用户显示的生命对象属性 和表的关系。比较有名的ORM框架有ActiveRecord等等。【注:ActiveRecord源自Ruby onRails,不过现在
PHP也有了】

关于ORM 的概念,我就解释到这里。但是和许多计算机领域的其他概念一样,ORM的定义也越来越模糊了。我不想在这片文章中讨论关于 ORM的争议,所以我说的ORM就是那个最基本的 ORM概念。

Magento模型

Magento 理所当然的也追随潮流应用了ORM。虽然 Magento自带的 Zend框架提供了SQL 抽象层,但是在大多数情况下我们将通过 Magento自带的模型和我们自己的模型来进行数据访问。他和视图层(View)一样,Magento的模型层也不是简单的 ORM,而是一个高度灵活, 高度抽象甚至有点令人费解。

Magento的模型解剖

大部分的 Magento模型分为两类。第一类是基本的 ActiveRecord类型,一张表一个对象的模型。第二类是Entity Attribute Value(EAV)模型。【译者注:EAV翻译成“实体属性值”有点词不达意,还是就叫 EAV的好】Magento 自己定义了一个数据类型叫做模型集合 (Model Collection)。顾名思义,模型集合就是一个对象里面包含了很多模型对象。Magento 的创造者Varien团队实现了PHP类库的标准接 口,“IteratorAggregate”,“Countable”。这样模型集合就能调用这些方法,这也是模型集合和数组的区别。

Magento 的模型并不直接访问数据库。每一个模型都有一个资源模型(ResourceModel),每一个资源模型拥有两个适配器(Adapter),一个读,一个写。这样的话逻辑模型和数据库访问就分开了,所以从理论上讲更改底层数据库 只需要重写适配器就可以了,所有上层代码都不需要更改。

创建基本模型

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

创建一个新的“Weblog”模块
为我们的模型创建一个数据库表
添加模型信息到配置,模型命名为Blogpost
为我们的Blogpost模型添加模型资源信息到配置文件
为我们的Blogpost模型添加一个读取适配器(Read Adapter)到配置文件
为我们的Blogpost模型添加写入适配器(Write Adapter)到配置文件
为Blogpost模型添加一个PHP类文件
为Blogpost资源模型添加一个PHP类文件
实例化模型

创建一个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>

1
2
3
4
5
6
7
8
9
10
11

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

然后添加以下内容到动作控制器(at Magentotutorial/Weblog/controllers/IndexController.php)中

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

1
2
3
4
5

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

清空Magento缓存和加载下面的URL,以确保一切都正确安装了。
http://example.com/weblog/index/testModel
1

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

1
2
3
4
5
6
7
8

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

1

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

 

全局配置和创建模型

我们要在配置文件里面设置一个模型有五个步骤。

在模块中启用模型
在模块中启用模型资源(Model Resources)
添加一个“实体”表配置到我们的模型资源。.
这是你可以实例化的magento的模型,你可以这样写

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

1

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

 

getmodel()方法里的URI的第一部分叫做模型组名(Model Group Name)。考虑到Magento为类使用__autoload方法,所以该模型组名必须是模块的小写形式。该URI的第二部分是你的模型名的小写形式。

所以,让我们添加下面的XML到我们的模块的config.xml。

<global>
<!-- ... -->
<models>
<weblog>
<class>Magentotutorial_Weblog_Model</class>
<!--
need to create our own resource, can't just
use core_resource
-->
<resourceModel>weblog_resource</resourceModel>
</weblog>
</models>
<!-- ... -->
</global>

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<global>
    <!--
...
-->
    <models>
        <weblog>
            <
1f494
class>Magentotutorial_Weblog_Model</class>
            <!--
            need
to create
our own resource,
can't
just
            use
core_resource
            -->
            <resourceModel>weblog_resource</resourceModel>
        </weblog>
    </models>
    <!--
...
-->
</global>

最外层的<weblog />标签是模型组名,应该匹配模块名。<class />中的值是weblog组中所有的模型都拥有的BASE名。<resourceModel />标签指定weblog组中的模型应该使用哪种模型资源,这里我们先记得它是由模型组名加“mysql4”。

S所以,我们还没有完成,但让我们看看如果我们清除我们的Magento缓存,试图实例化blogpos模型会发生什么。在你的testModelAction方法,使用下面的代码

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

1
2
3
4

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

1

include(Magentotutorial/Weblog/Model/Blogpost.php)
[function.include]:
failed to
open stream:
No such file
or directory

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

Magentotutorial_Weblog_Model_Blogpost

1

Magentotutorial_Weblog_Model_Blogpost

Magento试图自动加载这个模型,但是无法找到该文件。让我们创造它!在以下位置创建以下类(Magento的试图加载的这个模型),但是无法找到该文件。让我们创造它!在以下位置创建以下类

File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php

1

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');
}
}

1
2
3
4
5
6
7

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

}

刷新页面之后,异常就被该类名所取代了。

与数据库交互的所有基本模型都必须继承 “Mage_Core_Model_Abstract”类。这个抽象类强制你实现一个方法命名_construct(注意:这不是PHP的构造函数__construct)。这个方法应该调用父类已经定义好的“_init”方法,参数是资源模型的 URI,也就是我们要告诉模型使用哪个资源模型。 我们将在解释资源模型的时候再解释这个URI。

 

全局配置和模型资源

因此,我们已经建立了模型。接下来,我们需要设置我们的模型资源。模型包含的资源,实际上会谈到我们的数据库的代码。在上一小节中,我们在配置文件中添加了如下配置。

<resourceModel>weblog_resource</resourceModel>

1

<resourceModel>weblog_resource</resourceModel>

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

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

1

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

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

weblog_resource/blogpost

1

weblog_resource/blogpost

所以,如果你遵循了所有的方式,这是什么意思,资源模型的配置与模型的配置在XML配置文件中的相同节点。这可能会造成混淆。

因此,考虑到这一点,让我们配置我们的资源。在我们的<models>部分添加

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

1
2
3
4
5
6
7
8
9

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

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

Packagename_Modulename_Model_Resource

1

Packagename_Modulename_Model_Resource

因此,我们有一个配置的资源,让我们试着装了一些模型数据。改变你的action,如下所示

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);
}

1
2
3
4
5
6
7
8

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);
}

然后在浏览器加载下面的URL(清除Magento缓存后)
http://example.com/weblog/index/testModel/id/1
1

http://example.com/weblog/index/testModel/id/1
你应该可以看到一个异常像下面这样

Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost.php) [function.include]: failed to open stream: No such file ....

1

Warning:
include(Magentotutorial/Weblog/Model/Resource/Blogpost.php)
[function.include]:
failed to
open stream:
No such file
....

正如你的直觉,我们需要为我们的模型添加一个资源类。每种模型都有其自身的资源类。添加以下类在以下位置

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost.php

1

File:
app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost.php

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

1
2
3
4
5
6

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

这里“_init”方法的第一个参数这个资源模型将要使用的数据表的URI,第二个参数是数据表中的列名。这个列的内容必须唯一,往往是数据表的主键。清除缓存,重新加载,你应该会看到

Can't retrieve entity config: weblog/blogpost

1

Can't
retrieve entity
config:
weblog/blogpost

另一个例外!当我们使用模型的URI weblog/blogpost,我们将告诉Magento的,我们想要的模型组的weblog,,以及blogpost的实体。在扩展Mage_Core_Model_Resource_Db_Abstract简单的模型的背景下,一个实体对应一个表。在这种情况下,我们上面创建表名为blog_post。让我们加入实体到我们的XML配置文件。

<models>
<!-- ... --->
<weblog_resource>
<class>Magentotutorial_Weblog_Model_Resource</class>
<entities>
<blogpost>
<table>blog_posts</table>
</blogpost>
</entities>
</weblog_resource>
</models>

1
2
3
4
5
6
7
8
9
10
11

<models>
        <!--
...
--->
        <weblog_resource>
            <class>Magentotutorial_Weblog_Model_Resource</class>
            <entities>
                <blogpost>
                    <table>blog_posts</table>
                </blogpost>
            </entities>
        </weblog_resource>
    </models>

我们增加了一个新的<entities />节点给我们配置的资源模型部分。这反过来,有我们的entity (<blogpost />)命名的部分,用于指定要使用此模型的数据库表的名称。

清空Magento缓存,用你的手指,重新加载页面,…

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)

1
2
3
4
5
6
7
8

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

1

lib/Varien/Object.php

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

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

1
2

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

还有一个getOrigData方法,该方法将返回模型数据,因为它最初是在填充物中,(以protected _origData方式工作)。

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

1
2

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

Varien_Object类通过PHP的魔术方法__call实现了一些特殊的方法。你可以通过get,set,unset以及has加上驼峰命名的字段名的方式,获取、设置、unset及查看任意存在的字段值。

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

1
2
3
4

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

正因为如此,你可能会以小写字母及下划线来命名数据库字段。不过,最近版本的Magento已经舍弃了这种语法,转而实现PHP的数组连接(ArrayAccess)接口。

$id = $model->['blogpost_id'];
$model->['blogpost_id'] = 25;
//etc...

1
2
3

$id
= $model->['blogpost_id'];
$model->['blogpost_id']
= 25;
//etc...

 

Magento的CRUD操作

Magento模型支持基本的创建,读取,更新和删除的CRUD功能与加载,保存和删除的方法。你已经看到了正在运行的负载的方法。当传递一个参数,负载方法会返回一个记录的id字段(在模型的资源集)匹配传入的值。

$blogpost->load(1);

1

$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 with ID ' . $blogpost->getId() . ' created';
}

1
2
3
4
5
6
7

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

然后通过加载下面的URL执行你的控制器动作
http://example.com/weblog/index/createNewPost
1

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

1
2
3
4
5
6
7

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

1
2
3
4
5
6

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

模型集合(Model Collections)

对于单独一个模型的操作固然很有用,但是多数时候,我们会同时操作多个模型。比返回多个模型的一个多维嵌套数组更好的是,在Magento中,每个模型类 型都有一个唯一的收集对象与其关联。这些对象实现了PHP IteratorAggregate和Countable接口,这意味着它们可以被传递到count函数,并使用for each结构循环出数据。

我们将在后面具体介绍Magento的收集机制,现在我们先简要介绍下它的设置和使用。添加如下代码到控制器中,然后再浏览器中访问该地址。

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

1
2
3
4
5
6
7

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

加载操作URL,
http://example.com/weblog/index/showAllBlogPosts
1

http://example.com/weblog/index/showAllBlogPosts
你应该看到一个(现在)熟悉的警告。

Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php) [function.include]: failed to open stream

1

Warning:
include(Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php)
[function.include]:
failed to
open stream

你不感到惊讶,是吗?我们需要添加一个类来定义Blogpost的模型收集。每个模型资源拥有一个_resourceCollectionName保护属性,它包含了用来识别收集的URI。

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

1

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

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

Magentotutorial_Weblog_Model_Resource_Blogpost_Collection

1

Magentotutorial_Weblog_Model_Resource_Blogpost_Collection

添加下面的PHP类在以下位置

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php

1

File:
app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php

 

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

1
2
3
4
5
6

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

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

 

结束语

恭喜你,你已经创建并配置了你的Magento模型。在后面的文章中,我们将看看Magento的实体属性值模型(EAV),它将更深入了我们在这里学到的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: