首頁>技術>

hello,大家好。今天要給大家分享的是php的進階教程 ,透過本教程講同學們將學會如何一步步實現MVC的架構,打造自己的MVC框架。

還記得上次分享給同學們的教程 “php基礎-設計模式”的中用到的各種模式嗎?這些模式會在本章學習MVC架構時用到。

說了那麼多,那MVC究竟是個什麼鬼?我們首先看個圖,來直觀的感受下。

透過上圖,大家可以很清楚地看到 ,首先是瀏覽器傳送請求,透過路由找到controller,然後由controller 去排程view 和model。

Controller:排程 model和view層的排程器

Model: 處理業務程式碼,並與資料庫互動。

View:controller獲取model的資料後將資料渲染到View(返回html),最終呈現給瀏覽器端

瞭解了MVC的架構,也不難看出,這是個責任清晰的三層模型,每一層都有自己的責任。Controller負責排程model 和view ,model層處理業務。view層負責渲染資料。前端程式設計師只要管好view層的設計。後端程式設計師只需管好自己的那塊業務邏輯。各司其職。協作開發。大大提高了開發效率。

看到這裡,同學們對於為什麼要使用MVC應該有了的大致 的瞭解了吧。想到2007年以前,那個時代,MVC剛剛流行。有很多公司不屑去用它,結果工程專案大了,人多了,大家各自為政,寫了很多重複的東西。一個專案要組合在一起,也是要費很大的精力和時間的。

同學們在瞭解了什麼是MVC以及為什麼要使用MVC後。相信你們也是躍躍欲試,想要一探其奧秘。接下來馬上為大家揭開其神秘面紗。

我們先來回顧下上圖,任何請求都是由客戶端發起,經由路由到達controller。然後才由controller繼續下面的操作。因此讓我們先看下php是如何實現這部分功能的。

先來看下這麼個專案結構

等等,裡面好像少了model和view。到哪去了?呵呵,先別急,今天只講路由篇,所以只看這些就夠了,下一篇會有model,view,xss,csrf等等。慢慢來。心急吃不了那熱豆腐。

那我們就先從入口檔案開始講吧。先來條醒目地分割線

index.php

=============================================

require "Vendor/custome.php";require (ROOT.DIRECTORY_SEPARATOR."Vendor/lib/autoload.php");require "http/router.php";

=============================================

可以看見就三行程式碼。

Vendor/custome.php

=============================================

define("ROOT",dirname(dirname(__FILE__)));

=============================================

定義了專案根目錄,這個有什麼鳥用?其實在我們require 一個php檔案時,

都是根據這個根目錄來的。我這的專案的那個根就是 php-mvc-stepbystep。

注意:

__FILE__:當前目錄所在檔案,這裡指的就是

define("ROOT",dirname(dirname(__FILE__)));檔案所在父目錄,即 php-mvc-stepbystep 目錄 

Vendor/autoload.php

=============================================

class MyAutoLoad {     static  function doPrint($classname)    {        $classPath = str_replace("\\","/",$classname);        if(is_file(ROOT.DIRECTORY_SEPARATOR.$classPath.".php"))        {            require_once (ROOT.DIRECTORY_SEPARATOR.$classPath.".php");        }    }}spl_autoload_register(array('MyAutoLoad','doPrint'));

=============================================

我們先來說個函式。function __autoload($classname)。這個函式的作用就是自定載入類。這個自動載入類又是個什麼鬼?

話說php5之前還沒有這個函式。它有什麼作用呢?回想當初,我們要引入一個php檔案,那麼就得require "xxx.php",那如果要引入很多檔案呢?哦。MyGod 。拿得多累啊!!!! 所有後來php的建立者就開發這個功能。你可以不用一個個引入php檔案。只需要如此便可。

舉個例子

Vendor/lib/autoload.php

