Active Record 设计模式原理及简单实现

转载自: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 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 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 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 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 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()魔术方法会被自动调用,会将属性的名字及值写入到类成员 a t t r i b u t e s ( ) attributes成员进行了赋值(参见__construct()方法)。
接下来我们调用save方法 p h o n e > s a v e ( ) ; , s a v e i n s e r t attributes作为参数传递给insert, 在insert方法中首先新建一个数据库连接,然后将 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的成员来实现修改数据库中的某一行, 比如 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(), 这两种方式都是有效的。

比如以下例子:

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 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( 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对应的类实例,或者一个特定条件下得到一些类实例构成的数组。

例子:

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 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)
{
c o n n = this->getConnection();
q u e r y = conn->query(“SELECT * FROM { this->table_name} WHERE  {$this->id_name}= " . conn->quote( i d ) ) ; obj = $query->fetch(PDO::FETCH_ASSOC);

return ($obj) ? $this->newInstance($obj) : null;  

}
假如你想使用where 条件查询,可以这样做:

p h o n e = phone->where(“company=’nekia’”);
实现方式如下:

[html] view plain copy
abstract class ActiveRecordModel
….
/**
* Find rows given a where condition
*
* @param 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)
{
c o n n = this->getConnection();
q u e r y = conn->query(“SELECT * FROM { this->table_name} WHERE {$where_cond}”); objs = 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

发布了49 篇原创文章 · 获赞 31 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/zengqiang1/article/details/80001988