中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何搭建自己的PHP MVC框架

發布時間:2021-02-18 10:05:07 來源:億速云 閱讀:138 作者:小新 欄目:開發技術

這篇文章給大家分享的是有關如何搭建自己的PHP MVC框架的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

前言

說到寫PHP的MVC框架,大家想到的第一個詞--“造輪子”,是的,一個還沒有深厚功力的程序員,寫出的PHP框架肯定不如那些出自大神們之手、經過時間和各種項目考驗的框架。但我還是準備并且這么做了,主要是因為:

認為有關PHP的方方面面都了解了,但自己學習PHP的時間還短,基礎并不扎實,很多常用函數的參數還偶爾要查手冊,而且對于PHP的一些較新的特性如命名空間、反射等只是簡單的看過,并沒有能實際應用過。

PHP的知識多且雜,一個普通的項目往住是業務邏輯代碼為主,而框架是一個能把這些知識點能融匯在一起的項目。

在自己寫一個框架的時候,也會參考一些我使用過的框架如TP/CI/YII等的源碼,在自己看源碼時也能幫助自己理解框架,更容易接受以后要使用的框架。

所以說,這次造輪子的目的不是為了造輪子而是為了在造輪子的過程中熟悉其工藝,總結輪子特點,更好的使用輪子。

如果說寫一個完整的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的功能移植進來~

接上文(代碼有所更新),繼續完善框架(二):

對于本次更新,我想說:

① 本框架由本人挑時間完善,而我還不是PHP大神級的人物,所以框架漏洞難免,求大神們指出。
② 本框架的知識點應用都會寫在博客里,大家有什么異議的可以一起討論,也希望看博客的也能學習到它們。
③ 本次更新,更新了函數規范上的一些問題,如將函數盡量的獨立化,每一個函數盡量只單獨做好一件事情,盡量減少函數依賴。還對框架的整體優化了一下,添加了SQ全局類,用以處理全局函數,變量。

回調函數

替換了很low的類名拼裝實例化,然后拼裝方法名的用法,使用PHP的回調函數方式:

原代碼:

$controller_name = 'Controller\\' . self::$c_name;
$action_name = self::$a_name . 'Action';
$controller = new $controller_name();
$controller->$action_name();

修改后代碼

$controller_name = 'Controller\\' . self::$c_name;
$controller = new $controller_name();
call_user_func([
  $controller,
  self::$a_name . 'Action'
]);

這里介紹一下PHP的函數回調應用方式:call_user_func和call_user_func_array:

call_user_func ( callback $function [, mixed $parameter [, mixed $... ]] )

調用第一個參數所提供的用戶自定義的函數。

返回值:返回調用函數的結果,或FALSE。

call_user_func_array()的用法跟call_user_func類似,只不過傳入的參數params整體為一個數組。

另外,call_user_func系列函數還可以傳入在第一個參數里傳入匿名參數,可以很方便的回調某些事件,這些特性在復雜的框架里應用也十分廣泛,如yii2的事件機制里回調函數的使用就是基于此。

VIEW層和ob函數

框架在controller的基類中定義了render方法來渲染頁面,它會調用類VIEW的靜態函數來分析加載對應頁面的模板。

public static function display($data, $view_file) {
  if(is_array($data)) {
    extract($data);//extract函數解析$data數組中的變量
  }else {
    //拋出變量類型異常
  }
  ob_start();
  ob_implicit_flush(0);
  include self::checkTemplate($view_file);//自定義checkTemplate函數,分析檢查對應的函數模板,正常返回路徑
  $content = ob_get_clean();
  echo $content;
}

這里重點說一下ob(output buffering)系列函數,其作用引用簡明代魔法的ob作用介紹:

① 防止在瀏覽器有輸出之后再使用setcookie,或者header,session_start函數造成的錯誤。其實這樣的用法少用為好,養成良好的代碼習慣。
② 捕捉對一些不可獲取的函數的輸出,比如phpinfo會輸出一大堆的HTML,但是我們無法用一個變量例如$info=phpinfo();來捕捉,這時候ob就管用了。
③ 對輸出的內容進行處理,例如進行gzip壓縮,例如進行簡繁轉換,例如進行一些字符串替換。
④ 生成靜態文件,其實就是捕捉整頁的輸出,然后存成文件,經常在生成HTML,或者整頁緩存中使用。

