PHP DIY 系列------框架篇:3. 路由解析

13sai發表於2020-02-20

回顧

上一節我們介紹了編寫了如何處理請求與輸出資料,這一節我們開始編寫路由模組。


正文

還記得我們之前建立的Application在哪裡嗎?

我們先思考一下Application應該具備哪些功能?

首先很重要的,我們要讓應用執行起來,姑且就先定義run方法。另外我們需要處理請求並且輸出資料,我們再定義一個handleRequest方法。當然,我們的應用是有一些配置資訊(config)的。

因為,我們不難編寫出以下程式碼:

<?php

namespace Library;

use Library\Exceptions\SaiException;
use Library\Https\Request;
use Library\Https\Response;

class Application
{
    private $config;

    private $request;

    public function __construct(Request $request, $config = [])
    {
        $this->config = $config;
        $this->request = $request;
    }

    /**
     * 執行應用並輸出資料
     * @return bool
     */
    public function run()
    {
        try {
            $response = $this->handleRequest($this->request);
            $response->send();
            return $response->exitStatus;
        } catch (SaiException $e) {
            $e->response($e->getCode(), [
                'line' => $e->getLine(),
                'msg' => $e->getMessage(),
                'code' => $e->getCode(),
                'file' => $e->getFile(),
            ]);
            return false;
        }
    }

    /**
     * 處理請求
     * @param Request $request
     * @return mixed
     * @throws SaiException
     */
    public function handleRequest(Request $request)
    {
        // todo
        // 返回Response物件
        return $response;
    }
}

這裡我們看到handleRequest方法還有一部分程式碼為完成,回想以下流程圖,這裡就是我們比較核心的部分,路由處理模組。

路由解析

路由解析我們使用非常簡單而常見的處理方式,不妨看幾個url例子來理解一下:

route controller method
http://blog.13sai.com/ IndexController index
http://blog.13sai.com/admin AdminController index
http://blog.13sai.com/admin/test AdminController test
http://blog.13sai.com/admin/index/test Admin\IndexController test

有沒有看出規律,我們會以斜槓/分割路由為幾個部分,最後兩部分分別是對應的控制器名稱和方法名稱,少於兩部分預設用index,多餘兩部分的作為控制器的名稱空間。然後我們要根據路由找到控制器構建出控制器。

/**
 * 控制器處理
 * @param $route
 * @return mixed
 * @throws NotFoundException
 */
public function runAction($route)
{
    $match = explode('/', $route);
    $match = array_filter($match);

    // 處理$route=/
    if (empty($match)) {
        $match = ['index'];
        $controller = $this->createController($match);
        $action = 'index';

    // 處理$route=index
    } elseif (count($match) < 2) {
        $controller = $this->createController($match);
        $action = 'index';
    } else {
        $action = array_pop($match);
        $controller = $this->createController($match);

        if (!method_exists($controller, $action)) {
            throw new NotFoundException("method not found:".$action);
        }
    }

    // 將get和post注入控制器方法中
    return $controller->$action(array_merge($this->getQueryParams(), $this->getBodyParams()));
}

// app應用控制器名稱空間
private $controllerNameSpace = 'App\\Https\\Controllers\\';

// 之前定義的基類控制器
private $baseController = 'Library\\Https\\Controller';

public function createController($match)
{
    $controllerName = $this->controllerNameSpace;

    foreach ($match as $namespace) {
        $controllerName .= ucfirst($namespace).'\\';
    }

    $controllerName = rtrim($controllerName,'\\').'Controller';

    if (!class_exists($controllerName)) {
        if ($controllerName == $this->controllerNameSpace.'IndexController') {
            return new $this->baseController;
        }
        throw new NotFoundException("controller not found:".$controllerName);
    }

    return new $controllerName;
}

上面是尋找控制器和方法的過程,但我們需要提前獲得頁面地址以解析路由。

知識點:

  1. 反斜槓:反斜線有多種用法。首先,如果緊接著是一個非字母數字字元,表明取消 該字元所代表的特殊涵義。這種將反斜線作為轉義字元的用法在字元類 內部和外部都可用。
  2. array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array——依次將 array 陣列中的每個值傳遞到 callback 函式。如果 callback 函式返回 true,則 array 陣列的當前值會被包含在返回的結果陣列中。陣列的鍵名保留不變。程式碼中是過濾value為空的單元。

獲取頁面地址

/**
 * 返回不含引數的REQUEST_URI地址
 */
public function resolve()
{
    return $this->getPathUrl();
}

private $pathUrl;

/**
 * 獲取請求地址
 * @return bool|mixed|string
 */
public function getPathUrl()
{
    if (is_null($this->pathUrl)) {
        $url = trim($_SERVER['REQUEST_URI'], '/');
        $index = strpos($url, '?');
        $this->pathUrl = ($index > -1) ? substr($url, 0, $index) : $url;
    }

    return $this->pathUrl;
}

我們儘量讓Application變得簡潔,而路由解析又和Request關聯度較高,因此我們不妨把這些方法丟擲到Request物件。

知識點:

  1. strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int——返回 needle 在 haystack 中首次出現的數字位置,如果沒找到 needle,將返回 FALSE。

上面已經解析好路由並且找到了控制器和方法。這樣我們就可以完善Application的程式碼了。

處理請求

public function handleRequest(Request $request)
{
    $route = $request->resolve();

    $response = $request->runAction($route);
    /**
     * 執行結果賦值給$response->data,並返回給response物件
     */
    if ($response instanceof Response) {
        return $response;
    }

    throw new SaiException('輸出的內容格式錯誤');
}

再次需要說明的是,我們在這裡僅做了json格式輸出,如果有興趣,你可以自己動手擴充一下。

另:NotFoundException繼承自SaiException,程式碼:

<?php

namespace Library\Exceptions;

class NotFoundException extends SaiException
{
    protected $code = 404;
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

分享開發知識,歡迎交流。qq957042781

相關文章