前言
上學期學校的實作課之中,介紹了 Nodejs 的事件驅動、異步I/O特點,同時也練習使用 Express 和 React 充當前後端的框架。
於是乎我在想,PHP 當紅的框架是 Laravel,當初使用 Laravel 5.6 的時候,只知道它是一個完善的 PHP MVC 框架,可是如果單純使用 RESTful Api 的形式去使用,又顯得過於肥大,再加上 Laravel 學習曲線偏高,導致我對於這個框架避而不談。
就在我查找其他替代框架時(Symphony、CakePHP、CodeIgniter 4、Slim)發現了 Slim,Slim 這個框架非常特別,它不像主流框架一開始就跟你說它有什麼什麼功能,而是你需要什麼功能,可以按需擴充成自己想要的狀態。
更何況它符合PSR-7(Request、Response)、PSR-11(Denpendency Injection)、PSR-15(Middleware)的規範,要找適用的函式庫都有一定的形式規範,不用擔心遇到寫法雜亂的 library。
- 以下我構想這個框架要符合什麼條件:
- 基於 ADR 模式
- 要有一個基礎的 ORM(可以不用 Migration)
- 要有 Jwt 功能
- 能夠輕鬆寫單元測試、E2E 測試
- 要使用 Docker 一鍵啟動
- 使用 Github Actions CI / CD
- 本篇主要是參考 Fogless 的Slim 4 搭建 RESTful API 以及 Daniel Opitz 的Slim 4 - Tutorial,並且結合自身所學,再根據個人習慣建構。基礎篇的建構方向是能夠有基本的環境、 Slim 雛形、基本的返回JSON資訊、以及 Middleware 和 php-di 的相關配置。
安裝
本機最低需求
- PHP 8.0 (目前沒有使用 php-7.4 實測過,所以希望至少可以 8.0 以上)
- Composer
- Docker、Docker-Compose
- Nginx
- PHP 8.1
- MariaDB
Docker
使用 Docker 的原因很簡單,因為可以將自己想要的服務配置寫在一個yml檔,並用一個指令達成開啟 / 關閉的功能,再者,我的系統是 Windows,對於開發者而言其實安裝環境不是特別友善。
我們可以在專案目錄下建立一個
docker-compose.yml
,並建立一個 docker 資料夾創建鏡像所需配置以利於打包。
docker-compose.yml
1 | version: "3.8" |
- 配置需要的 PHP 環境
docker/php/Dockerfile
1 | FROM php:8.1-fpm |
- 配置 Nginx,主要是藉由 Http Server 向 php-fpm 傳遞服務請求,並將入口文件定義在 index.php,以及 log 存放位置(不過這邊我沒有掛載log,需要可以去yml配置掛載路徑), fastcgi 的設定。
docker/nginx/default.conf
1 | server { |
- 配置DB Schema,目前主要建立一個 Example 作為本次實作資料庫,並且有一個 Users 表處理基本需求
init.sql
1 | CREATE DATABASE IF NOT EXISTS Example; |
- 以上這些步驟處理完,使用 Docker 建立服務的步驟就算完成了。
安裝相關依賴
- Compose 是 php 的套件管理器,它的功能就跟 Nodejs 的 Npm 是一樣的。請先在本機安裝 PHP、Compose 之後在 terminal 輸入以下安裝指令。
1 | # Medoo 是一個輕量化的 PHP ORM 函式庫,並且支援多種不同類型的 DB(sqlite、MSSQL、Oracle、PostgreSQL、Sybase),也是我最常使用的 ORM |
新增.gitignore,避免我們的 vendor、資料庫資料、敏感資訊上傳到 Github 上
.gitignore
1 | docker/data/ |
建構 Slim 4
環境配置(Settings)
本專案將所有配置文件都放到
config
資料夾內。首先可以建立
config/settings.php
當作我們的系統設定。這邊可以定義系統的基本設定(時區、除錯相關的配置),在 return array 之中可以設定你想要給系統的一些變數。
config/settings
1 |
|
- 在 Fogless 這篇文章有配置 DB 資訊、ERROR 顯示相關敏感訊息,但我認為如果提交到 Github 上可能會有安全性的風險,於是我才使用
symfony/dotenv
來讀取我們.env
的資訊,如果認為麻煩也可以直接略過。
.env
1 | # dev/prod/stage/test |
依賴注入容器(Container)
- 其實這個專有名詞有涉及三種相關的概念,分別是:
- 依賴注入(Denpendency Injection)
- 依賴注入容器(DIC,Dependency Injection Container)
- 自動配裝(Autowiring)
依賴注入
簡單來說,當我們在 OOP 設計的時候,常常需要使用到其他類的相關功能,但是在類中直接實例化會導致兩個類之間存在著強耦合(Strong Coupling)的關係,然而這樣維護程式碼的時候一個修改可能導致其他依賴類受到嚴重的影響,造成程式碼不易維護的情況。
這邊我們以手槍類和子彈類作為我們的例子。
1 |
|
- 沒有依賴注入流程:
- Class Gun 實例化
- Class Gun 調用 shoot 方法
- 發現需要必須要有 Class Bullet_2_34
- Class Bullet_2_34 實例化
- Class Bullet_2_34 調用 load 方法
- Class Bullet_2_34 echo “填彈”
- Class Gun echo “射擊”
1 |
|
- 使用依賴注入流程:
- Class Gun 發現需要必須要有 Class Bullet_2_34
- Class Bullet_2_34 實例化
- Class Gun 實例化並且將 Class Bullet_2_34 注入
- Class Gun 調用 shoot 方法
- Class Bullet_2_34 調用 load 方法
- Class Bullet_2_34 echo “填彈”
- Class Gun echo “射擊”
- 上述流程可以發現,我們在實例化類的順序明顯遭到調換,從原本 Gun -> Bullet_2_34 相反變成 Bullet_2_34 -> Gun,這種模式我們又稱為控制反轉(Inversion of Control),這樣們如果我們的 Gun 想要換不同彈徑的 Bullet,可以直接新增一個新的 Bullet_5_56(5.56mm) 類注入 Gun 類之中,不用去修改原本 Bullet_2_34 類的程式碼(可能有其他不同的 Gun 類需要)。
依賴注入容器
- 從依賴注入的例子我們可以發現,每次對它類有依賴的時候我們需要提前實例化該類並注入,那麼是不是就需要有一個容器來存放當作一個管理工具呢?依賴注入容器就為此而生了。
1 |
|
- 我們只需要在容器內註冊所有類之後,就可以更好的為我們實現依賴注入了。
自動配裝
- 自動配裝這個機制的產生是因為,我們在使用依賴注入的時候直接將容器給注入到我們的類中,以下是 Slim 3 的範例程式碼。
1 |
|
- 一般我們稱這種情況叫做反面模式(Anti-pattern),其中違反了 SOLID 原則中的依賴反轉。
- 慶幸的是,Slim 4 解決的這個尷尬的局面,引用符合 PSR-11 標準的 PHP-DI,它會讓容器可以自動創建和注入依賴類的的能力。所以我們只要在建構子(__construct)中顯示聲明依賴類,那麼依賴注入容器就會自動幫你創建和注入好了。
1 |
|
容器配置
- 講了那麼多容器肯定看到頭都暈了,接下來我們建立
config/container.php
把我們需要的依賴都寫進去settings
: 一開始的設定,有其他需求的人可以再自行定義Dotenv::class
: 這是我們讀取.env
的一個函式庫App::class
: 將我們 SLIM 應用注入容器(參考 Dependency Injection in Slim 4)BasePathMiddleware::class
: Basepath 這個中間件幫我們自動註冊好路徑,不用再額外設定(不使用的話就要自行依據資料夾路徑來定義)Medoo::class
: 這是一個輕量的 PHP ORM 函式庫,$_ENV是我們.env
裡面定義的變數
config/container.php
1 |
|
中間件(Middleware)
我們可以把向 SERVER 的 HTTP 請求想像成一個正要出國的人,SERVER 就是我們要入境的國家,那麼每當我們出入海關的時候是不是就要示出我們的護照(COOKIE、TOKEN…),來證明自己的來自哪個國家,可以避免任何危險人物入境。
中間件也是扮演著類似的角色,無論我們要向 SERVER 做什麼請求,在處理請求之前可以同步預先額外處理或驗證,以便我們路由後續的業務能夠更加順利。像是有的中間件就是用於權限的限制,我們則必須攜帶 SERVER 分發的 COOKIE 或 TOKEN 證明自己的身分。
以下是 SLIM 4 官方的範例程式碼,可以看到使用
$app->add()
的方式去替返回的 Body 增加前綴和後綴。
1 |
|
- 這是把我們把路由前綴做 Group 劃分,並針對該 Group 去做額外處理的範例。
1 |
|
- 雖然目前只是初步構建我們的專案,不太需要複雜的處理,所以我們就在
config/middleware.php
新增基本的設定就好,主要是配置好我們解析 Body 的功能、將中間件插入我們的路由、配置 BASEPATH、錯誤處理。
config/middleware.php
1 |
|
路由(Routes)
我們可以把 HTTP 請求方法分成五種,分別是
GET(獲取資源)
、POST(新增資源)
、PATCH(更新,修改資源的部分內容)
、PUT(更新,通常做替換一個資源功能)
、DELETE(刪除資源)
,我們通常會根據我們需要的操作去提交對應的方法,而如果要讓 SERVER 知道我們要操作什麼資源,就必須提供URI(Uniform Resource Identifier,範例:”/api/user”)才可以讓 SERVER 知道我們想要什麼服務。舉個例子,假設我們今天要去郵局存錢,可是郵局就有儲匯、郵務、保險這幾種服務(URI),那麼我就應該選擇儲匯業務(“/api/money”)並跟櫃台服務人員說我要存(POST)多少({“money”: 2000}),但是進入郵局一開始要先跟櫃台領取號碼牌(中間件,rate limiter)跟著其他人排隊,最後櫃台服務人員就會根據你的資料(TOKEN)來判斷你的存錢服務是否成功。
1 |
|
- 看完解釋我們可以清楚地了解到路由就是 RESTful API 提供服務的最基本要素,所以我們必須建立
config/routes.php
,來新增最基礎的路由。
config/routes.php
1 |
|
啟動(Bootstrap)
- 剛剛我們把 SLIM 4 最基本的要素都建立好了,那我們來將那些整合到我們的啟動
config/bootstrap
之中。
這個啟動可以直接在測試中直接引入,進行整合或單元測試。
config/bootstrap.php
1 |
|
入口文件(Index)
- 寫好啟動後,就可以在我們的入口文件
public/index.php
直接引入並且執行。
public/index.php
1 |
|
開啟服務
- 當我們的 SLIM 應用都配置好的時候,可以直接在專案路徑下的命令行執行 docker,然後可以使用 postman 查看是否有沒有返回請求。
1 | docker-compose up -d |
基本應用
Hello World
- 除了固定地返回方式之外,URI 中可以
配置參數($args)
,來動態獲取我們需要的資源。
其他路由更多的用法可以參考官方文檔。
config/routes.php
1 |
|
成果展示
SQL CRUD
- 首先我們先在
config/bootstrap.php
獲取我們設定好的Medoo
,將它傳遞到 routes 的函數中,以利我們使用資料庫操作。
config/bootstrap.php
1 |
|
簡單撰寫一下資料庫請求的 API。
請注意當我們要將
$db
傳遞進去我們的閉包函數時,我們必須使用到use ($db)
才能使用。
Medoo 使用方法相對 PDO 簡便,如果有更進階的需求可以參考官方文檔
config/routes.php
1 |
|
成果展示
- 順序:新增 -> 更新 -> 獲取 -> 刪除 -> 獲取。
結語
當前目錄
1 | . |
未來工作
由於目前我們學會配置基本的設定操作,如果是一般精簡的微服務,可以照接下來的模式進行開發,但是我希望能夠藉由物件導向的概念,將它設計成一個 ADR模式的框架。
下一篇(進階篇),我將會新增
表單驗證 Class
、使用者登入 Api
、Jwt Class 封裝/加入中間件
、Response 統一格式 Class
等相關功能。
非常感謝 Fogless 的Slim 4 搭建 RESTful API 和 Daniel Opitz 的Slim 4 - Tutorial,讓我能夠站在巨人的肩膀上開發 SLIM 4 的應用,本文範例程式碼在 GitHub 上的 POABOB/Slim-Simple。