Controller(控制器)在 MVC 中承擔「請求協調者」角色:接收請求 → 驗證/預處理 → 調用業務邏輯 → 組裝響應。早期 PHP 框架(CodeIgniter 1.x、ThinkPHP 3.x)採用「URL 直接映射方法」:`/user/list` → `UserController::list()`。隨著業務複雜度提升,權限驗證、日志記錄、限流、參數轉換、異常捕獲等橫切關注點在每個 Action 中重複,導致控制器臃腫、難測試、難復用。現代框架(Laravel、Symfony、Slim、Hyperf)演進出中間件管道、依賴注入、Action 類、資源控制器等模式。
<?php
// index.php 入口
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$routes = [
'/user/list' => ['UserController', 'list'],
'/user/add' => ['UserController', 'add'],
];
if (isset($routes[$uri])) {
[$class, $method] = $routes[$uri];
(new $class())->$method();
} else {
http_response_code(404);
echo 'Not Found';
}
缺陷:路由表膨脹、無參數綁定、無前置/後置鉤子、控制器職責不清。
<?php
class Controller {
protected $request;
public function __construct() {
$this->request = Request::instance();
}
// 鉤子方法,子類可選重寫
protected function _initialize() {} // 前置
protected function _beforeAction($action) { return true; } // 返回 false 阻止
protected function _afterAction($action) {} // 後置
protected function _empty() { http_response_code(404); } // 兜底
}
class UserController extends Controller {
protected function _initialize() {
// 統一權限檢查
if (!Session::has('user_id')) $this->redirect('/login');
}
protected function _beforeAction($action) {
if ($action === 'delete' && !$this->request->isPost()) return false;
return true;
}
public function list() { /* ... */ }
public function add() { /* ... */ }
}
進步:邏輯復用、生命周期鉤子。局限:繼承耦合、無法組合多個橫切關注點(如「權限+限流+日志」需多層繼承或繁瑣調用)。
核心思想:請求處理 = 洋蔥模型。每個中間件是一層,包裹下一層,請求向內傳遞,響應向外回溯。
<?php
// PSR-15 標準接口
interface MiddlewareInterface {
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
}
// 實現:認證中間件
class AuthMiddleware implements MiddlewareInterface {
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (!$request->getAttribute('user_id')) {
return new RedirectResponse('/login');
}
return $handler->handle($request); // 傳遞給下一層
}
}
// 實現:日志中間件
class LoggingMiddleware implements MiddlewareInterface {
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
$start = microtime(true);
$response = $handler->handle($request);
$duration = microtime(true) - $start;
error_log("{$request->getMethod()} {$request->getUri()} -> {$response->getStatusCode()} ({$duration}s)");
return $response;
}
}
// Pipeline 構建器(簡化版)
class Pipeline {
private array $middlewares = [];
public function pipe(MiddlewareInterface $middleware): self {
$this->middlewares[] = $middleware;
return $this;
}
public function handle(ServerRequestInterface $request): ResponseInterface {
$handler = new class implements RequestHandlerInterface {
public function handle(ServerRequestInterface $request): ResponseInterface {
// 核心:路由分發 → 執行 Controller Action
return (new Router())->dispatch($request);
}
};
// 從最後一個中間件向前包裹
foreach (array_reverse($this->middlewares) as $middleware) {
$handler = new class($middleware, $handler) implements RequestHandlerInterface {
public function __construct(private MiddlewareInterface $mw, private RequestHandlerInterface $next) {}
public function handle(ServerRequestInterface $request): ResponseInterface {
return $this->mw->process($request, $this->next);
}
};
}
return $handler->handle($request);
}
}
// 使用:組合管道
$app = new Pipeline();
$app->pipe(new LoggingMiddleware())
->pipe(new CorsMiddleware())
->pipe(new RateLimitMiddleware(100, 60))
->pipe(new AuthMiddleware())
->pipe(new CsrfMiddleware());
$response = $app->handle(ServerRequest::fromGlobals());
$response->send();
優勢:組合大於繼承、單一職責、可測試、可復用、順序可配置、支持異步(Swoole/Hyperf)。
將每個「用例」拆分為獨立類,控制器僅作為路由入口分發器:
<?php
// 路由定義(屬性路由 / PHP 8 Attribute)
#[Route('/api/users', methods: ['GET'])]
class ListUsersAction {
public function __construct(
private UserRepository $repo,
private UserListPresenter $presenter
) {}
public function __invoke(ServerRequestInterface $request): ResponseInterface {
$page = (int)($request->getQueryParams()['page'] ?? 1);
$perPage = (int)($request->getQueryParams()['per_page'] ?? 15);
$users = $this->repo->paginate($page, $perPage);
return $this->presenter->present($users);
}
}
#[Route('/api/users', methods: ['POST'])]
class CreateUserAction {
public function __construct(
private UserCreator $creator,
private UserPresenter $presenter
) {}
public function __invoke(ServerRequestInterface $request): ResponseInterface {
$data = json_decode($request->getBody()->getContents(), true);
$user = $this->creator->create($data);
return $this->presenter->present($user, 201);
}
}
// 依賴注入容器自動解析構造函數參數
// 路由器根據 HTTP Method + URI 分發到對應 Action 類
// 中間件管道在 Action 之前執行
原文提供的一種極簡「閉包鏈」控制器骨架:
<?php
function boot(callable $func): void {
$context = [];
while ($func = $func($context)) {
; // 繼續執行返回的下一個閉包
}
}
boot(function (&$context) {
$context[] = 'A'; // 步驟 1
return function (&$context) {
$context[] = 'B'; // 步驟 2
return function (&$context) {
$context[] = 'C'; // 步驟 3
return function (&$context) {
var_dump($context); // 終點:輸出 ['A','B','C']
};
};
};
});
這本質是手動實現的中間件鏈:每個閉包接收上下文、執行邏輯、返回下一個閉包。`boot` 驅動鏈條直到返回 `null`。雖然巧妙,但可讀性差、調試難、異常處理弱、無類型提示,不建議生產使用,僅作為理解「請求管道」概念的教學演示。
從「URL 映射方法」到「中間件管道 + Action 類」,PHP 控制器模式經歷了去耦合、職責分離、標準化、異步化四次質變。理解這條演進線,能幫助你在維護老項目、選型新框架、設計自有架構時做出清醒決策:**控制器只是 HTTP 邊界的薄適配層,真正的業務價值在 Domain 層**。