它在ob_start()函數執行后,打開緩沖區,將后面的輸出內容裝進系統的緩沖區,ob_implicit_flush(0)函數來關閉絕對刷送(echo等),最后使用ob_get_clean()函數將緩沖區的內容取出來。

類__URL__常量和全局類

TP里的__URL__等全局常量用著很方便,可以很簡單的實現跳轉等操作,而定義它的函數createUrl函數我又想重用,于是借鑒YII的全局類定義方法:

定義基類及詳細方法(以后的全局方法會寫在這里)

class BaseSqier{
  //方法根據傳入的$info信息,和當前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;
    }
  }
}

在啟動文件中定義類并繼承基類;

require_once SQ_PATH.'BaseSqier.php';
class SQ extends BaseSqier{
}

在全局內都可以直接使用SQ::createUrl()方法來創建URL了。這樣,定義__URL__常量就很輕松了。

用單例模式定義數據庫連接基類

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("連接數據庫失敗,請檢查數據庫配置信息!");
    $link->query('set names utf8');
  }
  public function __clone() {
    return self::getInstance();
  }
}

使用單例模式的核心是:

① 私有化構造函數,使無法用new來創建對象,也防止子類繼承它并改寫其構造函數;
② 用靜態變量存放當前對象,定義靜態方法來返回對象,如對象還未實例化,實例化一個,存入靜態變量并返回。
③ 構造其__clone魔術方法,防止clone出一個新的對象;

DB類的sql查詢函數

DB查詢函數是一個很復雜的部分,它是一個自成體系的東西,像TP和YII的查詢方法都有其獨特的地方。我這里暫時先借用TP的MODEL基類,有時間再慢慢補這個。

嗯,介紹一下像TP的查詢里的方法聯查的實現,其訣竅在于,在每個聯查方法的最后都用 return this 來返回已處理過的查詢對象。

階段總結:

yii2里的數據表和model類屬性之間的映射很酷(雖然被深坑過), 前面一直避開的模塊(module,我可以想像得到把它也添加到URI時解析的麻煩)有時間考慮一下。

接上文,繼續完善框架(三)

本次更新的主要內容有:

① 介紹了異常處理機制
② 完善了異常和錯誤處理
③ 數據表跟Model類的映射

異常處理

異常處理:異常處理是編程語言或計算機硬件里的一種機制,用于處理軟件或信息系統中出現的異常狀況(即超出程序正常執行流程的某些特殊條件)

異常處理用于處理程序中的異常狀況,雖說是“異常狀態”,但仍然還是在程序編寫人員的預料之中,其實程序的異常處理完全可以用‘if else'語句來代替,但異常處理自然有其優勢之處。

個人總結其優點如下:

① 可以快速終止流程,重置系統狀態,清理變量和內存占用,在普通WEB應用中,一次請求結束后,FAST CGI會自動清理變量和上下文,但如果在PHP的命令行模式執行守護腳本時,它的效果就會很方便了。

② 大量的if else語句會使代碼變得繁雜難懂,使用異常處理可以使程序邏輯更清晰易懂,畢竟處理異常的入口只有catch語句一處。

③ 一量程序中的函數出現異常結果或狀況,如果使用函數的return方式返回異常信息,層層向上,每一次都要進行return判斷。使用異常處理我們可以假設所有的返回信息都是正常的,避免了大量的代碼重復。

雖然將代碼放在try catch塊中會有微微的效率差,但是跟這些優點一比,這點消耗就不算什么了。那么PHP的異常處理怎么使用呢?

PHP內置有Exception類,使得我們可以通過實例化異常類來拋出異常。我們將代碼放在try語句中執行,并在其后用catch試圖捕捉到在try代碼塊中拋出的異常,并對異常進行處理。我們還可以在catch代碼段后使用finally語句塊,無論是否有異常都會執行finally代碼塊的代碼,try catch語句形如下面代碼:

try{
  throw new Exeption('msg'[,'code',$previous_exeception]);
}catch(Exeption $var) {
  process($var);
}catch(MyException $e){
  process($e)
}finally{
  dosomething();
}

