首 页   · 站长博客 · 用户注册 · 会员登陆  · 会员排行  ·最新主题  ·最近回复  精华区  版权声明  ·论坛管理
  当前登录身份:游客,请先登录。  笔名: 口令: 验证码:   
楼 主  index »  PHP相关资源下载区 » 《PHP设计模式介绍》第十四章 动态记录模式(2)  


  作者:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

  lxshark@yeah.net..
  593993745
  hi.baidu.com/mythicsky

 

 发表:2009-08-08 20:57:39 阅读 1923 次 回复 1 次 得分0  |   字号 字色
《PHP设计模式介绍》第十四章 动态记录模式(2)
然而,因为书签类的构造函数调用了静态方法DB:conn()来获取数据库连接,要注入模拟连接到其中就很困难了。这儿有一些可能的实现方法:增加一个方法来改变$this->conn,增加一个可选参数到每一个方法中,或是增加一个参数到构造函数中。让我们选用最后一种方法:给Bookmark的构造函数增加一个可选的参数。


class Bookmark {
// ...
public function __construct($id=false, $conn=false) {
$this->conn = ($conn) ? $conn : DB::conn();
// ...
}



现在,新的Bookmark依然能正常工作,但新的Bookmark(1,   $connection)用参数中的$connection对象代替正常的ADOConnection对象。

当这段代码完成后,你就能方便的将正常的数据库连接对象用模拟的连接对象进行替换,并且能进行数据库错误的检测。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testDbFailure() {
$conn = new MockADOConnection($this);
$conn->expectOnce(‘execute’, array(‘*’,’*’));
$conn->setReturnValue(‘execute’,false);
$conn->expectOnce(‘errorMsg’);
$conn->setReturnValue(‘errorMsg’,
‘The database has exploded!!!!’);
$link = new Bookmark(1,$conn);
$this->assertErrorPattern(‘/exploded/i’);
$conn->tally();



动态记录实例ID

在前面的例子中,大多数属性都是公共的,然而,书签ID是受保护的,以其值被免意外更改(如果其值被意外更改,当你想更新书签数据的时候问题就出现了)。因为$ID是受保护的,因此增加一个辅助方法来获取其值。


class Bookmark {
protected $id;
//...
public function getId() {
return $this->id;
}



怎样来测试它呢?


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testGetId() {
$this->add(‘http://php.net’, ‘PHP’,
‘PHP Language Homepage’, ‘php’);
// second bookmark, id=2
$link = $this->add(‘http://phparch.com’,
‘php|architect’, ‘php|arch site’, ‘php’);
$this->assertEqual(2, $link->getId());
}



如上,add()方法生并成保存数据,并通过getid()方法获取生成数据的ID值并验证其是相匹配的。

但是,如果你想用别的条件来验证所生成的数据而不仅仅是用书签的ID,或是你如何确保从数据库中返回的ID是正确的?用select语句根据给定的属性条件取得数据,并验证返回行的ID值是一个好的技术方法。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testGetId() {
$this->add(‘http://php.net’, ‘PHP’,
‘PHP Language Homepage’, ‘php’);
// second bookmark, id=2
$link = $this->add(‘http://phparch.com’,
‘php|architect’, ‘php|arch site’, ‘php’);
$this->assertEqual(2, $link->getId());
$alt_test = $this->conn->getOne(
“select id from bookmark where url = ‘http://phparch.com’”);
$this->assertEqual(2, $alt_test);
//alternatively
$this->assertEqual($link->getId(), $alt_test);
}



注意到这个试验类似于你用手工执行一个SQL查询来验证数据是否正确插入到书签表中。通过本次实验所实现代码,还能用于你后续实验中来验证数据的正确性,而不是仅仅简单的去执行它。

记录搜索

现在,我们已能实现保存书签对象到数据库,并且能根据书签ID从数据库中获取相应数据来重建书签对象。但是当ID值并不知道(通常情况也是这样)时会发生什么?或是你想通过如部分名称或是URL等相关值来搜索数据库,则更常见的解决方法是增加一个”finder”方法。

例如,你也许想使用findByUrl()方法查找与给定参数相类似的书签,下面的实验则能实现上述的要求。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testFindByUrl() {
$this->add(‘http://blog.casey-sweat.us/’, ‘My Blog’,
‘Where I write about stuff’, ‘php’);
$this->add(‘http://php.net’, ‘PHP’,
‘PHP Language Homepage’, ‘php’);
$this->add(‘http://phparch.com’, ‘php|architect’,
‘php|arch site’, ‘php’);
$result = Bookmark::findByUrl(‘php’);
$this->assertIsA($result, ‘array’);
$this->assertEqual(2, count($result));
$this->assertEqual(2, $result[0]->getId());
$this->assertEqual(‘php|architect’, $result[1]->name);
}



该实验生成一些数据,查找URL中包含有“PHP”字样的行,并校检返回的书签对象数组中的字符。FindByUrl()之所以是一个静态方法,是因为你有可能在没的书签对象实例化的情况下进行该操作。(当然你也能将“查找“方法放到每一个对象中,但目前 “查找”方法仍然是书签类中的一个方法。)

以下代码实现上述实验的要求。


class Bookmark {
// ...
const SELECT_BY_URL = “
select id
from bookmark
where url like ?”;
public static function findByUrl($url) {
$rs = DB::conn()->execute(
self::SELECT_BY_URL
,array(“%$url%”));
$ret = array();
if ($rs) {
foreach ($rs->getArray() as $row) {
$ret[] = new Bookmark($row[‘id’]);
}
}
return $ret;
}
}
 
更新记录


CRUD操作中的建立与读取部分介绍完毕。何如更新数据呢?当然用save()方法来更新activate record对象是合理的,但目前save()方法只能完成插入数据,其代码如下


class Bookmark{
// ...
const INSERT_SQL = “
insert into bookmark (url, name, description, tag, created, updated)
values (?, ?, ?, ?, now(), now())
“;
protected function save() {
$rs = $this->conn->execute(
self::INSERT_SQL
,array($this->url, $this->name,
$this->description, $this->tag));
if ($rs) {
$this->id = (int)$this->conn->Insert_ID();
} else {
trigger_error(‘DB Error: ‘.$this->conn->errorMsg());
}
}
}
 
然而,如果你已有一个有效的书签实例,则你应该希望看到如下代码



class Bookmark {
// ...
const UPDATE_SQL = “
update bookmark set url = ?,
name = ?, description = ?, tag = ?,
updated = now()
where id = ?
“;
public function save() {
$this->conn->execute(
self::UPDATE_SQL
,array(
$this->url,
$this->name,
$this->description,
$this->tag,
$this->id));
}




要区别INSERT与UPDATE,你应该测试书签数据是新建的还是从数据库中获取得的。

首先,重新制作两个版本的save()方法,分别命令为insert()与update()。


class Bookmark {
// ...
protected function insert() {
$rs = $this->conn->execute(
self::INSERT_SQL
,array($this->url, $this->name,
$this->description, $this->tag));
if ($rs) {
$this->id = (int)$this->conn->Insert_ID();
}
}
protected function update() {
$this->conn->execute(
self::UPDATE_SQL
,array(
$this->url,
$this->name,
$this->description,
$this->tag,
$this->id));
}
}
 
现在你新的save()方法的代码就如下所示了。



class Bookmark {
const NEW_BOOKMARK = -1;
protected $id = Bookmark::NEW_BOOKMARK;
// ...
public function save() {
if ($this->id == Bookmark::NEW_BOOKMARK) {
$this->insert();
} else {
$this->update();
}
}




最后一个问题:当你插入或是更新记录时,时间戳总是要改变的。如果不采取从数据库中获取时间戳的手段,则没有更好的方法在书签对象中记录准确的时间戳了。因为在插入与修改中都要应用到,所以要更改Activate Record类,当save()方法完成后,就更新时间戳(实例的相关属性值),以避免后来产生的不同步。

class Bookmark {
// ...
public function save() {
if ($this->id == self::NEW_BOOKMARK) {
$this->insert();
} else {
$this->update();
}
$this->setTimeStamps();
}
protected function setTimeStamps() {
$rs = $this->conn->execute(
self::SELECT_BY_ID
,array($this->id));
if ($rs) {
$row = $rs->fetchRow();
$this->created = $row[‘created’];
$this->updated = $row[‘updated’];
}
}
}
 
书签对象已具有了动态记录模式的核心:save()方法知道如何处理更新与插入的SQL请求,知道对象的当前状态,并且能组装所需的参数来代替由原由对象属性所构成的数组。让我们来测试一下。 



class ActiveRecordTestCase extends UnitTestCase {
// ...
function testSave() {
$link = Bookmark::add(
‘http://blog.casey-sweat.us/’,
‘My Blog’,
‘Where I write about stuff’,
‘php’);
$link->description =
‘Where I write about PHP, Linux and other stuff’;
$link->save();
$link2 = Bookmark($link->getId());
$this->assertEqual($link->getId(), $link2->getId());
$this->assertEqual($link->created, $link2->updated);
}
}
 



现在,让我们转向如何处理删除操作。在16章――数据地图模式中有一个例子,但是你可以方便的从insert()和update()方法中推导出来。

总结

正如大多数初次尝试由面向过程到面向对象编程所表现的那样,动态记录模式在概念与执行上都较为简单。将你所有的SQL代码都组织在一起是非常好的,并且动态记录模式给了你一个非常好的将业务逻辑与数据库操作相结合来持续保存对象的方法。

本章的例子用了一个真实的数据库来开发测试代码。另一个测试简单数据库代码的方法是使用模拟对象(参见第6章)来模拟数据库连接。不幸的是,这个方法并是广泛有效。SQL是一个复杂的语言,模拟的每个语句都与数据库的细节实验密切相关。而用新建的,实际的表进行实验则令人觉得舒服得多,没有模拟SQL时的副作用了。

如果动态记录模式还有不利的方面,则是其复杂性了。一个动态记录类可能迅速的变大,就像一个块磁铁。例如,书签类现在只有一个findById()方法,但你很有可能想要findByDescription()方法,或是findByGroup(),findRecentlyCreated()等方法。

另一个问题是对象会变得“重复”,这在save()方法中可能会看到。例如,$link与$link2在实验用例表示的是不同的对象,但事实上它们都是指同一个书签ID。你可以用下面的实验来证明。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testSave() {
// ...
$this->assertNotIdentical($link, $link2);
}




如果认为解决这个问题是重要的,你有可能要增加一个内部的注册机制(参见第五章)确保Bookmark(1)返回的所有对象的实例应是同一个对象。因为你实际上用的是new操作来建立一个对象而不是用工厂方法,作为一种实际的Active Record类,你应该将Bookmark修改为代理(参见11章)来真正解决这个问题。

另一个方面,Active Record模式被设计成一次处理一行记录的方式。这种模式是典型的”管理”式应用的界面,如编辑一篇文章,一个链接,一个注释等。但是大数的网页要处理的是多数据集或是多行数据,这正是我们下一章要讨论的主要内容――表数据网关模式。

 
 
 1#楼  
 
  回复人:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

   
 1#楼 发表于2009-08-08 21:31:35  评分:× 

回复给楼主(lxshark)
希望能对你们更好的学习PHP有所帮助,也期望大家有时间多光顾我的
点此打开链接:http://hi.baidu.com/mythicsky
  页数1/1首页 « 1 » 末页
  发表回复:您还没有登陆,无法发表回复。请先[登陆]

一起PHP技术联盟 主办:一起PHP 联系方式:站长QQ:4304410 QQ群:8423742 20159565 站长博客 E-mail: nqp@nqp.me 执行时间:0.014sec
SimsmaBBS 2008 (v6.0) Developed by 17php.com,Copyright(C)2003-2010 All rights reserved. 副本授权:一起PHP官方专用版