iT邦幫忙

DAY 27
10

逐步提昇PHP技術能力系列 第 27

逐步提昇PHP技術能力 - 逐步改善軟體架構 - 轉換到PDO

PDO具備了幾個特性,除了是不同資料庫例如MySQL、SQLite、SQL Server等的抽象層,他提供的Prepared Statement也可以為資料庫操作增加一些安全性與執行效率。既然有這樣的好處,就把DAO實作轉換到PDO試試看。
參考:http://us1.php.net/manual/en/book.pdo.php

不過更換不同的實作,就需要在bootstrap.php中進行不同的初始化作業,既然在設定檔中已經訂好了指定不同DAO實作的參數,那利用這個參數,也可以指定在bootstrap中做不同的初始化過程。

稍微改了一下,發現如果定義好了各個實作的bootstrap方式,其實就可以把建立連線的部份從bootstrap.php中拿掉,做成一個singleton,需要取得連線的時候就呼叫。

調整過的bootstrap.php就直接刪掉資料庫連線的程式碼:

<?php
session_start();
include 'config.php';
//register autoloader
include 'vendor/autoload.php';
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
spl_autoload_extensions(".php");
spl_autoload_register();
spl_autoload_register(function($class) {
  $class = str_replace('_', '/', $class);
  $class = str_replace('\\', '/', $class);
  include $class.".php";
}, false);

然後設計一個MysqlBootstrap類別:

<?php
namespace Fillano\Core;

class MysqlBootstrap
{
	private static $conn = null;
	private function __construct()
	{

	}
	public static function getResource() {
		if(self::$conn===null) {
			self::$conn = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
			if(!self::$conn)
				throw new \Exception('mysql connection error: '.mysql_error());
			mysql_select_db(DB_NAME);
		}
		return self::$conn;
	}
}

然後在MysqlModelFactory中呼叫他:

<?php
namespace Fillano\Core;

class MysqlModelFactory
{
	public static function getInstance($name) {
		if(!defined('MODEL_IMPL')) {
			die('Model Implementation constant "MODEL_IMPL" not defined in config.php');
		}
		$class = 'Fillano\\Models\\'.MODEL_IMPL.$name;
		$bootstrap = 'Fillano\\Core\\'.MODEL_IMPL.'Bootstrap';
		if(class_exists($class) && class_exists($bootstrap)) {
			$conn = $bootstrap::getResource();
			return new $class($conn);
		} else {
			throw new \Exception("class $class or $bootstrap not found.");
		}
	}
}

修改結束。回頭看一下是否可以執行...改了幾個小錯誤就沒問題了(我搞錯self的寫法)。

接下來,就來寫用PDO實作的DAO。在這之前,跟之前Mysql的實作一樣,需要準備好MODEL_IMPL參數,就從"Mysql"改成"PDO"吧。定好名字,就需要寫幾個類別:PDOBootstrap、PDOModelFactory以及以PDO為前綴的DAO類別。

先來寫PDOBootstrap,同樣在這個類別用singleton的方式建立PDO物件:

<?php
namespace Fillano\Core;

class PDOBootstrap
{
	private static $pdo = null;
	private function __construct() {}
	public static function getPDO()
	{
		if(self::$pdo===null) {
			self::$pdo = new \PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASSWORD);
		}
		return self::$pdo;
	}
}

然後是PDOModelFactory,利用他把pdo物件傳給各個DAO:

<?php
namespace Fillano\Core;

class PDOModelFactory
{
	public static function getInstance($name) {
		if(!defined('MODEL_IMPL')) {
			die('Model Implementation constant "MODEL_IMPL" not defined in config.php');
		}
		$class = 'Fillano\\Models\\'.MODEL_IMPL.$name;
		$bootstrap = 'Fillano\\Core\\'.MODEL_IMPL.'Bootstrap';
		if(class_exists($class) && class_exists($bootstrap)) {
			return new $class($bootstrap::getPDO());
		} else {
			throw new \Exception("class $class or $bootstrap not found.");
		}
	}
}