使用try catch語句,需要注意:

① 當我們拋出異常時,會實例化一個異常類,此異常類可以自己定義,但在catch語句中,我們需要規定要捕獲的異常對象的類名,并且只能捕獲到特定類的異常對象,當然我們可以在最后捕獲一個異常基類(PHP內置異常類)來確保異常一定能被捕獲。

② 在拋出異常時,程序會被終止,并回溯代碼找到第一個能捕獲到它的catch語句,try catch語句是可以嵌套的,并且如上面代碼所示 cacth語句是可以多次定義的。

③ finally塊會在try catch塊結束后執行,即使在try catch塊中使用return返回,程序沒有執行到最后。

框架里的異常處理

說了那么多異常相關(當然解釋這些也是為了能理解和使用框架),那么框架里要怎么實現呢?

重寫異常類

我們可以重寫異常類,完善其內部方法:

<?php
class Exception
{
  protected $message = 'Unknown exception';  // 異常信息
  protected $code = 0;            // 異常代碼
  protected $file;              // 發生異常的文件名
  protected $line;              // 發生異常的代碼行號
  function __construct($message = null, $code = null,$previous_exeception = null);
  final function getMessage();        // 返回異常信息
  final function getCode();          // 返回異常代碼
  final function getFile();          // 返回發生異常的文件名
  final function getLine();          // 返回發生異常的代碼行號
  final function getTrace();         // 返回異常trace數組
  final function getTraceAsString();     // 返回異常trace信息
  /**
   * 記錄錯誤日志
   */
  protected function log(){
    Logger::debug();
  }
}

如上,final方法是不可以重寫的,除此之外,我們可以定義自己的方法,如記錄異常日志,像我自定義的log方法,在catch代碼塊中,就可以直接使用$e->log來記錄一個異常日志了。

注冊全局異常方法

我們可以使用set_exception_handler('exceptionHandler')來全局捕獲沒有被catch塊捕獲到的異常,此異常處理函數需要傳入一個異常處理對象,這樣可以分析此異常處理信息,避免系統出現不人性化的提示,增強框架的健壯性。

function exceptionHandler($e) {
  echo '有未被捕獲的異常,在' . $e->getFile() . "的" . $e->getLine() . "行!";
}

其他全局函數

順便再說一下其他的全局處理函數:

set_shutdown_function('shutDownHandler')來執行腳本結束時的函數,此函數即使是在ERROR結束后,也會自動調用。

set_error_handler('errorHandler')在PHP發生錯誤時自動調用,注意,必須在已注冊錯誤函數后才發出的錯誤才會調用。函數參數形式應為($errno, $errstr, $errfile, $errline);

但是要注意這些全局函數需要在代碼段的前面已經定義過再注冊。

數據表和Model類的ActiveRecord映射

初次使用yii2的ActivceRecord類覺得好方便,只需要定義其字段同名屬性再調用save方法就OK了(好神奇啊),它是怎么實現的呢,看了下源碼,明白了其大致實現過程(基類)。

1. 使用‘describe table_name' 查詢語句;
2. 分析查詢結果:對每一個字段,有Field(字段名)、Type(數據類型)、Null(是否為空)、Key(索引信息,‘PRI'表示為主鍵)、Default(默認值)、Extra(附加信息,如auto_increment)
3. 通過判斷其主鍵($row['KEY'] == 'PRI')信息,保存時看是否有主鍵信息,若存在,則為更新;不存在,則插入。

感謝各位的閱讀!關于“如何搭建自己的PHP MVC框架”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

安溪县| 荣昌县| 凉山| 鹤岗市| 曲沃县| 肥乡县| 边坝县| 广丰县| 错那县| 图木舒克市| 寿阳县| 汝州市| 五家渠市| 高邑县| 冀州市| 磐石市| 淳安县| 铜陵市| 五莲县| 五河县| 汉沽区| 湾仔区| 盐亭县| 南康市| 清苑县| 苗栗市| 华容县| 金山区| 崇文区| 永平县| 邳州市| 迁安市| 新安县| 左贡县| 龙南县| 辰溪县| 志丹县| 靖宇县| 花垣县| 太白县| 波密县|