转载自:http://blog.csdn.net/fanhengguang_php/article/details/54964490
概述
本文简要介绍Active Record 设计模式。Active Record 是一种数据访问设计模式,它可以帮助你实现数据对象Object到关系数据库的映射。
应用Active Record 时,每一个类的实例对象唯一对应一个数据库表的一行(一对一关系)。你只需继承一个abstract Active Record 类就可以使用该设计模式访问数据库,其最大的好处是使用非常简单,事实上,这个设计模式被很多ORM产品使用,例如:Laravel ORM Eloquent, Yii ORM, FuelPHP ORM or Ruby on Rails ORM. 本文将用一个简单的例子阐述Active Record 设计模式是如何工作的(这个例子非常简单,如果要应用的话还有许多工作要做。)
假设我们有一个MobilePhone类, 包含以下属性
name
company
我们会将MobilePhone类代表的数据通过Active Record 方式插入到数据库表中,其对应的数据库表为:
CREATE TABLE IF NOT EXISTS phone
(
id
int(11) NOT NULL AUTO_INCREMENT,
name
varchar(255) NOT NULL,
company
varchar(255) NOT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1
MobilePhone类的实现如下:
[php] view plain copy
class MobilePhone extends ActiveRecordModel
{
protected
tablename=‘phone′;protected
t
a
b
l
e
n
a
m
e
=
‘
p
h
o
n
e
′
;
p
r
o
t
e
c
t
e
d
username =’root’;
protected
password=‘root′;protected
p
a
s
s
w
o
r
d
=
‘
r
o
o
t
′
;
p
r
o
t
e
c
t
e
d
hostname = ‘localhost’;
protected $dbname = ‘activerecord’;
}
如上所示,MobilePhone继承ActiveRecordModel类, 并且包含了用于连接数据库的几个属性(username, password…).
Insert data
基于ActiveRecord模式,可以用如下代码插入一条数据
[php] view plain copy
// create a new phone
$phone = new MobilePhone(array(
“name” => “cool phone”,
“company” => “nekia”
));
// save it
$phone->save();
以上代码看起来非常简单易用, 这些都得益于ActiveRecordModel类, 我们看下是如何实现的。
[php] view plain copy
abstract class ActiveRecordModel
{
/**
* The attributes that belongs to the table
* @var Array
*/
protected
attributes=array();/∗∗∗Tablename∗@varString∗/protected
a
t
t
r
i
b
u
t
e
s
=
a
r
r
a
y
(
)
;
/
∗
∗
∗
T
a
b
l
e
n
a
m
e
∗
@
v
a
r
S
t
r
i
n
g
∗
/
p
r
o
t
e
c
t
e
d
table_name;
/**
* Username
* @var String
*/
protected
username;/∗∗∗password∗@varString∗/protected
u
s
e
r
n
a
m
e
;
/
∗
∗
∗
p
a
s
s
w
o
r
d
∗
@
v
a
r
S
t
r
i
n
g
∗
/
p
r
o
t
e
c
t
e
d
password;
/**
* The DBMS hostname
* @var String
*/
protected
hostname;/∗∗∗Thedatabasename∗@varString∗/protected
h
o
s
t
n
a
m
e
;
/
∗
∗
∗
T
h
e
d
a
t
a
b
a
s
e
n
a
m
e
∗
@
v
a
r
S
t
r
i
n
g
∗
/
p
r
o
t
e
c
t
e
d
dbname;
/**
* The DBMS connection port
* @var String
*/
protected $port = “3306”;
protected $id_name = 'id';
function __construct(Array $attributes = null) {
$this->attributes = $attributes;
}
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
public function newInstance(array $data)
{
$class_name = get_class($this);
return new $class_name($data);
}
/**
* Save the model
* @return bool
*/
public function save()
{
try
{
if(array_key_exists($this->id_name, $this->attributes))
{
$attributes = $this->attributes;
unset($attributes[$this->id_name]);
$this->update($attributes);
}
else
{
$id = $this->insert($this->attributes);
$this->setAttribute($this->id_name, $id);
}
}
catch(ErrorException $e)
{
return false;
}
return true;
}
/**
* Used to prepare the PDO statement
*
* @param $connection
* @param $values
* @param $type
* @return mixed
* @throws InvalidArgumentException
*/
protected function prepareStatement($connection, $values, $type)
{
if($type == "insert")
{
$sql = "INSERT INTO {$this->table_name} (";
foreach ($values as $key => $value) {
$sql.="{$key}";
if($value != end($values) )
$sql.=",";
}
$sql.=") VALUES(";
foreach ($values as $key => $value) {
$sql.=":{$key}";
if($value != end($values) )
$sql.=",";
}
$sql.=")";
}
elseif($type == "update")
{
$sql = "UPDATE {$this->table_name} SET ";
foreach ($values as $key => $value) {
$sql.="{$key} =:{$key}";
if($value != end($values))
$sql.=",";
}
$sql.=" WHERE {$this->id_name}=:{$this->id_name}";
}
else
{
throw new InvalidArgumentException("PrepareStatement need to be insert,update or delete");
}
return $connection->prepare($sql);
}
/**
* Used to insert a new record
* @param array $values
* @throws ErrorException
*/
public function insert(array $values)
{
$connection = $this->getConnection();
$statement = $this->prepareStatement($connection, $values, "insert");
foreach($values as $key => $value)
{
$statement->bindValue(":{$key}", $value);
}
$success = $statement->execute($values);
if(! $success)
throw new ErrorException;
return $connection->lastInsertId();
}
/**
* Get the connection to the database
*
* @throws PDOException
*/
protected function getConnection()
{
try {
$conn = new PDO("mysql:host={$this->hostname};dbname={$this->dbname};port=$this->port", $this->username, $this->password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
}
}
当我们设置类的属性的时候,__set()魔术方法会被自动调用,会将属性的名字及值写入到类成员
attributes中(这些属性名与表中的列名一致)。对于上面这个例子,我们直接在新建类对象的时候对
a
t
t
r
i
b
u
t
e
s
中
(
这
些
属
性
名
与
表
中
的
列
名
一
致
)
。
对
于
上
面
这
个
例
子
,
我
们
直
接
在
新
建
类
对
象
的
时
候
对
attributes成员进行了赋值(参见__construct()方法)。
接下来我们调用save方法
phone−>save();,save方法会调用insert方法并将
p
h
o
n
e
−
>
s
a
v
e
(
)
;
,
s
a
v
e
方
法
会
调
用
i
n
s
e
r
t
方
法
并
将
attributes作为参数传递给insert, 在insert方法中首先新建一个数据库连接,然后将
attributes构成的key=>value键值对通过PDO插入到数据库中,最后将插入的新行的id写入到
a
t
t
r
i
b
u
t
e
s
构
成
的
k
e
y
=>
v
a
l
u
e
键
值
对
通
过
P
D
O
插
入
到
数
据
库
中
,
最
后
将
插
入
的
新
行
的
i
d
写
入
到
attributes中。
Update data
你可以通过修改一个model的成员来实现修改数据库中的某一行, 比如
model−>update(array("newvalue"=>"value))或者你可以直接设置model的成员变量
m
o
d
e
l
−
>
u
p
d
a
t
e
(
a
r
r
a
y
(
"
n
e
w
v
a
l
u
e
"=>"
v
a
l
u
e
)
)
或
者
你
可
以
直
接
设
置
m
o
d
e
l
的
成
员
变
量
model->newvalue = “value”然后执行$model->save(), 这两种方式都是有效的。
比如以下例子:
phone−>name=“newname!”;
p
h
o
n
e
−
>
n
a
m
e
=
“
n
e
w
n
a
m
e
!
”
;
phone->save();
update实现原理如下:
[php] view plain copy
abstract class ActiveRecordModel
…
/**
* Update the current row with new values
*
* @param array
values∗@returnbool∗@throwsErrorException∗@throwsBadMethodCallException∗/publicfunctionupdate(array
v
a
l
u
e
s
∗
@
r
e
t
u
r
n
b
o
o
l
∗
@
t
h
r
o
w
s
E
r
r
o
r
E
x
c
e
p
t
i
o
n
∗
@
t
h
r
o
w
s
B
a
d
M
e
t
h
o
d
C
a
l
l
E
x
c
e
p
t
i
o
n
∗
/
p
u
b
l
i
c
f
u
n
c
t
i
o
n
u
p
d
a
t
e
(
a
r
r
a
y
values)
{
if( ! isset(
this−>attributes[
t
h
i
s
−
>
a
t
t
r
i
b
u
t
e
s
[
this->id_name]))
throw new BadMethodCallException(“Cannot call update on an object non already fetched”);
$connection = $this->getConnection();
$statement = $this->prepareStatement($connection, $values, "update");
foreach($values as $key => $value)
{
$statement->bindValue(":{$key}", $value);
}
$statement->bindValue(":{$this->id_name}", $this->attributes[$this->id_name]);
$success = $statement->execute();
// update the current values
foreach($values as $key => $value)
{
$this->setAttribute($key, $value);
}
if(! $success)
throw new ErrorException;
return true;
}
如上,update方法会根据attributes成员新建一个PDO statement, 然后执行这个statement, 最后更新$attributes成员。
Find and update
我们也可以通过find 或者 where方法得到一个特定id对应的类实例,或者一个特定条件下得到一些类实例构成的数组。
例子:
samephone=
s
a
m
e
p
h
o
n
e
=
phone->find(77);
得到一个id为77的phone对象。
ActiveRecordModel 中对应的实现为:
[html] view plain copy
abstract class ActiveRecordModel
…
/**
* Find a row given the id
*
* @param
id∗@returnnull|Mixed∗/publicfunctionfind(
i
d
∗
@
r
e
t
u
r
n
n
u
l
l
|
M
i
x
e
d
∗
/
p
u
b
l
i
c
f
u
n
c
t
i
o
n
f
i
n
d
(
id)
{
conn=
c
o
n
n
=
this->getConnection();
query=
q
u
e
r
y
=
conn->query(“SELECT * FROM {this->table_name} WHERE {$this->id_name}= " .
this->table_name} WHERE {$this->id_name}= " .
conn->quote(
id));
i
d
)
)
;
obj = $query->fetch(PDO::FETCH_ASSOC);
return ($obj) ? $this->newInstance($obj) : null;
}
假如你想使用where 条件查询,可以这样做:
phone=
p
h
o
n
e
=
phone->where(“company=’nekia’”);
实现方式如下:
[html] view plain copy
abstract class ActiveRecordModel
….
/**
* Find rows given a where condition
*
* @param
wherecond∗@returnnull|PDOStatement∗/publicfunctionwhere(
w
h
e
r
e
c
o
n
d
∗
@
r
e
t
u
r
n
n
u
l
l
|
P
D
O
S
t
a
t
e
m
e
n
t
∗
/
p
u
b
l
i
c
f
u
n
c
t
i
o
n
w
h
e
r
e
(
where_cond)
{
conn=
c
o
n
n
=
this->getConnection();
query=
q
u
e
r
y
=
conn->query(“SELECT * FROM {this->table_name} WHERE {$where_cond}”);
this->table_name} WHERE {$where_cond}”);
objs =
query−>fetchAll(PDO::FETCHASSOC);//themodelinstantiated
q
u
e
r
y
−
>
f
e
t
c
h
A
l
l
(
P
D
O
::
F
E
T
C
H
A
S
S
O
C
)
;
/
/
t
h
e
m
o
d
e
l
i
n
s
t
a
n
t
i
a
t
e
d
models = array();
if(! empty($objs))
{
foreach($objs as $obj)
{
$models[] = $this->newInstance($obj);
}
}
return $models;
}
通过上面的例子,我们可以看到,ActiveRecord的实质就是将一个类的实例与数据库表中的一行关联起来了,将数据库表的列与类的attributes成员映射起来,并使用attributes的值进行数据库的查询和写入。
总结
ActiveRecord 设计模式非常简单易懂。但是缺少灵活性,实际中当你每次处理一张表时使用这个模式是可以的, 但是当你要处理很多关联表,类继承关系较为复杂的时候就会有很多问题,这种情况你可以使用Data Mapper模式,之后会写一个新的章节对其介绍。
本文翻译自:http://www.jacopobeschi.com/post/active-record-design-pattern