最後來寫用PDO實作的DAO,之前用Mysql實作的叫做MysqlIndex,現在改成PDOIndex:

<?php
namespace Fillano\Models;

class PDOIndex implements IIndex
{
	private $pdo;
	public function __construct($pdo)
	{
		$this->pdo = $pdo;
	}
	public function getForumList()
	{
		$sql = "SELECT f.*,count(a.forums_id) AS count  "
			."FROM forums f "
			."LEFT JOIN articles a ON a.forums_id = f.id "
			."GROUP BY a.forums_id "
			."ORDER BY f.id"
		;
		$forums = $this->pdo->query($sql);
		$data = array();
		$sql = "SELECT * "
			."FROM articles "
			."WHERE forums_id=:forum_id "
			." ORDER BY id DESC LIMIT 1";
		$stmt = $this->pdo->prepare($sql);
		foreach($forums as $forum) {
			$article = $stmt->execute(array(":forum_id"=>$forum['id']));
			$data[] = array('id'=>$forum['id'], 'name'=>$forum['name'], 'count'=>$forum['count'], 'title'=>$article['title']);
		}
		return $data;		
	}
}

寫好以後,回去改設定檔config.php,把實作從Mysql改成PDO:

<?php
define('DB_HOST','localhost');
define('DB_USER','root');
define('DB_PASSWORD', '');
define('DB_NAME', 'myforum');
define('CLASS_DIR', 'class/');
define('TEMPLATE_ENGINE', 'TwigView');
define('MODEL_IMPL', 'PDO');

打開瀏覽器操作一下看看...嗯,跟之前一樣,應該沒有問題。

======

抽換DAO實作的過程,完全沒有碰到主程式,也沒動到View與樣板,這就是把商業邏輯從主程式分離開來的好處。另外,有了這些實作,終於可以做單元測試了,明天就來針對Index這個Model來做單元測試吧。(本來還想找一個ORM來做DAO的實作,不過我怕沒時間了,所以先寫測試,後面幾天留給Controller)

PHP已經有很多成熟的資料庫抽象層與ORM,如果要從mysql/mysqli開始,最接近的大概是PDO,接下來可以嘗試一些query builder,例如FluentPDO、PHP SqlBuilder(國產)等方案,最後還有完整的ORM,例如Doctrine、Propel等等。


上一篇
逐步提昇PHP技術能力 - 逐步改善軟體架構 - 用DAO初步分離資料邏輯
下一篇
逐步提昇PHP技術能力 - 逐步改善軟體架構 - 單元測試
系列文
逐步提昇PHP技術能力30
0
player
iT邦大師 1 級 ‧ 2013-10-27 21:00:31

PHP如果要支援PDO
要在安裝PHP時就啟用它
不然早期的PHP版本記得是預設不啟用PDO
現今的PHP未知是否預設啟用PDO

0
fillano
iT邦超人 1 級 ‧ 2013-10-28 09:16:51

PDO從PHP-5.0加入PECL,PHP-5.1內建在PHP extension(也就是原始檔的ext目錄),不過預設是否啟用,恐怕跟套件發行者有關。也就是說,這要看AppServ、WAMP、XAMPP等發行者,或是像rpm/deb等系統的套件管理者,是否有把它在編譯及發行時設為預設套件。

0
amigoccs
iT邦研究生 4 級 ‧ 2014-07-21 22:36:40

Dear all,

或者,也可以使用 RedBean 之類的 ORB,可以抽象畫資料庫,他是架構在 PDO 之上,所以還是與發行套件有關。

我目前使用的 ZurmoCRM 就以 RedBean 作為 ORB,簡化來回修改資料結構的時間。

Just my two cents.

Best regards,

Amigo

fillano iT邦超人 1 級 ‧ 2014-07-22 07:40:08 檢舉

讚

我要留言

立即登入留言