PHP DIY 系列------框架篇:1. 框架目錄與輔助

13sai發表於2020-02-18

那麼就利用composer來開始我們的專案吧。

新建目錄並進入目錄,輸入命令:

composer init

命令列會跟你確認以下資訊(以下資訊可以自行DIY)

# 1. 輸入專案名稱空間
# 注意<vendor>/<name> 必須要符合 [a-z0-9_.-]+/[a-z0-9_.-]+
Package name (<vendor>/<name>) yourname/projectname

# 2. 專案描述
Description []:這是一個測試composer init 專案

# 3. 輸入作者資訊
Author [13sai <sai0556@qq.com>, n to skip]:

# 4. 輸入最低穩定版本,stable, RC, beta, alpha, dev
Minimum Stability []:dev

# 5. 輸入專案型別
Package Type (e.g. library, project, metapackage, composer-plugin) []:project

# 6. 輸入授權型別
License []:MIT

Define your dependencies.

# 7. 輸入依賴資訊
Would you like to define your dependencies (require) interactively [yes]?

# 7.1. 如果需要依賴,則輸入要安裝的依賴
Search for a package:php

# 7.2. 輸入版本號
Enter the version constraint to require (or leave blank to use the latest version): >=5.4.0

#  如需多個依賴,則重複以上兩個步驟(7.1/7.2)
Search for a package:

# 8. 是否需要require-dev,
Would you like to define your dev dependencies (require-dev) interactively [yes]?


{
    "name": "sai/saif",
    "description": "php framework",
    "type": "project",
    "require": {
    },
    "license": "MIT",
    "authors": [
        {
            "name": "13sai",
            "email": "sai0556@qq.com"
        }
    ],
    "minimum-stability": "dev"
}

# 9. 是否生成composer.json
Do you confirm generation [yes]?

# 現在安裝依賴項嗎
Would you like to install dependencies now [yes]?

我們的目錄下會生成composer.json。


然後我們來思考一個問題:

你覺得一個基礎的API框架需要什麼模組呢?

下面是我的思考結果:

  • 路由
  • 請求
  • 資料響應
  • 異常處理
  • 日誌系統…

當然,你可能覺得還應該有:

  • 配置
  • session
  • 快取
  • 驗證
  • 模型
  • 服務層
  • 檔案上傳…

也許你想到更多:

  • 任務排程
  • 佇列
  • 使用者驗證

那麼這麼些我們如何取捨呢?手心手背都是肉啊。

這裡需要做一下說明,我們所做的框架無需考慮太多的功能,是做一個簡單可用的API介面框架,我們接受get/post請求,返回json資料,並且路由好用,這是我們的初衷,其他的暫且就“斷舍離”吧。

我們先畫一個極簡的流程圖。

簡單流程圖

我們一切從簡,所以我們定義以下幾個模組:

  • 路由
  • 控制器
  • 請求
  • 資料響應
  • 配置
  • 異常處理

基於這些,我們新建目錄,app和library,public

  • app web應用
  • library 核心程式碼
  • public 入口目錄

出於簡單安全考慮,我們的入口單獨放在public目錄,並在目錄下新建index.php作為我們的入口檔案。

library下面新建幾個目錄和檔案

  • Components 常用元件
  • Exceptions 異常模組
  • Https http應用模組
  • Sessions session模組
  • Application.php 應用檔案
  • Config.php 配置檔案
  • Functions.php 常用函式
  • System.php 框架自定義常量

image

對應的我們在composer.json中加入一些autoload配置,用以自動載入,省去我們實現自動載入。

"autoload": {
    "psr-4": {
        "Library\\": "library/",
        "App\\": "app/"
    },
    "files": [
        "library/Functions.php"
    ]
}

執行一下,composer install或者composer dump-autoload即可。


這裡簡單說明一下autoload的四種方式:

autoload的四種方式

  1. PSR-4

在psr-4鍵下,定義了相對於包根目錄從名稱空間到路徑的對映。當自動載入一個類(如foo\bar\baz)時,指向src/目錄的名稱空間字首foo\,意味著自動載入程式將查詢一個名為src/bar/baz.php的檔案,幷包括它(如果存在)。注意,與舊的psr-0樣式相反,字首(foo\)不在檔案路徑中。

名稱空間字首必須以“\”結尾,以避免類似字首之間的衝突。例如,foo將匹配foobar名稱空間中的類,因此後面的反斜槓可以解決問題:foo\,foobar\。

該陣列可以在生成的檔案vendor/composer/autoload_psr4.php中找到。

  1. PSR-0

