那麼就利用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 框架自定義常量
對應的我們在composer.json中加入一些autoload配置,用以自動載入,省去我們實現自動載入。
"autoload": {
"psr-4": {
"Library\\": "library/",
"App\\": "app/"
},
"files": [
"library/Functions.php"
]
}
執行一下,composer install或者composer dump-autoload即可。
這裡簡單說明一下autoload的四種方式:
autoload的四種方式
- 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中找到。
- PSR-0
在 psr-0 key 下你定義了一個名稱空間到實際路徑的對映(相對於包的根目錄)。注意,這裡同樣支援 PEAR-style 方式的約定(與名稱空間不同,PEAR 類庫在類名上採用了下劃線分隔)。
請注意,名稱空間的申明應該以 \ 結束,以確保 autoloader 能夠準確響應。例: Foo 將會與 FooBar 匹配,然而以反斜槓結束就可以解決這樣的問題, Foo\ 和 FooBar\ 將會被區分開來。
PSR-0 引用都將被結合為一個單一的鍵值對陣列,儲存至 vendor/composer/autoload_namespaces.php 檔案中。
- classmap
你可以用 classmap 生成支援支援自定義載入的不遵循 PSR-0/4 規範的類庫。要配置它指向需要的目錄,以便能夠準確搜尋到類檔案。
classmap 引用的所有組合會儲存到 vendor/composer/autoload_classmap.php 檔案中。這個 map 是經過掃描指定目錄(同樣支援直接精確到檔案)中所有的 .php 和 .inc 檔案裡內建的類而得到的。
- 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;
}
}
這裡有兩個知識點:
- ArrayAccess陣列式訪問介面(提供像訪問陣列一樣訪問物件的能力的介面。)
- 魔術方法set和get(在給不可訪問屬性賦值時set() 會被呼叫;讀取不可訪問屬性的值時get() 會被呼叫。)
如果想了解更多,可看官方文件:
本作品採用《CC 協議》,轉載必須註明作者和本文連結