1. 程式人生 > >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類的實現如下:

[php] view plain copy
class MobilePhone extends ActiveRecordModel
{
protected tablename=phone;protectedusername =’root’;
protected password=root;protectedhostname = ‘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();/Tablename@varString/protectedtable_name;
/**
* Username
* @var String
*/
protected username;/password@varString/protectedpassword;
/**
* The DBMS hostname
* @var String
*/
protected hostname;/Thedatabasename@varString/protecteddbname;
/**
* 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調insertattributes作為引數傳遞給insert, 在insert方法中首先新建一個數據庫連線,然後將