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類的實現如下:
[php] view plain copy
class MobilePhone extends ActiveRecordModel
{
protected username =’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 table_name;
/**
* Username
* @var String
*/
protected password;
/**
* The DBMS hostname
* @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成員進行了賦值(參見__construct()方法)。
接下來我們呼叫save方法attributes作為引數傳遞給insert, 在insert方法中首先新建一個數據庫連線,然後將