為什么要構建自己的 PHP 框架?
現在的 PHP 框架很多,當然不止 PHP ,即使是其他編程語言也有很多框架,這篇文章講 PHP 框架構建是因為我對 PHP 的生態最為熟悉,但這個方法同樣也適用于其他編程語言框架的構建。
框架是為了提升我們的應用開發效率,市面上有很多開源免費的框架給我們使用,我們盡可以拿來用,為什么還要自己構建一個自己的框架呢?原因就在于市面上的開源框架,是給大部分人用的,給通用項目用的,作為框架的開發者是不知道自己的框架使用者的具體業務的,所以開源框架一定是滿足大部分人的需求,而且力求能夠為開發者提供所有可能用到的功能。
但是對于一個商業項目或者是一個你自己要做的項目也許只能用到框架的很少一部分功能,或者是框架給你提供的東西并不是最符合你自己的需求的,你使用了框架的一部分功能,另一部分根本沒用,這樣使用框架首先是性能上的損失,一些你根本用不到的功能卻要降低你應用的性能顯然不合適的。再就是也許框架提供的功能不是你想要的,或者這個功能這個框架提供的并不是符合你需求的,又或者要使用這部分功能必須按照框架開發者制定的規范來使用,這個規范并符合你的開發哲學。
從哪開始?
各種現代編程語言都有自己的包管理工具,PHP 就是 composer ,利用它我們就可以構建屬于自己的框架了,并能很好的組織我們的框架。
怎么開始?
我們該怎么開始構建我們自己的框架呢?從零開始嗎?這個問題沒有標準答案,如果你要做的項目要求很嚴格,從底層開始就要保證項目架構的最穩定可控,那么建議你從零開始。如果要求不是非常嚴格那么我們就從那些開發一個應用最基本需要的功能開始,這樣的功能誰提供呢?PHP 有很多微框架,這些框架提供開發一個應用最基礎的功能,我們可以從這里開始。
首先我們通過 composer init 初始化一個項目:
{ "name": "dongm2ez/m2ez-framework", "description": "a dongm2ez's framework", "keywords": ["framework", "m2ez-framework"], "license": "MIT", "authors": [ { "name": "dongm2ez", "email": "dongm2ez@163.com" } ], "require": {} }
復制代碼這就是我得到的一個 composer.json 的描述文件,現在我們就從這里開始。我的目標是構建一個最符合我開發習慣的框架,讓我的開發效率最高。
我選擇 Slim 框架作為我的框架基礎框架,這是一個微框架,我喜歡它,它足夠簡單,提供了 web 開發 和 API 開發最基礎的功能,而且還有一個原因開發這個框架的作者寫了一本名為《Modern PHP》的書,這本書顛覆了我對 PHP 這個語言的認知,開始喜歡并樂于使用它。
在引入這個框架之前我還要對 PHP 版本做個限制,從我使用 PHP 從 5.2 開始到現在,PHP 已經發展到 PHP 7.2 了,但是我不想再去使用低版本的 PHP,一個是 PHP 低版本馬上將失去官方的支付,另一個是一些 PHP 的新特性我不能使用,而且低版本的性能也是不好的。所以我要將我的框架限制在 PHP 7.0 以上,同時我希望我的框架對中文有更好的支持。
那么我將更新我的框架 composer.json 文件:
{ "name": "dongm2ez/m2ez-framework", "description": "a dongm2ez's framework", "keywords": ["framework", "m2ez-framework"], "license": "MIT", "authors": [ { "name": "dongm2ez", "email": "dongm2ez@163.com" } ], "require": { "php": ">=7.0.0", "ext-mbstring": "*", "slim/slim": "^3.0" } }
復制代碼這樣我就獲得了一個最基礎的我的框架版本,但是我還沒完成,因為我們沒有定義我的框架目錄結構。我覺得 laravel 框架的目錄劃分是挺讓我喜歡的,但我又不完全喜歡 laravel 的目錄結構,我需要對它進行改造。
├── app
│ ├── Helpers.php
│ ├── Http
│ │ └── Controllers
│ └── Models
├── composer.json
└── tests復制代碼我這樣設置我的目錄結構,并更新我的 composer.json:
{ "name": "dongm2ez/m2ez-framework", "description": "a dongm2ez's framework", "keywords": ["framework", "m2ez-framework"], "license": "MIT", "type": "project", "authors": [ { "name": "dongm2ez", "email": "dongm2ez@163.com" } ], "require": { "php": ">=7.0.0", "slim/slim": "^3.0" }, "require-dev": { "phpunit/phpunit": "~6.0" }, "autoload": { "classmap": [ "app/Models" ], "psr-4": { "App\": "app/" }, "files": [ "app/Helpers.php" ] }, "autoload-dev": { "psr-4": { "Tests\": "tests/" } } }
復制代碼這樣的框架可以訪問嗎,顯然是不行的,我們還要加一些東西讓我們的框架真正可以跑起來,然后在來迭代它。
├── app
│ ├── Helpers.php
│ ├── Http
│ │ └── Controllers
│ └── Models
├── bootstrap
│ ├── app.php
│ └── autoload.php
├── composer.json
├── config
├── public
│ └── index.php
├── routers
└── tests復制代碼我們將目錄結構改造成這樣,并編寫一些啟動框架的代碼到相應的文件
// public/index.php
<?php require __DIR__.'/../bootstrap/autoload.php'; $app = require_once __DIR__.'/../bootstrap/app.php'; $app->run(); // bootstrap/app.php <?php $app = new SlimApp; return $app; // bootstrap/autoload.php <?php define('M2EZ_START', microtime(true)); require __DIR__.'/../vendor/autoload.php';
復制代碼然后運行 composer install 安裝框架的依賴包,安裝完成后我們的目錄中就會多出一個 vendor 的目錄和 composer.lock 的文件,此時運行 php -S 0.0.0.0:8080 -t public public/index.php 利用 PHP 自帶的 web 服務器進行測試,為了這個命令更簡單使用,我們可以將這個命令加到 composer.json 的 script 中。
此時訪問 127.0.0.1:8080 或 localhost:8080 就可以看到如下的頁面:
file
這說明框架正確啟動了,那么我們怎么確定框架工作正常呢,這里有個簡單方法:
<?php use SlimHttpRequest; use SlimHttpResponse; require __DIR__.'/../bootstrap/autoload.php'; $app = require_once __DIR__.'/../bootstrap/app.php'; $app->get('/hello/{name}', function (Request $request, Response $response) { $name = $request->getAttribute('name'); $response->getBody()->write("Hello, $name"); return $response; }); $app->run();
復制代碼對 public/index.php 的代碼進行修改,此時訪問 http://localhost:8080/hello/dongm2ez,那么我們就會看到:
file
測試是成功了,但是我們不能把路由和邏輯都寫到 index.php 文件里,因此我們需要代碼更好的組織。要讓我們的目錄規劃發揮正在的作用。
為了單獨管理路由,我將路由單獨寫在 routers 文件夾中,在文件夾中我們新建兩個 PHP 腳本文件,然后在 public/index.php 中加入兩行代碼:
<?php require __DIR__ . '/../bootstrap/autoload.php'; $app = require_once __DIR__ . '/../bootstrap/app.php'; require __DIR__ . '/../routers/web.php'; require __DIR__ . '/../routers/api.php'; $app->run();
復制代碼變成這樣,這樣我就可以單獨管理 API 和 WEB 項目的路由了,如果有其他路由就也可以 require