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


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

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

 

 发表:2009-08-08 20:57:02 阅读 1922 次 回复 1 次 得分0  |   字号 字色
《PHP设计模式介绍》第十四章 动态记录模式
到目前为止,您所看到的这些设计模式大大提高了代码的可读性与可维护性。然而,在WEB应用设计与开发中一个基本的需求与挑战:数据库应用,这些设计模式都没有涉及到。本章与接下来的两章—表数据网关与表数据映射,提供了三种设计模式使您能更好的组织你的应用程序与数据库进行交互。

问题

大多数WEB应用将信息持续保存在数据库中。有将数据库操作抽象化,以达到简化表数据存取和对业务逻辑的集成存取方法吗?

解决方案

理论上,动态记录模式是最简化的有关数据库的设计模式。动态记录模式包含了如何在类中直接实现与数据库交互的相关知识。

动态记录模式在程序代码与数据库结构之间产生了一种很高的结合度,在一些相对简单的应用环境中,就能比采用别的复杂方案更容易解决这种因结合所产生的一些固有问题。动态记录模式也能满足许多初级的数据库项目。只有当复杂性增加而难以用动态记录模式处理时,你才有必要使用表数据网关模式(参见15章),或是数据地图模式(参见16章)或是别的数据库设计模式

企业级应用架构模式

根据Martin Fowler’s的著作《企业级应用架构模式》,所谓企业级应用就是与别的应用集成化,包含了重要的业务逻辑(或如应用需求所呈现的非逻辑的东西),并且具有许多并发存取和保存从各种接口取得的数据。有趣的是,web应用正好具备了上述多个特点,这正好能解释为什么Martin Fowler’s的著作能引起PHP程序员的强烈反响。

PHP数据对象

PDO是一个高性能的数据库通道接口(并非数据库抽象)。PDO是一个由C语言构成的本地驱动包,因此其速度是很快的。PDO为所有的PDO驱动提供了申明,增强了脚本使用库时的安全性。 

样本代码

