記錄 PHP 學習/重構的第一天,核心任務:搞定字元編碼、踩過模板引擎坑、嘗試主流框架 DB 類、最終選擇輕量級開源 MySQL 類並深度解析其原始碼。這不只是流水帳,更是一份「從造輪子到懂輪子」的進階筆記。
第一行有效代碼,永遠是設定編碼:
header('Content-Type: text/html; charset=utf-8');
看似簡單,卻能省去 80% 的「為什麼中文變問號」靈異事件。建議直接寫在統一入口檔案或框架 Bootstrap 階段。
嘗試在自定類別中實例化 Smarty,報錯不斷。常見坑點:
教訓: 框架整合的模板引擎(如 Laravel Blade、ThinkPHP 模板)已處理好上下文環境;若手動整合,請先跑通官方 Demo 再封裝。
想單獨抽離框架的資料庫操作類直接用,發現高度耦合:
| 框架 | 核心依賴 | 剝離難度 | 結論 |
|---|---|---|---|
| BroPHP | Config 類、Log 類、Exception 體系、路由常量 | ⭐⭐⭐⭐⭐ | 幾乎等於移植半個框架 |
| ThinkPHP (早期版本) | Think 核心類、Hook 機制、快取驅動 | ⭐⭐⭐⭐ | 同樣難以獨立運行 |
框架的 DB 類設計初衷是「在框架生態內好用」,而非「隨處可攜」。這讓我意識到:**輕量級、零依賴的資料庫抽象層**對於中小專案或微服務更具價值。
GitHub 上找到一個星標不錯的 MySQL 類(作者:微涼),支援鏈式操作、事務、預處理雛形。以下是核心架構解讀:
class MySQL {
// 配置屬性
private $db_mysql_hostname, $db_mysql_username, $db_mysql_password;
private $db_mysql_database, $db_mysql_port, $db_mysql_charset;
// 查詢構建器暫存
private $query_list = [];
// 執行狀態統計
public $query_count = 0;
public $query_start_time;
// 連線資源
protected $queryID;
protected $conn;
// 事務控制
protected $transTimes = 0;
protected $numRows = 0;
protected $error = '';
}
public function __construct($hostname_or_conf, $username, $password, $database, $port = '3306', $char = 'utf8') {
if (is_array($hostname_or_conf)) {
// 支援配置陣列注入:['hostname'=>'', 'username'=>'', ...]
$this->db_mysql_hostname = $hostname_or_conf['hostname'];
// ... 其他賦值
} elseif (!empty($hostname_or_conf) || !empty($username) || !empty($password) || !empty($database)) {
// 支援傳統標量參數
$this->db_mysql_hostname = $hostname_or_conf;
// ... 其他賦值
} else {
die('configuration error.');
}
$this->connect();
}
這種設計兼容了「配置檔陣列」與「硬編碼參數」兩種常見場景,極大提升了類別的複用性。
public function table($table) { $this->query_list['table'] = $table; return $this; }
public function where($where) { $this->query_list['where'] = $where; return $this; }
public function order($order) { $this->query_list['order'] = $order; return $this; }
public function limit($offset, $length = null) {
if (!isset($length)) { $length = $offset; $offset = 0; }
$this->query_list['limit'] = "limit $offset,$length";
return $this;
}
public function data($data) { $this->query_list['data'] = $data; return $this; }
每個設定方法回傳 `$this`,實現流暢介面:
$db->table('users')->where('status=1')->order('id desc')->limit(10)->select();
select()、add()、update()、delete() 負責根據 `$this->query_list` 組裝 SQL 字串,最終委派給 query()(查詢)或 execute()(增刪改)執行。
public function select() {
$select_sql = 'select ';
$fields = isset($this->query_list['fields']) ? $this->query_list['fields'] : '*';
$select_sql .= $fields . ' from `' . $this->query_list['table'] . '` ';
// 拼接 join, where, group, having, order, limit ...
return $this->query($select_sql);
}
public function startTrans() {
if ($this->transTimes == 0) mysql_query('START TRANSACTION', $this->conn);
$this->transTimes++;
}
public function commit() {
if ($this->transTimes > 0) {
$result = mysql_query('COMMIT', $this->conn);
$this->transTimes = 0;
if (!$result) throw new Exception($this->error());
}
return true;
}
public function rollback() {
if ($this->transTimes > 0) {
$result = mysql_query('ROLLBACK', $this->conn);
$this->transTimes = 0;
if (!$result) throw new Exception($this->error());
}
return true;
}
透過計數器 `transTimes` 支援巢狀事務邏輯(雖然 MySQL 本身不支援巢狀事務,但這樣寫能避免重複 COMMIT/ROLLBACK 報錯)。
原始碼大量使用 `mysql_connect`、`mysql_query` 等,**PHP 7.0+ 已完全移除**。實際專案中**必須遷移至 PDO 或 MySQLi**。這也是為什麼「造輪子」不如「用成熟輪子」(如 `illuminate/database`、Doctrine DBAL、Medoo)的原因。
// 1. 實例化(支援陣列配置)
$config = [
'hostname' => '127.0.0.1',
'username' => 'root',
'password' => '',
'database' => 'myly',
'charset' => 'utf8'
];
$db = new MySQL($config);
// 2. 鏈式查詢
$list = $db->table('data')->select();
// 3. 新增資料
$db->table('data')->data(['name' => 'Test', 'value' => 123])->add();
// 4. 事務範例
$db->startTrans();
try {
$db->table('account')->data(['money' => -100])->where('id=1')->update();
$db->table('account')->data(['money' => +100])->where('id=2')->update();
$db->commit();
} catch (Exception $e) {
$db->rollback();
echo '轉帳失敗:' . $e->getMessage();
}
順手記錄一個滑鼠懸停列項變色的片段,純前端無 PHP 相關,但全端工程師常需處理:
$(document).ready(function() {
$('.content').each(function(index) {
$(this).hover(
function() { // mouseenter
$('.user').eq(index).css('background', '#F8FBFD');
$('.text').eq(index).css('background', '#FFFDF6');
},
function() { // mouseleave
$('.user').eq(index).css('background', '#E6F0F9');
$('.text').eq(index).css('background', '#F8FBFD');
}
);
});
});
現代寫法建議改用 CSS `:hover` 偽類或 CSS 變數,將樣式邏輯從 JS 剝離,效能更好且無閃爍。
核心感悟: 「拿來即用」的代碼往往隱藏技術債。讀懂原始碼、評估依賴、判斷維護成本,才是資深工程師的必修課。