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
 
  1. class MobilePhone extends ActiveRecordModel  
  2. {  
  3.     protected $table_name = 'phone';  
  4.     protected $username ='root';  
  5.     protected $password = 'root';  
  6.     protected $hostname = 'localhost';  
  7.     protected $dbname = 'activerecord';  
  8. }  

 

如上所示,MobilePhone继承ActiveRecordModel类, 并且包含了用于连接数据库的几个属性(username, password...).

Insert data

基于ActiveRecord模式,可以用如下代码插入一条数据

 

[php] view plain copy
 
  1. // create a new phone  
  2. $phone = new MobilePhone(array(  
  3.  "name" => "cool phone",  
  4.  "company" => "nekia"  
  5. ));  
  6.   
  7. // save it  
  8. $phone->save();  


以上代码看起来非常简单易用, 这些都得益于ActiveRecordModel类, 我们看下是如何实现的。

 

 

[php] view plain copy
 
  1. abstract class ActiveRecordModel  
  2. {  
  3.     /** 
  4.      * The attributes that belongs to the table 
  5.      * @var  Array 
  6.      */  
  7.     protected $attributes = array();  
  8.     /** 
  9.      * Table name 
  10.      * @var  String 
  11.      */  
  12.     protected $table_name;  
  13.     /** 
  14.      * Username 
  15.      * @var String 
  16.      */  
  17.     protected $username;  
  18.     /** 
  19.      * password 
  20.      * @var  String 
  21.      */  
  22.     protected $password;  
  23.     /** 
  24.      * The DBMS hostname 
  25.      * @var  String 
  26.      */  
  27.     protected $hostname;  
  28.     /** 
  29.      * The database name 
  30.      * @var  String 
  31.      */  
  32.     protected $dbname;  
  33.     /** 
  34.      * The DBMS connection port 
  35.      * @var  String 
  36.      */  
  37.     protected $port = "3306";  
  38.       
  39.     protected $id_name = 'id';  
  40.       
  41.     function __construct(Array $attributes = null) {  
  42.         $this->attributes = $attributes;  
  43.     }  
  44.     public function __set($key, $value)  
  45.     {  
  46.         $this->setAttribute($key, $value);  
  47.     }  
  48.     public function newInstance(array $data)  
  49.     {  
  50.         $class_name = get_class($this);  
  51.         return new  $class_name($data);  
  52.     }  
  53.       
  54.     /** 
  55.      * Save the model 
  56.      * @return bool 
  57.      */  
  58.     public function save()  
  59.     {  
  60.         try  
  61.         {  
  62.             if(array_key_exists($this->id_name, $this->attributes))  
  63.             {  
  64.                 $attributes = $this->attributes;  
  65.                 unset($attributes[$this->id_name]);  
  66.                 $this->update($attributes);  
  67.             }  
  68.             else  
  69.             {  
  70.                 $id = $this->insert($this->attributes);  
  71.                 $this->setAttribute($this->id_name, $id);  
  72.             }  
  73.         }  
  74.         catch(ErrorException $e)  
  75.         {  
  76.             return false;  
  77.         }  
  78.       
  79.         return true;  
  80.     }  
  81.       
  82.     /** 
  83.      * Used to prepare the PDO statement 
  84.      * 
  85.      * @param $connection 
  86.      * @param $values 
  87.      * @param $type 
  88.      * @return mixed 
  89.      * @throws InvalidArgumentException 
  90.      */  
  91.     protected function prepareStatement($connection, $values, $type)  
  92.     {  
  93.         if($type == "insert")  
  94.         {  
  95.         $sql = "INSERT INTO {$this->table_name} (";  
  96.         foreach ($values as $key => $value) {  
  97.             $sql.="{$key}";  
  98.             if($value != end($values) )  
  99.                 $sql.=",";  
  100.         }  
  101.         $sql.=") VALUES(";  
  102.         foreach ($values as $key => $value) {  
  103.             $sql.=":{$key}";  
  104.             if($value != end($values) )  
  105.                 $sql.=",";  
  106.         }  
  107.         $sql.=")";  
  108.         }  
  109.         elseif($type == "update")  
  110.         {  
  111.             $sql = "UPDATE {$this->table_name} SET ";  
  112.             foreach ($values as $key => $value) {  
  113.                 $sql.="{$key} =:{$key}";  
  114.                 if($value != end($values))  
  115.                     $sql.=",";  
  116.             }  
  117.             $sql.=" WHERE {$this->id_name}=:{$this->id_name}";  
  118.         }  
  119.         else  
  120.         {  
  121.             throw new InvalidArgumentException("PrepareStatement need to be insert,update or delete");  
  122.         }  
  123.       
  124.         return $connection->prepare($sql);  
  125.     }  
  126.       
  127.     /** 
  128.      * Used to insert a new record 
  129.      * @param array $values 
  130.      * @throws ErrorException 
  131.      */  
  132.     public function insert(array $values)  
  133.     {  
  134.         $connection = $this->getConnection();  
  135.         $statement = $this->prepareStatement($connection, $values, "insert");  
  136.         foreach($values as $key => $value)  
  137.         {  
  138.             $statement->bindValue(":{$key}", $value);  
  139.         }  
  140.       
  141.         $success = $statement->execute($values);  
  142.         if(! $success)  
  143.             throw new ErrorException;  
  144.       
  145.         return $connection->lastInsertId();  
  146.     }  
  147.       
  148.     /** 
  149.      * Get the connection to the database 
  150.      * 
  151.      * @throws  PDOException 
  152.      */  
  153.     protected function getConnection()  
  154.     {  
  155.         try {  
  156.             $conn = new PDO("mysql:host={$this->hostname};dbname={$this->dbname};port=$this->port", $this->username, $this->password);  
  157.             $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
  158.       
  159.             return $conn;  
  160.         } catch(PDOException $e) {  
  161.             echo 'ERROR: ' . $e->getMessage();  
  162.         }  
  163.     }  
  164. }  