任何对数据库连接的讨论都依赖于对数据库系统与对数据库访问层的选择。本章与随后两章都使用MYSQL(http://www.mysql.com/)这个流行的开源数据库及ADOdb (http://adodb.sf.net/)作为数据库访问层。我将ADOdb作为我自己工作室的标准是因为它优异的性能,且抽象了Oracle OCI接口,并提供了统一的访问PostgreSQL, Sybase, MySQL和其它数据库的接口,而成为了易于使用的PHP API,让你专注于程序与业务逻辑的处理。 

放心的替换你的自己的数据库与访问层,因为这里提出的许多概念也适合于别的解决方案。

在研究t动态记录模式之前,让我们从基本的数据库连接开始。有一个集中的,简单的方式去指定连接参数(主机名,用户名,密码,数据库)并建立一个数据库连接对象是很理想的。一个单一模式对象(参见第四章)就非常适合了。

这是一个DB类,其conn()方法返回一个单一模式的ADOConnection类的实例。


// PHP5
require_once ‘adodb/adodb.inc.php’;
class DB {
//static class, we do not need a constructor private function __construct() {}
public static function conn() {
static $conn;
if (!$conn) {
$conn = adoNewConnection(‘mysql’);
$conn->connect(‘localhost’, ‘username’, ‘passwd’, ‘database’);
$conn->setFetchMode(ADODB_FETCH_ASSOC);
}
return $conn;
}



DB类允许你设定数据库的类型与连接参数。第一行代码将ADOdb库包含进来(你可能需要根据你的实际环境来调整路径);因为没有必要每次都实例化DB,所以DB的构造函数是私有的; 行$conn->setFetchMode(ADODB_FETCH_ASSOC)设定对象返回的记录集是以(字段名=>值)形式的关联数组。与数据库打交道中采用关联数组是非常重要的经验习惯,这样您的代码就不会受到因SQL语句中字段排序而产生的影响。

作为示例程序,让我们建立一个Active Record对象来维护一个超链接表。以下是一个在MySQL数据库中建立这个超链接表的SQL。


define(‘BOOKMARK_TABLE_DDL’, <<<EOS
CREATE TABLE `bookmark` (
`id` INT NOT NULL AUTO_INCREMENT ,
`url` VARCHAR( 255 ) NOT NULL ,
`name` VARCHAR( 255 ) NOT NULL ,
`description` MEDIUMTEXT,
`tag` VARCHAR( 50 ) ,
`created` DATETIME NOT NULL ,
`updated` DATETIME NOT NULL , PRIMARY KEY ( `id` )
) EOS
); 


实验的独立性

各个实验间应是相互独立的;否则,仅仅是运行了某一个实验就会影响到后续实验的结果。

为了避免这些都基于同一数据库的实验间相互干扰,最好是在每个测试开始前删除并重建相关表。以下简单的实验为后续实验提供了一种标准的setup方法。

以下代码演示如何在每个实验开始前重置你的数据库:


class ActiveRecordTestCase extends UnitTestCase {
protected $conn;
function __construct($name=’’) {
$this->UnitTestCase($name);
$this->conn = DB::conn();
}
function setup() {
$this->conn->execute(‘drop table bookmark’);
$this->conn->execute(BOOKMARK_TABLE_DDL);
}



这段代码用一个标准的ADOConnection对象来给$conn的属性赋值,并且使用了Connection的execute()方法来执行SQL删除与重建表的操作。因为这些代码在一个名为setup()的方法中,使得每一个实验都能在一个新的数据库环境中工作。

更进一步,你应做一些对setup()方法的较为全面的测试(并多学习一些关于ADOConnection API的实例)


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testSetupLeavesTableEmptyWithCorrectStructure() {
$rs = $this->conn->execute(‘select * from bookmark’);
$this->assertIsA($rs, ‘ADORecordSet’);
$this->assertEqual(0,$rs->recordCount());
foreach(array(
‘id’,
‘url’,
‘name’,
‘description’,
‘tag’,
‘created’,
‘updated’) as $i => $name) {
$this->assertEqual($name, $rs->fetchField($i)->name);
}
}




即使你不太熟悉ADOdb,你也能正确的认识到execute()方法在成功执行后将返回一个ADORecordSet 对象,这个对象有一个recordCount()方法,这个方法可以验证表是否为空。记录集对象还有一些方法去浏览记录集元数据和fetchField()方法来效验表结构。

记录建立

在连接到数据库后,您的“创建,读取,更新,删除”(CRUD)程序就能在数据库中进行相关的行操作。


CRUD

CRUD 是创建,读取,更新,删除的缩写. 这些都是数据库交互式应用系统的基础

许多PHP WEB使用都是CRUD界面应用的示例

(http://c2.com/cgi/wiki?CrudScreen).

下面的示例应用是将一些书签存入数据库,因此我们将这个动态记录类命名为Bookmark。要建立一个新的书签,可使用new方法建立一个该类的实例,并设定实例的相关属性。当所有的属性都已设定(强制性),我们使用save()方法将其存入到数据库中。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testNew() {
$link = new Bookmark;
$link->url = ‘http://simpletest.org/’;
$link->name = ‘SimpleTest’;
$link->description = ‘SimpleTest project homepage’;
$link->tag = ‘testing’;
$link->save();
$this->assertEqual(1, $link->getId());
}




从这个实验中我们看到,Bookmark类有一些公共的属性与一个save()方法。当实例被保存到数据库后,getid()方法可以返回指定给它的数据库记录号(ID)。

下面是Bookmark类的属性定义


class Bookmark {
public $url;
public $name;
public $description;
public $tag;




让我们转向研究save()方法。它需要一个数据库连接,因此我们在构造函数中用DB::conn()连接工厂实现。


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



$conn 现在就成为适合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());
}
}




ADOdb 的 MySQL 驱动支持按位置的参数替换功能,并且能正确的引用(按类型加引号)参数。SQL的形参以问号(?)的形式指定,你可以在execute()方法的第二个参数的位置,以数组的形式传递实参值到SQL语句中来替换以问号点位的形参。

Insert_ID()方法可能已引起你的注意:它返回最后一次执行插入操作时自增长列的值。

到目前为止,这些实验已证明了以下事实:属性可以被设置,save()能正常工作,$ID属性已被置为1。让我们进一步的深入到数据表去验证其它的属性值也被正确的保存。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testNew() {
$link = new Bookmark;
$link->url = ‘http://simpletest.org/’;
$link->name = ‘SimpleTest’;
$link->description = ‘SimpleTest project homepage’;
$link->tag = ‘testing’;
$link->save();
$this->assertEqual(1, $link->getId());
// fetch the table as an array of hashes
$rs = $this->conn->getAll(‘select * from bookmark’);
$this->assertEqual(1, count($rs), ‘returned 1 row’);
foreach(array(‘url’, ‘name’, ‘description’, ‘tag’) as $key) {
$this->assertEqual($link->$key, $rs[0][$key]);
}
}




以上突出显示代码的功能是获取整个书签表数据。GetAll()方法执行查询并返回结果集,该结果集是以数组形式存放的记录的哈稀表。AssertEqual()方法验证只有一条记录存在于结果集中。通过foreach循环比较从数据表中取得记录的字段与$link对象的属性值是否一致。

上述代码已能正常工作,但通过手工的方法设定属性值去增加书签表数据的方法还是略显繁琐。因此,为上述的案例增加一个方便(通用)的方法,来实现增加的新建书签对象。

The ActiveRecordTestCase::add()方法带有(处理)四个参数,可建立与插入一个新的ActiveRecord书签对象。如果你在后续实验中要用到新创建的书签对象,add()方法还可以在创建成功后返回它。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function add($url, $name, $description, $tag) {
$link = new Bookmark;
$link->url = $url;
$link->name = $name;
$link->description = $description;
$link->tag = $tag;
$link->save();
return $link;
}




你完全可以在本实验案例中写一个测试方法来证明其可用性。


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testAdd() {
$this->add(‘http://php.net’, ‘PHP’,
‘PHP Language Homepage’, ‘php’);
$this->add(‘http://phparch.com’, ‘php|architect’,
‘php|arch site’, ‘php’);
$rs = $this->conn->execute(‘select * from bookmark’);
$this->assertEqual(2,$rs->recordCount());
$this->assertEqual(2,$this->conn->Insert_ID());
}




既然书签可以创建并存储于数据库中,让我们给Active Record书签对象增加一个方法,可以简单的从数据库中获取数据并在实例的属性中保存所获取的值。一种通用的建立动态记录对象的技术是通过传递一个标示符,如书签号(或是别的什么标准)到它的构造函数中,并且从数据库中取出与这个ID相关联的行数据。 


class ActiveRecordTestCase extends UnitTestCase {
// ...
function testCreateById() {
$link = $this->add(
‘http://blog.casey-sweat.us/’,
‘My Blog’,
‘Where I write about stuff’,
‘php’);
$this->assertEqual(1, $link->getId());
$link2 = new Bookmark(1);
$this->assertIsA($link2, ‘Bookmark’);
$this->assertEqual($link, $link2);
}




这个实验传递了一个ID到构造函数,这是前面的实验所没有出现过的。是否传递ID是可选的,如果没有传递ID,则前述试验中建立新的空书签实例的功能将依然正常工作。

这儿是一些实现上述功能要求的代码。


class Bookmark {
// ...
const SELECT_BY_ID = ‘select * from bookmark where id = ?’;
public function __construct($id=false) {
$this->conn DB::conn();
if ($id) {
$rs = $this->conn->execute(
self::SELECT_BY_ID
,array((int)$id));
if ($rs) {
$row = $rs->fetchRow();
foreach($row as $field => $value) {
$this->$field = $value;
}
} else {
trigger_error(‘DB Error: ‘.$this->conn->errorMsg());
}
}
}
// ...




构造函数允许一个名为$id的参数,它的默认为假。如果传来的参数不为假,则BOOKmark则用此ID为关键字查询数据库中BOOKmark表的相关行,如果该行存在,则用获取的数据来设定对象属性的值。

数据错误测试


Mock::generate(‘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!!!!’);
}




这段代码调用了Mock::generate() 来生成一个MockADOConnection 类,并生成一个模拟连接的实例,同时设定一些基本的返回值来指明错误,和定义在这些环境中可能会出现的意外。
 
 
 1#楼  
 
  回复人:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

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

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

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