《PHP實(shí)例:搭建自己的PHP MVC框架詳解》要點(diǎn):
本文介紹了PHP實(shí)例:搭建自己的PHP MVC框架詳解,希望對您有用。如果有疑問,可以聯(lián)系我們。
PHP實(shí)例本文詳細(xì)講述了搭建自己的PHP MVC框架的方法.分享給大家供大家參考,具體如下:
PHP實(shí)例前言
PHP實(shí)例說到寫PHP的MVC框架,大家想到的第一個(gè)詞--“造輪子”,是的,一個(gè)還沒有深厚功力的程序員,寫出的PHP框架肯定不如那些出自大神們之手、經(jīng)過時(shí)間和各種項(xiàng)目考驗(yàn)的框架.但我還是準(zhǔn)備并且這么做了,主要是因?yàn)椋?/p>
PHP實(shí)例認(rèn)為有關(guān)PHP的方方面面都了解了,但自己學(xué)習(xí)PHP的時(shí)間還短,基礎(chǔ)并不扎實(shí),很多常用函數(shù)的參數(shù)還偶爾要查手冊,而且對于PHP的一些較新的特性如命名空間、反射等只是簡單的看過,并沒有能實(shí)際應(yīng)用過.
PHP實(shí)例PHP的知識多且雜,一個(gè)普通的項(xiàng)目往住是業(yè)務(wù)邏輯代碼為主,而框架是一個(gè)能把這些知識點(diǎn)能融匯在一起的項(xiàng)目.
PHP實(shí)例在自己寫一個(gè)框架的時(shí)候,也會參考一些我使用過的框架如TP/CI/YII等的源碼,在自己看源碼時(shí)也能幫助自己理解框架,更容易接受以后要使用的框架.
PHP實(shí)例所以說,這次造輪子的目的不是為了造輪子而是為了在造輪子的過程中熟悉其工藝,總結(jié)輪子特點(diǎn),更好的使用輪子.
PHP實(shí)例如果說寫一個(gè)完整的PHP框架,那需要掌握的PHP知識點(diǎn)非常多,像設(shè)計(jì)模式、迭代器、事件與鉤子等等,還有許多基礎(chǔ)知識的靈活應(yīng)用.我自認(rèn)為這些還無法完全掌控,所以我的步驟是先自己搭建一個(gè)骨架,然后參考借鑒不同的PHP框架的特點(diǎn),將其慢慢完善.因?yàn)楣ぷ髟?而且晚上還要補(bǔ)算法、網(wǎng)絡(luò)等編程基礎(chǔ),PHP框架部分可能只有周末有時(shí)間更新,我會在進(jìn)行框架功能更新之后,總結(jié)使用的知識點(diǎn),更新博文.
PHP實(shí)例首先放上框架的目前源碼:GITHUB/zhenbianshu
PHP實(shí)例或者點(diǎn)擊此處本站下載.
PHP實(shí)例框架整體
PHP實(shí)例首先自己總結(jié)一下PHP的MVC框架的工作流程:
PHP實(shí)例簡單來說,它以一個(gè)入口文件來接受請求,選擇路由,處理請求,返回結(jié)果.
PHP實(shí)例當(dāng)然,幾句話總結(jié)完的東西實(shí)際上要做的工作很多,PHP框架會在每次接受請求時(shí),定義常量,加載配置文件、基礎(chǔ)類,根據(jù)訪問的URL進(jìn)行邏輯判斷,選擇對應(yīng)的(模塊)控制器和方法,并且自動(dòng)加載對應(yīng)類,處理完請求后,框架會選擇并渲染對應(yīng)的模板文件,以html頁面的形式返回響應(yīng).在處理邏輯的時(shí)候,還要考慮到錯(cuò)誤和異常的處理.
PHP實(shí)例1、作為MVC框架,一定要有一個(gè)唯一的入口文件來統(tǒng)領(lǐng)全局,所有的訪問請求都會首先進(jìn)入這個(gè)入口文件,如我框架根目錄的index.php,在里面,我定義了基本文件夾路徑,當(dāng)前環(huán)境,并根據(jù)當(dāng)前環(huán)境定義錯(cuò)誤報(bào)告的級別.
PHP實(shí)例2、PHP中加載另外的文件,使用require和include,它們都是將目標(biāo)文件內(nèi)容加載到當(dāng)前文件內(nèi),替換掉require或include語句,require是加載進(jìn)來就執(zhí)行,而include是加載進(jìn)來在需要的時(shí)候執(zhí)行,而它們的_once結(jié)構(gòu)都是表示在寫多次的時(shí)候只執(zhí)行一次.
PHP實(shí)例3、框架內(nèi)的配置變量等使用專用的配置文件來保存,這里我仿照了TP里的數(shù)組返回法,用了一個(gè)compileConf()
函數(shù)來解析數(shù)組,將數(shù)組的鍵定義為常量,值為數(shù)組的值.
PHP實(shí)例 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實(shí)例命名空間和自動(dòng)加載
PHP實(shí)例為什么把命名空間和自動(dòng)加載放到一塊說呢?
PHP實(shí)例在一個(gè)PHP項(xiàng)目中,類特別多的時(shí)候,如果類名重復(fù)的話就會造成混亂,而且相同文件夾內(nèi)也不能存在同名的文件,所以這時(shí)候命名空間和文件夾就搭檔出場了.文件夾就是一個(gè)一個(gè)的盒子,命名空間在我理解就像是一個(gè)標(biāo)簽,盒子對應(yīng)標(biāo)簽.我們定義類時(shí),把各種類用不同的盒子分別裝好,并貼上對應(yīng)的標(biāo)簽.而在自動(dòng)加載類時(shí),我們根據(jù)標(biāo)簽(命名空間)可以很輕易找到對應(yīng)的盒子(文件夾)然后找到對應(yīng)的類文件.
PHP實(shí)例而類的自動(dòng)加載,我們知道的__autoload()魔術(shù)函數(shù),它會在你實(shí)例化一個(gè)當(dāng)前路徑找不到的對象時(shí)自動(dòng)調(diào)用,根據(jù)傳入的類名,在函數(shù)體內(nèi)加載對應(yīng)的類文件.
PHP實(shí)例現(xiàn)在我們多用spl_autoload_register()函數(shù),它可以注冊多個(gè)函數(shù)來代替__autoload函數(shù)的功能,我們傳入一個(gè)函數(shù)名為參數(shù),spl_autoload_register會將這個(gè)函數(shù)壓入棧中,在實(shí)例化一個(gè)當(dāng)前路徑內(nèi)找不到的類時(shí),系統(tǒng)將會將函數(shù)出棧依次調(diào)用,直到實(shí)例化成功.
PHP實(shí)例 spl_autoload_register('Sqier\Loader::autoLoad'); class Loader { public static function autoLoad($class) { //如果有的話,去除類最左側(cè)的\ $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; } }
PHP實(shí)例現(xiàn)在Loader類還是一個(gè)簡單的類,待以后慢慢完善.
PHP實(shí)例路由選擇
PHP實(shí)例接下來就是路由選擇了,其本質(zhì)是根據(jù)當(dāng)前定義的全局URL模式選擇合適的方法來分析傳入的URI,加載對應(yīng)的類,并實(shí)現(xiàn)對應(yīng)的方法.
PHP實(shí)例 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(); } }
PHP實(shí)例這樣,我在地址欄輸入 zbs.com/index.php?r=index/login 后,系統(tǒng)會自動(dòng)調(diào)用/app/Controller/Index.php下的login方法.完成了這么一個(gè)簡單的路由.
PHP實(shí)例階段總結(jié):
PHP實(shí)例接下來我會優(yōu)化現(xiàn)有的工具類,添加顯示層,添加數(shù)據(jù)庫類,還會將一些別的框架里非常cool的功能移植進(jìn)來~
PHP實(shí)例接上文(代碼有所更新),繼續(xù)完善框架(二):
PHP實(shí)例對于本次更新,我想說:
PHP實(shí)例① 本框架由本人挑時(shí)間完善,而我還不是PHP大神級的人物,所以框架漏洞難免,求大神們指出.
② 本框架的知識點(diǎn)應(yīng)用都會寫在博客里,大家有什么異議的可以一起討論,也希望看博客的也能學(xué)習(xí)到它們.
③ 本次更新,更新了函數(shù)規(guī)范上的一些問題,如將函數(shù)盡量的獨(dú)立化,每一個(gè)函數(shù)盡量只單獨(dú)做好一件事情,盡量減少函數(shù)依賴.還對框架的整體優(yōu)化了一下,添加了SQ全局類,用以處理全局函數(shù),變量.
PHP實(shí)例回調(diào)函數(shù)
PHP實(shí)例替換了很low的類名拼裝實(shí)例化,然后拼裝方法名的用法,使用PHP的回調(diào)函數(shù)方式:
PHP實(shí)例原代碼:
PHP實(shí)例 $controller_name = 'Controller\\' . self::$c_name; $action_name = self::$a_name . 'Action'; $controller = new $controller_name(); $controller->$action_name();
PHP實(shí)例修改后代碼
PHP實(shí)例 $controller_name = 'Controller\\' . self::$c_name; $controller = new $controller_name(); call_user_func([ $controller, self::$a_name . 'Action' ]);
PHP實(shí)例這里介紹一下PHP的函數(shù)回調(diào)應(yīng)用方式:call_user_func和call_user_func_array:
PHP實(shí)例 call_user_func ( callback $function [, mixed $parameter [, mixed $... ]] )
PHP實(shí)例調(diào)用第一個(gè)參數(shù)所提供的用戶自定義的函數(shù).
PHP實(shí)例返回值:返回調(diào)用函數(shù)的結(jié)果,或FALSE.
PHP實(shí)例call_user_func_array()
的用法跟call_user_func類似,只不過傳入的參數(shù)params整體為一個(gè)數(shù)組.
PHP實(shí)例另外,call_user_func系列函數(shù)還可以傳入在第一個(gè)參數(shù)里傳入匿名參數(shù),可以很方便的回調(diào)某些事件,這些特性在復(fù)雜的框架里應(yīng)用也十分廣泛,如yii2的事件機(jī)制里回調(diào)函數(shù)的使用就是基于此.
PHP實(shí)例VIEW層和ob函數(shù)
PHP實(shí)例框架在controller的基類中定義了render方法來渲染頁面,它會調(diào)用類VIEW的靜態(tài)函數(shù)來分析加載對應(yīng)頁面的模板.
PHP實(shí)例 public static function display($data, $view_file) { if(is_array($data)) { extract($data);//extract函數(shù)解析$data數(shù)組中的變量 }else { //拋出變量類型異常 } ob_start(); ob_implicit_flush(0); include self::checkTemplate($view_file);//自定義checkTemplate函數(shù),分析檢查對應(yīng)的函數(shù)模板,正常返回路徑 $content = ob_get_clean(); echo $content; }
PHP實(shí)例這里重點(diǎn)說一下ob(output buffering)系列函數(shù),其作用引用簡明代魔法的ob作用介紹:
PHP實(shí)例① 防止在瀏覽器有輸出之后再使用setcookie,或者h(yuǎn)eader,session_start函數(shù)造成的錯(cuò)誤.其實(shí)這樣的用法少用為好,養(yǎng)成良好的代碼習(xí)慣.
② 捕捉對一些不可獲取的函數(shù)的輸出,比如phpinfo會輸出一大堆的HTML,但是我們無法用一個(gè)變量例如$info=phpinfo();來捕捉,這時(shí)候ob就管用了.
③ 對輸出的內(nèi)容進(jìn)行處理,例如進(jìn)行g(shù)zip壓縮,例如進(jìn)行簡繁轉(zhuǎn)換,例如進(jìn)行一些字符串替換.
④ 生成靜態(tài)文件,其實(shí)就是捕捉整頁的輸出,然后存成文件,經(jīng)常在生成HTML,或者整頁緩存中使用.
PHP實(shí)例它在ob_start()函數(shù)執(zhí)行后,打開緩沖區(qū),將后面的輸出內(nèi)容裝進(jìn)系統(tǒng)的緩沖區(qū),ob_implicit_flush(0)函數(shù)來關(guān)閉絕對刷送(echo等),最后使用ob_get_clean()函數(shù)將緩沖區(qū)的內(nèi)容取出來.
PHP實(shí)例類__URL__常量和全局類
PHP實(shí)例TP里的__URL__等全局常量用著很方便,可以很簡單的實(shí)現(xiàn)跳轉(zhuǎn)等操作,而定義它的函數(shù)createUrl函數(shù)我又想重用,于是借鑒YII的全局類定義方法:
PHP實(shí)例定義基類及詳細(xì)方法(以后的全局方法會寫在這里)
PHP實(shí)例 class BaseSqier{ //方法根據(jù)傳入的$info信息,和當(dāng)前URL_MODE解析返回URL字符串 public static function createUrl($info = '') { $url_info = explode('/', strtolower($info)); $controller = isset($url_info[1]) ? $url_info[0] : strtolower(CONTROLLER); $action = isset($url_info[1]) ? $url_info[1] : $url_info[0]; switch(URL_MODE){ case URL_COMMON: return "/index.php?r=" . $controller . '/' . $action; case URL_REWRITE: return '/' .$controller . '/' . $action; } } }
PHP實(shí)例在啟動(dòng)文件中定義類并繼承基類;
PHP實(shí)例 require_once SQ_PATH.'BaseSqier.php'; class SQ extends BaseSqier{ }
PHP實(shí)例在全局內(nèi)都可以直接使用SQ::createUrl()
方法來創(chuàng)建URL了.這樣,定義__URL__常量就很輕松了.
PHP實(shí)例用單例模式定義數(shù)據(jù)庫連接基類
PHP實(shí)例 class Db { protected static $_instance; public static function getInstance() { if(!(self::$_instance instanceof self)) { self::$_instance = new self(); } return self::$_instance; } private function __construct() { $link = new \mysqli(DB_HOST, DB_USER, DB_PWD, DB_NAME) or die("連接數(shù)據(jù)庫失敗,請檢查數(shù)據(jù)庫配置信息!"); $link->query('set names utf8'); } public function __clone() { return self::getInstance(); } }
PHP實(shí)例使用單例模式的核心是:
PHP實(shí)例① 私有化構(gòu)造函數(shù),使無法用new來創(chuàng)建對象,也防止子類繼承它并改寫其構(gòu)造函數(shù);
② 用靜態(tài)變量存放當(dāng)前對象,定義靜態(tài)方法來返回對象,如對象還未實(shí)例化,實(shí)例化一個(gè),存入靜態(tài)變量并返回.
③ 構(gòu)造其__clone魔術(shù)方法,防止clone出一個(gè)新的對象;
PHP實(shí)例DB類的sql查詢函數(shù)
PHP實(shí)例DB查詢函數(shù)是一個(gè)很復(fù)雜的部分,它是一個(gè)自成體系的東西,像TP和YII的查詢方法都有其獨(dú)特的地方.我這里暫時(shí)先借用TP的MODEL基類,有時(shí)間再慢慢補(bǔ)這個(gè).
PHP實(shí)例嗯,介紹一下像TP的查詢里的方法聯(lián)查的實(shí)現(xiàn),其訣竅在于,在每個(gè)聯(lián)查方法的最后都用 return this 來返回已處理過的查詢對象.
PHP實(shí)例階段總結(jié):
PHP實(shí)例yii2里的數(shù)據(jù)表和model類屬性之間的映射很酷(雖然被深坑過), 前面一直避開的模塊(module,我可以想像得到把它也添加到URI時(shí)解析的麻煩)有時(shí)間考慮一下.
PHP實(shí)例接上文,繼續(xù)完善框架(三)
PHP實(shí)例本次更新的主要內(nèi)容有:
PHP實(shí)例① 介紹了異常處理機(jī)制
② 完善了異常和錯(cuò)誤處理
③ 數(shù)據(jù)表跟Model類的映射
PHP實(shí)例異常處理
PHP實(shí)例異常處理:異常處理是編程語言或計(jì)算機(jī)硬件里的一種機(jī)制,用于處理軟件或信息系統(tǒng)中出現(xiàn)的異常狀況(即超出程序正常執(zhí)行流程的某些特殊條件)
PHP實(shí)例異常處理用于處理程序中的異常狀況,雖說是“異常狀態(tài)”,但仍然還是在程序編寫人員的預(yù)料之中,其實(shí)程序的異常處理完全可以用‘if else'語句來代替,但異常處理自然有其優(yōu)勢之處.
PHP實(shí)例個(gè)人總結(jié)其優(yōu)點(diǎn)如下:
PHP實(shí)例① 可以快速終止流程,重置系統(tǒng)狀態(tài),清理變量和內(nèi)存占用,在普通WEB應(yīng)用中,一次請求結(jié)束后,FAST CGI會自動(dòng)清理變量和上下文,但如果在PHP的命令行模式執(zhí)行守護(hù)腳本時(shí),它的效果就會很方便了.
PHP實(shí)例② 大量的if else語句會使代碼變得繁雜難懂,使用異常處理可以使程序邏輯更清晰易懂,畢竟處理異常的入口只有catch語句一處.
PHP實(shí)例③ 一量程序中的函數(shù)出現(xiàn)異常結(jié)果或狀況,如果使用函數(shù)的return方式返回異常信息,層層向上,每一次都要進(jìn)行return判斷.使用異常處理我們可以假設(shè)所有的返回信息都是正常的,避免了大量的代碼重復(fù).
PHP實(shí)例雖然將代碼放在try catch塊中會有微微的效率差,但是跟這些優(yōu)點(diǎn)一比,這點(diǎn)消耗就不算什么了.那么PHP的異常處理怎么使用呢?
PHP實(shí)例PHP內(nèi)置有Exception類,使得我們可以通過實(shí)例化異常類來拋出異常.我們將代碼放在try語句中執(zhí)行,并在其后用catch試圖捕捉到在try代碼塊中拋出的異常,并對異常進(jìn)行處理.我們還可以在catch代碼段后使用finally語句塊,無論是否有異常都會執(zhí)行finally代碼塊的代碼,try catch語句形如下面代碼:
PHP實(shí)例 try{ throw new Exeption('msg'[,'code',$previous_exeception]); }catch(Exeption $var) { process($var); }catch(MyException $e){ process($e) }finally{ dosomething(); }
PHP實(shí)例使用try catch語句,需要注意:
PHP實(shí)例① 當(dāng)我們拋出異常時(shí),會實(shí)例化一個(gè)異常類,此異常類可以自己定義,但在catch語句中,我們需要規(guī)定要捕獲的異常對象的類名,并且只能捕獲到特定類的異常對象,當(dāng)然我們可以在最后捕獲一個(gè)異?;?PHP內(nèi)置異常類)來確保異常一定能被捕獲.
PHP實(shí)例② 在拋出異常時(shí),程序會被終止,并回溯代碼找到第一個(gè)能捕獲到它的catch語句,try catch語句是可以嵌套的,并且如上面代碼所示 cacth語句是可以多次定義的.
PHP實(shí)例③ finally塊會在try catch塊結(jié)束后執(zhí)行,即使在try catch塊中使用return返回,程序沒有執(zhí)行到最后.
PHP實(shí)例框架里的異常處理
PHP實(shí)例說了那么多異常相關(guān)(當(dāng)然解釋這些也是為了能理解和使用框架),那么框架里要怎么實(shí)現(xiàn)呢?
PHP實(shí)例重寫異常類
PHP實(shí)例我們可以重寫異常類,完善其內(nèi)部方法:
PHP實(shí)例 <?php class Exception { protected $message = 'Unknown exception'; // 異常信息 protected $code = 0; // 異常代碼 protected $file; // 發(fā)生異常的文件名 protected $line; // 發(fā)生異常的代碼行號 function __construct($message = null, $code = null,$previous_exeception = null); final function getMessage(); // 返回異常信息 final function getCode(); // 返回異常代碼 final function getFile(); // 返回發(fā)生異常的文件名 final function getLine(); // 返回發(fā)生異常的代碼行號 final function getTrace(); // 返回異常trace數(shù)組 final function getTraceAsString(); // 返回異常trace信息 /** * 記錄錯(cuò)誤日志 */ protected function log(){ Logger::debug(); } }
PHP實(shí)例如上,final方法是不可以重寫的,除此之外,我們可以定義自己的方法,如記錄異常日志,像我自定義的log方法,在catch代碼塊中,就可以直接使用$e->log來記錄一個(gè)異常日志了.
PHP實(shí)例注冊全局異常方法
PHP實(shí)例我們可以使用set_exception_handler('exceptionHandler')來全局捕獲沒有被catch塊捕獲到的異常,此異常處理函數(shù)需要傳入一個(gè)異常處理對象,這樣可以分析此異常處理信息,避免系統(tǒng)出現(xiàn)不人性化的提示,增強(qiáng)框架的健壯性.
PHP實(shí)例 function exceptionHandler($e) { echo '有未被捕獲的異常,在' . $e->getFile() . "的" . $e->getLine() . "行!"; }
PHP實(shí)例其他全局函數(shù)
PHP實(shí)例順便再說一下其他的全局處理函數(shù):
PHP實(shí)例① set_shutdown_function('shutDownHandler')
來執(zhí)行腳本結(jié)束時(shí)的函數(shù),此函數(shù)即使是在ERROR結(jié)束后,也會自動(dòng)調(diào)用.
PHP實(shí)例② set_error_handler('errorHandler')
在PHP發(fā)生錯(cuò)誤時(shí)自動(dòng)調(diào)用,注意,必須在已注冊錯(cuò)誤函數(shù)后才發(fā)出的錯(cuò)誤才會調(diào)用.函數(shù)參數(shù)形式應(yīng)為($errno, $errstr, $errfile, $errline);
PHP實(shí)例但是要注意這些全局函數(shù)需要在代碼段的前面已經(jīng)定義過再注冊.
PHP實(shí)例數(shù)據(jù)表和Model類的ActiveRecord映射
PHP實(shí)例初次使用yii2的ActivceRecord類覺得好方便,只需要定義其字段同名屬性再調(diào)用save方法就OK了(好神奇啊),它是怎么實(shí)現(xiàn)的呢,看了下源碼,明白了其大致實(shí)現(xiàn)過程(基類).
PHP實(shí)例1. 使用‘describe table_name' 查詢語句;
2. 分析查詢結(jié)果:對每一個(gè)字段,有Field(字段名)、Type(數(shù)據(jù)類型)、Null(是否為空)、Key(索引信息,‘PRI'表示為主鍵)、Default(默認(rèn)值)、Extra(附加信息,如auto_increment)
3. 通過判斷其主鍵($row['KEY'] == 'PRI')信息,保存時(shí)看是否有主鍵信息,若存在,則為更新;不存在,則插入.
4. 另外,解析出來的字段信息還有更多妙用~~
PHP實(shí)例更多關(guān)于php框架相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php優(yōu)秀開發(fā)框架總結(jié)》、《codeigniter入門教程》、《ThinkPHP入門教程》、《Zend FrameWork框架入門教程》、《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門教程》、《php+mysql數(shù)據(jù)庫操作入門教程》及《php常見數(shù)據(jù)庫操作技巧匯總》
PHP實(shí)例希望本文所述對大家PHP程序設(shè)計(jì)有所幫助.
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/299.html