Active Record 设计模式原理及简单实现
Active Record 设计模式原理及简单实现
概述
本文简要介绍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类的实现如下:
- 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模式,可以用如下代码插入一条数据
- // create a new phone
- $phone = new MobilePhone(array(
- "name" => "cool phone",
- "company" => "nekia"
- ));
- // save it
- $phone->save();
以上代码看起来非常简单易用, 这些都得益于ActiveRecordModel类, 我们看下是如何实现的。
- 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()魔术方法会被自动调用,会将属性的名字及值写入到类成员$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实现原理如下:
- 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 中对应的实现为:
- 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'");实现方式如下:
- 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

 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号