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


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

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

 

 发表:2009-08-08 20:59:26 阅读 1998 次 回复 1 次 得分0  |   字号 字色
《PHP设计模式介绍》第十六章 数据映射模式(2)
接下来,让我们写一些代码来执行它。


class BookmarkMapper {
//...
const INSERT_SQL = “
insert into bookmark (url, name, description,
tag, created, updated)
values (?, ?, ?, ?, now(), now())
“;
public function save($bookmark) {
$rs = $this->conn->execute(
self::INSERT_SQL
,array(
$bookmark->getUrl()
,$bookmark->getName()
,$bookmark->getDesc()
,$bookmark->getGroup()));
}



一个对象的常量存储了插入数据的语句,并且代码“自动”把Bookmark 的accessor方法映射到相对应的SQL语句。

现在看起来都齐全了,但是我们还需要做两个事情:处理数据库错误的代码和根据数据库的改变更改初始化的时候设置后者更改$bookmark的属性。


class BookmarkMapper {
//...
public function save($bookmark) {
$rs = $this->conn->execute(
self::INSERT_SQL
,array(
$bookmark->getUrl()
,$bookmark->getName()
,$bookmark->getDesc()
,$bookmark->getGroup()));
if ($rs) {
$inserted = $this->findById($this->conn->Insert_ID());
//clean up database related fields in parameter instance
$bookmark->setId($inserted->getId());
$bookmark->setCrtTime($inserted->getCrtTime());
$bookmark->setModTime($inserted->getModTime());
} else {
throw new Exception(‘DB Error: ‘.$this->conn->errorMsg());
}
}



findById() 看起来内容还很少,它的作用是找到并返回匹配ID的Bookmark 实例。本质上来说, BookmarkMapper 用来插入新的Bookmark,从数据库提取数据,并且根据正确的值来设置何时的属性值。而且因为Bookmark 实例自己就是参数并可以被更新,所以必须要返回任何值。

让我们来看下findById()的的详细内容。你可以使用同样的BaseTestCase(前面的Table Data Gateway章节):


class BookmarkMapperTestCase extends BaseTestCase {
// ...
function testFindById() {
$mapper = new BookmarkMapper($this->conn);
$this->addSeveralBookmarks($mapper);
$this->assertIsA(
$bookmark = $mapper->findById(1)
, ‘Bookmark’);
$this->assertEqual(1, $bookmark->getId());
}



从技术上来说,addSeveralBookmarks() 必须等待findById()工作正常再开始工作。(看save()方法里面的代码就知道了),我们等下再来研究addSeveralBookmarks()。


class BookmarkMapper {
// ...
public function findById($id) {
$row = $this->conn->getRow(
‘select * from bookmark where id = ?’
,array((int)$id)
);
if ($row) {
$bookmark = new Bookmark($this);
foreach($this->map as $field) {
$setprop = (string)$field->mutator;
$value = $row[(string)$field->name];
if ($setprop && $value) {
call_user_func(array($bookmark, $setprop), $value);
}
}
return $bookmark;
} else {
return false;
}
}
}
 
因为设计模式里面每一个finder 方法都需要把一个数据库的行转变为一个Bookmark 实例,所以我们需要为了实现这个功能在创建一个新的方法,命名为 


createBookmarkFromRow()。
class BookmarkMapper {
// ...
protected function createBookmarkFromRow($row) {
$bookmark = new Bookmark($this);
foreach($this->map as $field) {
$setprop = (string)$field->mutator;
$value = $row[(string)$field->name];
if ($setprop && $value) {
call_user_func(array($bookmark, $setprop), $value);
}
}
return $bookmark;
}
}
 
通过这个方法,我们对findById()进行简化: 


class BookmarkMapper {
// ...
public function findById($id) {
$row = $this->conn->getRow(
‘select * from bookmark where id = ?’
,array((int)$id)
);
if ($row) {
return $this->createBookmarkFromRow($row);
} else {
return false;
}
}
}
 
所以上面阐述的内容有些复杂了,所以一个UML的流程图可以让你更容易了解。 

首先,数据从数据库提取出来;随后,建立一个Bookmark的实例。然后,对于映射的每一个部分,代码找到合适的setter方法并把数据行的值传递给setter方法。Bookmark实例,添加了数据库的数据后,用findById()取出。

现在,让我们看下BookmarkMapper::add()方法,通过BaseTestCase::addSeveralBookmarks()实现。通过一个测试用例,我们可以发现他们都是在表里面创建一个新的行并且返回Bookmark类的一个拥有实际数据的实例。


class BookmarkMapperTestCase extends BaseTestCase {
// ...
function testAdd() {
$mapper = new BookmarkMapper($this->conn);
$bookmark =
$mapper->add(
‘http://phparch.com’,
‘php|arch’,
‘php|architect magazine homepage’,
‘php’);
$this->assertEqual(1,
$this->conn->getOne(‘select count(1) from bookmark’));
$this->assertEqual(‘http://phparch.com’, $bookmark->getUrl());
$this->assertEqual(‘php|arch’, $bookmark->getName());
$this->assertEqual(‘php|architect magazine homepage’,
$bookmark->getDesc());
$this->assertEqual(‘php’, $bookmark->getGroup());
}
}
 
这里是BookmarkMapper 的相关代码。 


class BookmarkMapper {
// ...
public function add($url, $name, $description, $group) {
$bookmark = new Bookmark;
$bookmark->setUrl($url);
$bookmark->setName($name);
$bookmark->setDesc($description);
$bookmark->setGroup($group);
$this->save($bookmark);
return $bookmark;
}



这个与动态的记录ActiveRecordTestCase::add()是很类似的,方便使用。但是这里它已经被加入到数据映射里面而不是测试用例,这样在整个项目里面都可以使用它

你现在可以开始操作更多的finder方法,包括收集Bookmark实例的方法。


class BookmarkMapperTestCase extends BaseTestCase {
// ... 
function testFindByGroup() {
$mapper = new BookmarkMapper($this->conn);
$this->addSeveralBookmarks($mapper);
$this->assertIsA(
$php_links = $mapper->findByGroup(‘php’)
,’array’);
$this->assertEqual(3, count($php_links));
foreach($php_links as $link) {
$this->assertIsA($link, ‘Bookmark’);
}
}




寻找特殊组的bookmarks 可以操作如下:


class BookmarkMapper {
// ...
public function findByGroup($group) {
$rs = $this->conn->execute(
‘select * from bookmark where tag like ?’
,array($group.’%’));
if ($rs) {
$ret = array();
foreach($rs->getArray() as $row) {
$ret[] = $this->createBookmarkFromRow($row);
}
return $ret;
}
}




ADOConnection::execute()方法返回的时一个ADOResultSet 对象。所以返回的结果有一个getArray() 方法来进行处理,返回的一个联合数组 (field => value)。数组包含了每一行的数据。

接着,这些数据行形成的数组传递给createBookmarkFromRow()方法进行处理并创建Bookmark类的实例。

怎么更新数据映射呢?更新的操作通用需要用到Bookmark和BookmarkMapper。确保bookmarks有没有更新最好的方法是使用BookmarkTestCase。测试数据库访问的部分则由测试BookmarkMapper的代码负责。


class BookmarkTestCase extends BaseTestCase {
// ...
function testSaveUpdatesDatabase() {
$mapper = new BookmarkMapper($this->conn);
$this->addSeveralBookmarks($mapper);
$bookmark = $mapper->findById(1);
$this->assertEqual(
‘http://blog.casey-sweat.us/’
,$bookmark->getUrl());
$bookmark->setUrl(
‘http://blog.casey-sweat.us/wp-rss2.php’);
$mapper->save($bookmark);
$bookmark2 = $mapper->findById(1);
$this->assertEqual(
‘http://blog.casey-sweat.us/wp-rss2.php’
,$bookmark2->getUrl());
}




现在,save()方法通过INSERT把新的bookmards插入到数据库。但是,就像这个测试用例涵盖的一样,save()现在必须确定Bookmark参数是新的或者已经增加到数据库里面了。对于前者,INSERT就可以操作了;对于后者,就需要用UPDATE了。

就目前的情况,让我们重构下操作INSERT语句的代码(这个原来是涵盖在save()方法里面的),成为一个新的私有的方法,命名为insert()。


class BookmarkMapper {
//...
protected function insert($bookmark) {
$rs = $this->conn->execute(
self::INSERT_SQL
,array(
$bookmark->getUrl()
,$bookmark->getName()
,$bookmark->getDesc()
,$bookmark->getGroup()));
if ($rs) {
$inserted = $this->findById($this->conn->Insert_ID());
// clean up database related fields in parameter instance
if (method_exists($inserted,’setId’)) {
$bookmark->setId($inserted->getId());
$bookmark->setCrtTime($inserted->getCrtTime());
$bookmark->setModTime($inserted->getModTime());
}
} else {
throw new Exception(‘DB Error: ‘.$this->conn->errorMsg());
}
}
}
 
在把现有的save()方法的一部分重新命名为insert()的同时,新的save()方法必须用getId()确认它的属性$id是否被设置。



class BookmarkMapper {
//...
public function save($bookmark) {
if ($bookmark->getId()) {
$this->update($bookmark);
} else {
$this->insert($bookmark);
}
}




现在,你还需要一个update() 方法,它和insert()方法很类似。回想一下,insert()方法按照固定的模式来编写代码从属性到域名进行数据映射。那么对于update(),让我们用一个更加动态的方法,从bookmark.xml里面获得信息并进行更改。


class BookmarkMapper {
//...
const UPDATE_SQL = “
update bookmark set
url = ?,
name = ?,
description = ?,
tag = ?,
updated = now()
where id = ?
“;
protected function update($bookmark) {
$binds = array();
foreach(array(‘url’,’name’,
‘description’,’tag’,’id’) as $fieldname) {
$field = $this->map[$fieldname];
$getprop = (string)$field->accessor;
$binds[] = $bookmark->$getprop();
}
$this->conn->execute(
self::UPDATE_SQL
,$binds);
}
}
 
值得注意的是数组里面元素的排列顺序和我们SQL语句需要的顺序是一致的。这个update()方法从数据映射里面捕捉到基础的内容:它建立起属性和域(列)的关系。


最后,让我们看下“删除”的操作。我们为BookmarkMapper类写一个方法来接受一个Bookmark并把它从数据库删掉。

首先,写一个测试代码:


class BookmarkMapperTestCase extends BaseTestCase {
// ...
function testDelete() {
$mapper = new BookmarkMapper($this->conn);
$this->addSeveralBookmarks($mapper);
$this->assertEqual(5, $this->countBookmarks());
$delete_me = $mapper->findById(3);
$mapper->delete($delete_me);
$this->assertEqual(4, $this->countBookmarks());
}
function countBookmarks() {
return $this->conn->getOne(
‘select count(1) from bookmark’);
}




代码本身:


class BookmarkMapper {
// ...
public function delete($bookmark) {
$this->conn->execute(
‘delete from bookmark where id = ?’
,array((int)$bookmark->getId()));
}




现在,你可以通过数据映射模式来完整第进行操作了。

如果你的域对象创建起来比较繁琐,你可能需要写一个BookmarkMapper::deleteById()方法,它不需要加载域对象就能删除数据。

总结

很明显,在数据库和域对象之间增加一个转换层会造成一定的复杂性。但是,这个复杂性可以给你的代码带来巨大的灵活性,因为你可以不管数据库的表结构自由地升级你的类。

另外,你还需要记住的是所有这些例子还只是一个非常简单的转换机制。如果你需要对这个简单的机制进行升级,你可以参考holy grail of ORM—ObjectRelational Mapping—那里面会进行详细的阐述。 

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

   
 1#楼 发表于2009-08-08 21:30:14  评分:× 

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

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