当我们设置类的属性的时候,__set()魔术方法会被自动调用,会将属性的名字及值写入到类成员$attributes中(这些属性名与表中的列名一致)。对于上面这个例子,我们直接在新建类对象的时候对$attributes成员进行了赋值(参见__construct()方法)。

 

接下来我们调用save方法$phone->save(); , save方法会调用insert方法并将$attributes作为参数传递给insert, 在insert方法中首先新建一个数据库连接,然后将$attributes构成的key => value键值对通过PDO插入到数据库中, 最后将插入的新行的id写入到$attributes中。

Update data

你可以通过修改一个model的成员来实现修改数据库中的某一行, 比如$model->update(array("newvalue"=>"value)) 或者你可以直接设置model的成员变量$model->newvalue = "value"然后执行$model->save(), 这两种方式都是有效的。

比如以下例子:

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

update实现原理如下:

 

[php] view plain copy
 
  1. abstract class ActiveRecordModel  
  2. ...  
  3.  /** 
  4.  * Update the current row with new values 
  5.  * 
  6.  * @param array $values 
  7.  * @return bool 
  8.  * @throws ErrorException 
  9.  * @throws BadMethodCallException 
  10.  */  
  11. public function update(array $values)  
  12. {  
  13.     if( ! isset($this->attributes[$this->id_name]))  
  14.         throw new BadMethodCallException("Cannot call update on an object non already fetched");  
  15.   
  16.     $connection = $this->getConnection();  
  17.     $statement = $this->prepareStatement($connection, $values, "update");  
  18.     foreach($values as $key => $value)  
  19.     {  
  20.         $statement->bindValue(":{$key}", $value);  
  21.     }  
  22.     $statement->bindValue(":{$this->id_name}", $this->attributes[$this->id_name]);  
  23.     $success = $statement->execute();  
  24.   
  25.     // update the current values  
  26.     foreach($values as $key => $value)  
  27.     {  
  28.         $this->setAttribute($key, $value);  
  29.     }  
  30.   
  31.     if(! $success)  
  32.         throw new ErrorException;  
  33.   
  34.     return true;  
  35. }  


如上,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
 
  1. abstract class ActiveRecordModel  
  2. ...  
  3.  /**  
  4.  * Find a row given the id  
  5.  *  
  6.  * @param $id  
  7.  * @return null|Mixed  
  8.  */  
  9. public function find($id)  
  10. {  
  11.     $conn = $this->getConnection();  
  12.     $query = $conn->query("SELECT * FROM {$this->table_name} WHERE  {$this->id_name}= " . $conn->quote($id));  
  13.     $obj = $query->fetch(PDO::FETCH_ASSOC);  
  14.   
  15.     return ($obj) ? $this->newInstance($obj) : null;  
  16. }  

 

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

$phone = $phone->where("company='nekia'");

实现方式如下:

 

[html] view plain copy
 
  1. abstract class ActiveRecordModel  
  2. ....  
  3. /**  
  4.  * Find rows given a where condition  
  5.  *  
  6.  * @param $where_cond  
  7.  * @return null|PDOStatement  
  8.  */  
  9. public function where($where_cond)  
  10. {  
  11.     $conn = $this->getConnection();  
  12.     $query = $conn->query("SELECT * FROM {$this->table_name} WHERE {$where_cond}");  
  13.     $objs = $query->fetchAll(PDO::FETCH_ASSOC);  
  14.     // the model instantiated  
  15.     $models = array();  
  16.   
  17.     if(! empty($objs))  
  18.     {  
  19.         foreach($objs as $obj)  
  20.         {  
  21.             $models[] = $this->newInstance($obj);  
  22.         }  
  23.     }  
  24.   
  25.     return $models;  
  26. }  


通过上面的例子,我们可以看到,ActiveRecord的实质就是将一个类的实例与数据库表中的一行关联起来了,将数据库表的列与类的attributes成员映射起来,并使用attributes的值进行数据库的查询和写入。

 

总结

ActiveRecord 设计模式非常简单易懂。但是缺少灵活性,实际中当你每次处理一张表时使用这个模式是可以的, 但是当你要处理很多关联表,类继承关系较为复杂的时候就会有很多问题,这种情况你可以使用Data Mapper模式,之后会写一个新的章节对其介绍。

本文翻译自:http://www.jacopobeschi.com/post/active-record-design-pattern

 

posted @ 2017-11-24 16:49  sky20080101  阅读(187)  评论(0)    收藏  举报