資料庫
你的PHP程式碼常常需要使用資料庫來保存資訊,這時,你有一些選擇來連接並與你的資料庫互動,在PHP 5.1.0之前的推薦方案是使用一些原生的驅動程式,例如mysqli, pgsql, mssql等等。
如果你在應用程式中只使用 一個 資料庫,原生的驅動程式(native driver)是一個很好的選擇。但是,假如你使用了MySQL和一點點的MSSQL,或是你需要連接到Oracle資料庫,那麼你就不能只使用同一個驅動程式,你將需要針對每個不同的資料庫學習API — 而這些行為非常的愚蠢。
MySQL 擴充套件
PHP的mysql擴充套件已經不再主動的開發,官方也在PHP 5.5.0時不贊成使用,意思是mysql擴充套件將在接下來的幾個釋出版本移除。如果你正在使用類似mysql_connect()
和 mysql_query()
這些mysql_*
開頭的函式,這些函式將在未來的幾個版本不能使用了。這代表你將需要重寫程式,所以最好的選擇就是開始使用 mysqli 或是 PDO。
如果你正要開始寫程式,那麼絕對不要使用 mysql 擴充套件: 請轉而使用MySQLi extension或是使用PDO。
PDO 擴充套件
PDO 是從 — PHP 5.1.0 — 可以開始使用的一個和資料庫連結的抽象程式庫,它提供了一個共同的介面來和不同的資料庫溝通。例如,你可以使用同一套程式碼和MySQL或是SQLite溝通。
// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some\_field FROM some\_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);
// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some\_field FROM some\_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);
PDO不會轉譯你的SQL請求,或是模擬失去的特性。它只是簡單的使用同一個API去連向各個不同類型的資料庫。最重要的是, PDO
允許你簡單的直接將外部的輸入插進你的SQL請求,而不需要擔心SQL注入攻擊(SQL injection attacks)。讓我們假設一個PHP腳本接收一個數字ID當作請求參數。這個ID是用來從資料庫取得使用者的資料。這是錯誤
的使用方式。
<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- 不!
這是一個很糟糕的程式碼,你正在插入一個未處理過的請求參數到資料庫請求中。這會導致你在一瞬間被駭入。用一個實際的案例來解釋這個情況:資料庫注入攻擊。想像一下有一個駭客使用一個網址類似這樣http://domain.com/?id=1%3BDELETE+FROM+users
。這將會造成 $_GET['id']
變數的值變成 1;DELETE FROM users
,這會刪除所有你的使用者!因此你應該要使用PDO bound parameters對這個ID輸入消毒。
<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$stmt->bindParam(':id', $_GET['id'], PDO::PARAM_INT); // <-- PDO自動消毒這個變數
$stmt->execute();
這個是正確的程式碼,它在PDO敘述上使用了bound parameter。這些動作在被放入資料庫請求之前逃脫了外部的ID輸入以避免潛藏的資料庫注入攻擊。
你應該要注意到資料庫連線沒有自行關閉而耗盡資源的問題,這個問題不是沒有前例的。當使用PDO的時候,你可以藉由摧毀物件來自行結束連線,以確保所有關聯到這個物件的東西都被刪除。例如,將它的值設為NULL。如果你沒有明確的將它關閉,那麼PHP會在腳本結束時自動的關閉連線,除非你使用的是持續的連線。
與資料庫溝通
當開發者們開始學習PHP,他們常常將資料庫溝通的動作與他們的表達邏輯混合,而使得程式碼長得像這樣:
<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
</ul>
這是一個很糟的例子,主要是因為它很難除蟲,很難測試,很難閱讀,而且沒有限制的話,它將會輸出過多資料。
而有非常多的方法可以達到同樣的目的,一切都看你喜歡哪種,物件導向 或是 函數編程 - 但是一定要有基本的分離。
考慮以下最基本的步驟:
<?php
function getAllFoos($db) {
return $db->query('SELECT * FROM table');
}
foreach (getAllFoos($db) as $row) {
echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // 不好!!
}
這是一個好的開始,將這兩個程式碼放在不同的兩個檔案,你將會得到一些乾淨的分離。
創造一個類別來放置這個方法,你將會得到一個 "Model",再創造一個簡單的 .php
檔案來放入表達的邏輯,你將會得到一個 "View",這非常接近 MVC - 一個對多數的 frameworks 非常普遍的物件導向架構。
foo.php
<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');
// 使你的model可以被使用
include 'models/FooModel.php';
// 創造一個實例
$fooList = new FooModel($db);
// 展示你的view
include 'views/foo-list.php';
models/FooModel.php
<?php
class Foo()
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function getAllFoos() {
return $this->db->query('SELECT * FROM table');
}
}
views/foo-list.php
<? foreach ($fooList as $row): ?>
<?= $row['field1'] ?> - <?= $row['field1'] ?>
<? endforeach ?>
這是對許多現代的frameworks非常基本的架構,你不需要每次都做這些,但是將表達邏輯和資料庫溝通的部分混合,在想要做單元測試的情況下,這是一個非常真實的問題
PHPBridge 有個叫 Creating a Data Class 的資源來說明非常相似的主題,而且對剛開始接觸這些概念的程式開發者而言,這是一個不錯的開始。
抽象層
許多的framework提供他們自己的抽象層,有些會建立在PDO的上層,這些抽象層常常會藉由將你的資料庫請求包裝起來,以模擬一些非你正在使用的資料庫所擁有的特色,提供你一個真正的資料庫抽象,而不是僅僅是PDO所提供的連線抽象。這些抽象層當然地會造成一些小小的效能負擔,但是你如果你正在建立一個應用程式需要與MySQL, PostgreSQL和SQLite的時候,這些代價來交換程式碼的乾淨是值得的。