說到寫PHP的MVC框架,大家想到的第一個詞--“造輪子”,是的,一個還沒有深厚功力的程序員,寫出的PHP框架肯定不如那些出自大神們之手、經過時間和各種項目考驗的框架。但我還是准備並且這麼做了,主要是因為:
所以說,這次造輪子的目的不是為了造輪子而是為了在造輪子的過程中熟悉其工藝,總結輪子特點,更好的使用輪子。
如果說寫一個完整的PHP框架,那需要掌握的PHP知識點非常多,像設計模式、迭代器、事件與鉤子等等,還有許多基礎知識的靈活應用。我自認為這些還無法完全掌控,所以我的步驟是先自己搭建一個骨架,然後參考借鑒不同的PHP框架的特點,將其慢慢完善。因為工作原因,而且晚上還要補算法、網絡等編程基礎,PHP框架部分可能只有周末有時間更新,我會在進行框架功能更新之後,總結使用的知識點,更新博文。
首先放上框架的目前源碼:GITHUB/zhenbianshu
首先自己總結一下PHP的MVC框架的工作流程:
簡單來說,它以一個入口文件來接受請求,選擇路由,處理請求,返回結果。
當然,幾句話總結完的東西實際上要做的工作很多,PHP框架會在每次接受請求時,定義常量,加載配置文件、基礎類,根據訪問的URL進行邏輯判斷,選擇對應的(模塊)控制器和方法,並且自動加載對應類,處理完請求後,框架會選擇並渲染對應的模板文件,以html頁面的形式返回響應。在處理邏輯的時候,還要考慮到錯誤和異常的處理。
1、作為MVC框架,一定要有一個唯一的入口文件來統領全局,所有的訪問請求都會首先進入這個入口文件,如我框架根目錄的index.php,在裡面,我定義了基本文件夾路徑,當前環境,並根據當前環境定義錯誤報告的級別。
2、PHP中加載另外的文件,使用require和include,它們都是將目標文件內容加載到當前文件內,替換掉require或include語句,require是加載進來就執行,而include是加載進來在需要的時候執行,而它們的_once結構都是表示在寫多次的時候只執行一次。
3、框架內的配置變量等使用專用的配置文件來保存,這裡我仿照了TP裡的數組返回法,用了一個compileConf()函數來解析數組,將數組的鍵定義為常量,值為數組的值。
if (!function_exists('compile_conf')) {
function compileConf($conf) {
foreach ($conf as $key => $val) {
if(is_array($val)){
compileConf($val);
}else{
define($key, $val);
}
}
}
}
compileConf(require_once CONF_PATH.'config.php');
為什麼把命名空間和自動加載放到一塊說呢?
在一個PHP項目中,類特別多的時候,如果類名重復的話就會造成混亂,而且相同文件夾內也不能存在同名的文件,所以這時候命名空間和文件夾就搭檔出場了。文件夾就是一個一個的盒子,命名空間在我理解就像是一個標簽,盒子對應標簽。我們定義類時,把各種類用不同的盒子分別裝好,並貼上對應的標簽。而在自動加載類時,我們根據標簽(命名空間)可以很輕易找到對應的盒子(文件夾)然後找到對應的類文件。
而類的自動加載,我們知道的__autoload()魔術函數,它會在你實例化一個當前路徑找不到的對象時自動調用,根據傳入的類名,在函數體內加載對應的類文件。
現在我們多用spl_autoload_register()函數,它可以注冊多個函數來代替__autoload函數的功能,我們傳入一個函數名為參數,spl_autoload_register會將這個函數壓入棧中,在實例化一個當前路徑內找不到的類時,系統將會將函數出棧依次調用,直到實例化成功。
spl_autoload_register('Sqier\Loader::autoLoad');
class Loader {
public static function autoLoad($class) {
//如果有的話,去除類最左側的\
$class = ltrim($class, '\\');
//獲取類的路徑全名
$class_path = str_replace('\\', '/', $class) . EXT;
if (file_exists(SYS_PATH . $class_path)) {
include SYS_PATH . $class_path;
return;
}
if (file_exists(APP_PATH . $class_path)) {
include APP_PATH . $class_path;
return;
}
}
現在Loader類還是一個簡單的類,待以後慢慢完善。
接下來就是路由選擇了,其本質是根據當前定義的全局URL模式選擇合適的方法來分析傳入的URI,加載對應的類,並實現對應的方法。
class Router {
public static $uri;
public static function bootstrap() {
self::$uri = $_SERVER['REQUEST_URI'];
switch (URL_MODE) {
case 1: {
self::rBoot();
break;
}
default: {
self::rBoot();
}
}
}
public static function rBoot() {
$router = isset($_GET['r']) ? explode('/', $_GET['r']) : [
'index',
'index'
];
$cName = 'Controller\\' . ucfirst($router[0]);
$aName = isset($router[1]) ? strtolower($router[1]) . 'Action' : 'indexAction';
$controller = new $cName();
$controller->$aName();
}
}
這樣,我在地址欄輸入 zbs.com/index.php?r=index/login 後,系統會自動調用/app/Controller/Index.php下的login方法。完成了這麼一個簡單的路由。
接下來我會優化現有的工具類,添加顯示層,添加數據庫類,還會將一些別的框架裡非常cool的功能移植進來~
待續...