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

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 $table_name = 'phone';  
    protected $username ='root';  
    protected $password = 'root';  
    protected $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();  
    /** 
     * Table name 
     * @var  String 
     */  
    protected $table_name;  
    /** 
     * Username 
     * @var String 
     */  
    protected $username;  
    /** 
     * password 
     * @var  String 
     */  
    protected $password;  
    /** 
     * The DBMS hostname 
     * @var  String 
     */  
    protected $hostname;  
    /** 
     * The database name 
     * @var  String 
     */  
    protected $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中(这些属性名与表中的列名一致)。对于上面这个例子,我们直接在新建类对象的时候对 attributes()attributes成员进行了赋值(参见__construct()方法)。

接下来我们调用save方法 p h o n e − > s a v e ( ) ; , s a v e 方 法 会 调 用 i n s e r t 方 法 并 将 phone->save(); , save方法会调用insert方法并将 phone>save();,saveinsertattributes作为参数传递给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构成的key => value键值对通过PDO插入到数据库中, 最后将插入的新行的id写入到 attributeskey=>valuePDOidattributes中。
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->update(array("newvalue"=>"value)) 或者你可以直接设置model的成员变量 model>update(array("newvalue"=>"value))modelmodel->newvalue = "value"然后执行$model->save(), 这两种方式都是有效的。

比如以下例子:

$phone->name = “new name!”;
$phone->save();

update实现原理如下:

[php] view plain copy

abstract class ActiveRecordModel  
...  
 /** 
 * Update the current row with new values 
 * 
 * @param array $values 
 * @return bool 
 * @throws ErrorException 
 * @throws BadMethodCallException 
 */  
public function update(array $values)  
{  
    if( ! isset($this->attributes[$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对应的类实例,或者一个特定条件下得到一些类实例构成的数组。

例子:

$same_phone = $phone->find(77);

得到一个id为77的phone对象。

ActiveRecordModel 中对应的实现为:

[html] view plain copy

abstract class ActiveRecordModel  
...  
 /**  
 * Find a row given the id  
 *  
 * @param $id  
 * @return null|Mixed  
 */  
public function find($id)  
{  
    $conn = $this->getConnection();  
    $query = $conn->query("SELECT * FROM {$this->table_name} WHERE  {$this->id_name}= " . $conn->quote($id));  
    $obj = $query->fetch(PDO::FETCH_ASSOC);  
  
    return ($obj) ? $this->newInstance($obj) : null;  
}  

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

$phone = $phone->where(“company=‘nekia’”);

实现方式如下:

[html] view plain copy

abstract class ActiveRecordModel  
....  
/**  
 * Find rows given a where condition  
 *  
 * @param $where_cond  
 * @return null|PDOStatement  
 */  
public function where($where_cond)  
{  
    $conn = $this->getConnection();  
    $query = $conn->query("SELECT * FROM {$this->table_name} WHERE {$where_cond}");  
    $objs = $query->fetchAll(PDO::FETCH_ASSOC);  
    // the model instantiated  
    $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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值