在 psr-0 key 下你定義了一個名稱空間到實際路徑的對映(相對於包的根目錄)。注意,這裡同樣支援 PEAR-style 方式的約定(與名稱空間不同,PEAR 類庫在類名上採用了下劃線分隔)。

請注意,名稱空間的申明應該以 \ 結束,以確保 autoloader 能夠準確響應。例: Foo 將會與 FooBar 匹配,然而以反斜槓結束就可以解決這樣的問題, Foo\ 和 FooBar\ 將會被區分開來。

PSR-0 引用都將被結合為一個單一的鍵值對陣列,儲存至 vendor/composer/autoload_namespaces.php 檔案中。

image

  1. classmap

你可以用 classmap 生成支援支援自定義載入的不遵循 PSR-0/4 規範的類庫。要配置它指向需要的目錄,以便能夠準確搜尋到類檔案。

classmap 引用的所有組合會儲存到 vendor/composer/autoload_classmap.php 檔案中。這個 map 是經過掃描指定目錄(同樣支援直接精確到檔案)中所有的 .php 和 .inc 檔案裡內建的類而得到的。

  1. files

如果你想要明確的指定,在每次請求時都要載入某些檔案,那麼你可以使用 ‘files’ autoloading。通常作為函式庫的載入方式(而非類庫)。files 引用的檔案會儲存到 vendor/composer/autoload_files.php 檔案中


我們先不著急進行核心程式碼編寫,不妨先做一下輔助工作,常用方法,異常處理等。

常用函式Functions

編寫常用函式

<?php
/**
 * 常用函式
 */

if (!function_exists("p")) {
    function p($var)
    {
        if (is_bool($var)) {
            var_dump($var);
        } elseif (is_null($var)) {
            var_dump(null);
        } else {
            die("<meta charset='utf-8'/>
<pre style='position:relative;
z-index:999;
padding:10px;
border-radius:5px;
background:#f5f5f5;
border:1px solid #aaa;
font-size:14px;
line-height:18px;
opacity:0.8;'>".print_r($var, true)."</pre>");
        }
    }
}

······

異常處理

在Exceptions目錄下,定義一個最基礎的異常SaiException:

<?php

namespace Library\Exceptions;

class SaiException extends \Exception
{
    const CODE_MAPPING = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',

        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status',
        226 => 'IM Used',

        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Reserved',
        307 => 'Temporary Redirect',

        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        426 => 'Upgrade Required',
        429 => 'Too Many Request',

        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        510 => 'Not Extended',

        1001 => 'LACK PARAMS',
        1002 => 'RETRY TOO MANY',
    ];

    /**
     * 輸出指定HTTP狀態碼的響應頭資訊
     * @param int $code
     * @param $data
     * @return void
     */
    public function response($code, $data){
        $code = array_key_exists($code, self::CODE_MAPPING)? $code : 500;
        $desc = self::CODE_MAPPING[$code];

        $protocol = $_SERVER['SERVER_PROTOCOL'];
        if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
            $protocol = 'HTTP/1.0';
        $header = "$protocol $code $desc";
        header($header);
        p($data);
    }
}

幾乎後面所有Exception的類都會繼承這個異常類。


在Components目錄下新建基礎的類Base:

<?php

namespace Library\Components;

class Base implements \ArrayAccess
{
    private $_container;

    public function __get($name)
    {
        if (method_exists($this, $method = 'get'.ucfirst($name))) {
            return $this->$method($name);
        }

        return null;
    }

    public function __set($name, $value)
    {
        if (method_exists($this, $method = 'set'.ucfirst($name))) {
            return $this->$method($name, $value);
        }
    }

    public function offsetSet($offset, $value) 
    {
        if (is_null($offset)) {
            $this->_container[] = $value;
        } else {
            $this->_container[$offset] = $value;
        }
    }
    public function offsetExists($offset)
    {
        return isset($this->_container[$offset]);
    }

    public function offsetUnset($offset)
    {
        unset($this->_container[$offset]);
    }

    public function offsetGet($offset)
    {
        return isset($this->_container[$offset]) ? $this->_container[$offset] : null;
    }
}

這裡有兩個知識點:

  1. ArrayAccess陣列式訪問介面(提供像訪問陣列一樣訪問物件的能力的介面。)
  2. 魔術方法set和get(在給不可訪問屬性賦值時set() 會被呼叫;讀取不可訪問屬性的值時get() 會被呼叫。)

如果想了解更多,可看官方文件:

本作品採用《CC 協議》,轉載必須註明作者和本文連結

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

相關文章