=============================================

 function __autoload($classname)    {        $classPath = str_replace("\\","/",$classname);//將名稱空間轉換成類檔案所在目錄        if(is_file(ROOT.DIRECTORY_SEPARATOR.$classPath.".php"))        {            require_once (ROOT.DIRECTORY_SEPARATOR.$classPath.".php");        }    }

=============================================

php-mvc-stepbystep/index.php

=============================================

require (ROOT.DIRECTORY_SEPARATOR."Vendor/lib/autoload.php");

use Test\Test

Test::sayHello();

=============================================

php-mvc-stepbystep/Test/Test.php

=============================================

namespace Test;class Test{    static function sayHello()    {        echo "hello";    }}

=============================================

注意:

index.php在引入auload.php後 無需 require "Test/Test.php" 盡然也能執行。神奇吧,這就是autoload的好處。這裡的namespace 就是檔案所在位置。而 類名必須與檔名一致

在充分了解__autoload($classname)後,再來看下sp_autoload_register(array('自定義autoload類','方法'))

如果這個函式引數為空,則預設呼叫__autoload($classname),否則

就呼叫自定義類種的方法。

現在再回過頭來看下

Vendor/autoload.php

=============================================

class MyAutoLoad {     static  function doPrint($classname)    {        $classPath = str_replace("\\","/",$classname);//將namespace 轉換成類檔案目錄        if(is_file(ROOT.DIRECTORY_SEPARATOR.$classPath.".php"))//判斷檔案是否存在        {            require_once (ROOT.DIRECTORY_SEPARATOR.$classPath.".php");//引入檔案        }    }}spl_autoload_register(array('MyAutoLoad','doPrint'));

=============================================

http/router.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 18:25 */use Vendor\router;//route('執行請求的url{型別引數1,型別引數2}','控制器類@方法')router::route('/test1/myttt/{Ints,Bools,Bools}','http\controllers\Test@mytest');

=============================================

這就是本文的重點,路由,沒錯,就是它!!!一個神奇的路由。將客戶端請求路由到指定的類檔案下的某個方法。那它又是如何實現的呢?別急。請跟我來。

例子

根據上面的route設定,在瀏覽器中輸入

引數型別不匹配規則 ,比如最後一個引數改成sss

路徑不匹配規則 ,如

就是如此神奇。那這個是如何實現的呢?請看以下程式碼

Vendor/router.php

=============================================

static function route($url,$call){    $reqUrl = explode('/',$_SERVER['REQUEST_URI']);//客戶端請求的url    if(count($reqUrl)>0) //是否帶引數 /test1/myttt/1/true/true    {        //  解析請求路徑 用來與$url作格式匹配    self::$classPath  = $reqUrl[1];    self::$methodPath = $reqUrl[2];    }    $arr = explode('@',$call);//要呼叫的控制器類和方法進行解析    self::$controller = $arr[0];//控制器類  self::$method     = $arr[1];//方法   $params = self::getParams($reqUrl,3);//請求url 中獲得的型別引數    self::patternUrl($url,$reqUrl);//匹配請求路徑與$url是否一致    kernel::initObserver(self::$controller);//註冊控制器  call_user_func_array( array(registerObj::$objs[self::$controller],self::$method),arrayy($params)  );//呼叫註冊好的控制器及對應的方法}//匹配路徑規則
static function patternUrl($patternUrl,$reqUrl){    $res = parse_url($patternUrl,PHP_URL_PATH);//解析路徑規則    $patternUrl = explode("/",$res);//將其分割成陣列    $patternUrlParams = explode(    ',',trim(trim(self::getParams($patternUrl,3)[0],"{"),"}")//獲取型別引數  );  //url請求 是否匹配路徑規則    if($patternUrl[1] == self::$classPath )    {        if($patternUrl[2] == self::$methodPath  )        {        } else {            print_r("路徑與route中設定的格式不匹配");            exit;        }    } else {        echo '路徑與route中設定的格式不匹配';        exit;    }    $params = self::getParams($reqUrl,3);//獲取請求url 的型別的引數  //型別引數是否與規則中設定的引數數量一致    if(count($patternUrlParams) != count($params))    {        echo "引數個數不匹配";        exit;    }  //引數型別是否與設定的型別一致 Ints代表 int, Strings 代表string ,Bools 代表bool    foreach ($patternUrlParams as $k=>$v)    {        //var_dump(Types::$v($params[$k]));        if(!Types::$v($params[$k]))        {            echo '引數型別不匹配';            exit;        }    }    unset($params);    unset($res);    unset($patternUrl);    unset($patternUrlParams);}

=============================================

執行流程

首先 route($url,$call) 方法的作用是根據第一個引數設定路的徑規則,校驗請求的路徑和引數(具體如何校驗,請看patternUrl($patternUrl,$reqUrl))。滿足路徑匹配規則後解析第二個引數裡的對應控制器和方法。接著註冊該控制器,最後呼叫控制器及方法。

下面貼出route.php的完整程式碼

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 17:19 */namespace Vendor;use Vendor\lib\registerObj;use kernel\kernel;use Vendor\lib\Types\Types;class router{    private static $ins=null;    private static $objList = [];    private static $controller;    private static $method;    private static $classPath;    private static $methodPath;    private function __construct(){}    private function __clone(){}    public static function getParams($reqUrl,$step)    {        for($i=0;$i<$step;$i++)        {            array_shift($reqUrl);        }        return $reqUrl;    }    static function patternUrl($patternUrl,$reqUrl)    {        $res = parse_url($patternUrl,PHP_URL_PATH);        $patternUrl = explode("/",$res);        $patternUrlParams = explode(',',trim(trim(self::getParams($patternUrl,3)[0],"{"),"}"));        if($patternUrl[1] == self::$classPath )        {            if($patternUrl[2] == self::$methodPath  )            {            } else {                print_r("路徑與route中設定的格式不匹配");                exit;            }        } else {            echo '路徑與route中設定的格式不匹配';            exit;        }        $params = self::getParams($reqUrl,3);        if(count($patternUrlParams) != count($params))        {            echo "引數個數不匹配";            exit;        }        foreach ($patternUrlParams as $k=>$v)        {            //var_dump(Types::$v($params[$k]));            if(!Types::$v($params[$k]))            {                echo '引數型別不匹配';                exit;            }        }        unset($params);        unset($res);        unset($patternUrl);        unset($patternUrlParams);    }    static function route($url,$call)    {        $reqUrl = explode('/',$_SERVER['REQUEST_URI']);        if(count($reqUrl)>0)        {            self::$classPath  = $reqUrl[1];            self::$methodPath = $reqUrl[2]??"index";        }        $arr = explode('@',$call);        self::$controller = $arr[0];        self::$method     = $arr[1];        $params = self::getParams($reqUrl,3);        self::patternUrl($url,$reqUrl);        //var_dump($params);die;        kernel::initObserver(self::$controller);        call_user_func_array(   array(registerObj::$objs[self::$controller],self::$method),array($params)    );    }}

=============================================

在route.php,細心的同學可能對kernel::initObserver(self::$controller)表示很疑惑。它是如何註冊控制器的呢?不註冊不行嗎?是的。任何使用者自定義的控制器類必須註冊。否則不能使用。下面就讓我們來一探它的真實面貌。

kernel/kernel.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/25 * Time: 0:32 */namespace kernel;use Vendor\lib\RegObserver\RegObserver;use Vendor\lib\RegObs\RegObs;use Vendor\lib\ManagerObserver\ManagerObserver;class kernel{    private function __construct(){}    private static $instance = null;    private function __clone(){}    static function initObserver($key)    {        $observer = new RegObserver();        $obs      = new RegObs();        $mg       = new ManagerObserver();        if (is_array($key))        {            foreach ($key as $k=>$v)            {                $mg->add($v,$obs);                $observer->notify($mg);            }        } else {            $mg->add($key,$obs);            $observer->notify($mg);        }    }}

=============================================

看到如此簡潔的程式碼,開不開心,意不意外?其思想是採用觀察者模式,透過代理 呼叫Vendor/lib/registerObj.php 將空控制器類註冊到 該物件的靜態變數registerObj::$objs,就是上面route.php 中的

 call_user_func_array(   array(registerObj::$objs[self::$controller],self::$method),array($params)    );

是不是有點暈,沒關係。我們來分步驟講解

先看這段程式碼

static function initObserver($key)    {        $observer = new RegObserver();//觀察者        $obs      = new RegObs();//被觀察者        $mg       = new ManagerObserver();//新增,刪除被觀察者        if (is_array($key))//判斷控制器類是否是以陣列形式傳遞的        {            foreach ($key as $k=>$v)            {                $mg->add($v,$obs);//新增控制器類到被觀察者                $observer->notify($mg);//通知被觀察者註冊控制器類            }        } else {            $mg->add($key,$obs);            $observer->notify($mg);        }    }

這段程式碼主要就是註冊控制器類。再來看看註冊具體實現方式

$observer = new RegObserver();

Vendor/lib/RegObserver/RegObserver.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 1:12 */namespace Vendor\lib\RegObserver;use IFS\Observer\Observer;class RegObserver implements Observer {    public function notify( $obs)    {        // TODO: Implement notify() method.    /*     *$obs->observers這是個被觀察者列表,     *由Vendor/lib/ManagerObserver/ManagerObserver/ManagerObserver.php新增     */    $t = $obs->observers;    foreach ($t as $k=>$v) {
            $v->handler($k);//註冊控制器類        }    }}

=============================================

這段程式碼這要是通知被觀察者去註冊控制器類,然後再看下這個

$mg       = new ManagerObserver();

Vendor/lib/ManagerObserver/ManagerObserver.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 2:38 */namespace Vendor\lib\ManagerObserver;use IFS\Observermanager\Observermanager;class  ManagerObserver implements Observermanager {    public $observers = [];//被觀察者列表,就是控制器類    function add($key, $obs)    {        // TODO: Implement add() method.        $this->observers[$key] = $obs;    }    function delete($key)    {        // TODO: Implement delete() method.        unset($this->observers[$key]);    }}

=============================================

Vendor/lib/RegObs/RegObs.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 2:34 */namespace Vendor\lib\RegObs;use IFS\Obs\Obs;use Vendor\lib\regProxy\regProxy;class RegObs implements Obs {//這裡的$obj 就是控制器類    function handler($obj)    {        //var_dump((new $obj));die;        // TODO: Implement handler() method.        //(new $obj)->mytest();die;        $proxy = new regProxy();        $proxy->register($obj,(new $obj));    }}

=============================================

以上程式碼就是被觀察者

透過代理類regProxy 去掉Vendor/registerObj/registerObj.php的register方法來註冊控制器類

注意 (new $obj) 這個就是以字串作為物件來建立

例如:

(new 'http\controllers\Test') 等同於 new http\controllers\Test()

看完被觀察者,再來看下代理類

Vendor/lib/regProxy/regProxy.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 2:45 */namespace Vendor\lib\regProxy;class regProxy  {    function __call($func, $args)    {        // TODO: Implement registerObj() method.        //$c = new registerObj();        //var_dump($args);die;        call_user_func_array(array('Vendor\lib\registerObj',$func),$args);    }}

=============================================

以上這段程式碼是代理registerObj.php的類的register方法來註冊控制器類,那理解了這個代理方式,接下來就請下這個registerObj吧

Vendor/lib/registerObj/registerObj.php

=============================================

<?php/** * Created by PhpStorm. * User: mario * Date: 2021/1/26 * Time: 3:10 */namespace Vendor\lib;use IFS\Register\Register;class registerObj implements Register {    public static $objs = [];    public static function register($key, $val)    {        self::$objs[$key] = $val;        //var_dump(get_class_methods(self::$objs[$key]));    }}

=============================================

以上程式碼即是正確的註冊控制器類的方法。

總結執行流程:

1 router 校驗url規則

2 透過自動載入方法對映對應控制器類

3由kernel類透過觀察者模式將控制器類註冊到registerObj的$objs中

4呼叫控制器的方法。

對應程式碼如下

kernel::initObserver(self::$controller);//註冊控制器類call_user_func_array(    array(registerObj::$objs[self::$controller],self::$method),array($params));//呼叫控制器方法

到這裡為止,“php-mvc-stepbystep-路由篇” 已經講完。下一篇要講得是

php-mvc-stepbystep-model篇”。同學們 敬請期待。下期再見

16
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 用別人寫好的程式碼,完成我的工作,剩下的時間去摸魚