路由

概述

理解

基本用法

404 未找到处理程序

默认情况下,如果找不到 URL,Flight 将发送一个非常简单且朴素的 HTTP 404 Not Found 响应。 如果您想要更自定义的 404 响应,您可以 映射 自己的 notFound 方法:

Flight::map('notFound', function() {
    $url = Flight::request()->url;
    // 您也可以使用 Flight::render() 与自定义模板。
    $output = <<<HTML
        <h1>My Custom 404 Not Found</h1>
        <h3>The page you have requested {$url} could not be found.</h3>
        HTML;
    $this->response()
        ->clearBody()
        ->status(404)
        ->write($output)
        ->send();
});

方法未找到处理程序

默认情况下,如果找到 URL 但方法不允许,Flight 将发送一个非常简单且朴素的 HTTP 405 Method Not Allowed 响应(例如:方法不允许。允许的方法是:GET, POST)。它还将包含一个 Allow 标头,带有该 URL 的允许方法。

如果您想要更自定义的 405 响应,您可以 映射 自己的 methodNotFound 方法:

use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
    $url = Flight::request()->url;
    $methods = implode(', ', $route->methods);
    // 您也可以使用 Flight::render() 与自定义模板。
    $output = <<<HTML
        <h1>My Custom 405 Method Not Allowed</h1>
        <h3>The method you have requested for {$url} is not allowed.</h3>
        <p>Allowed Methods are: {$methods}</p>
        HTML;
    $this->response()
        ->clearBody()
        ->status(405)
        ->setHeader('Allow', $methods)
        ->write($output)
        ->send();
});

高级用法

路由中的依赖注入

如果您想通过容器(PSR-11、PHP-DI、Dice 等)使用依赖注入,则唯一可用的路由类型是直接自己创建对象并使用容器创建您的对象,或者使用字符串定义要调用的类和方法。您可以转到 依赖注入 页面获取更多信息。

以下是一个快速示例:


use flight\database\PdoWrapper;
// Greeting.php
class Greeting
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    public function hello(int $id) {
        // 使用 $this->pdoWrapper 做些什么
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "Hello, world! My name is {$name}!";
// index.php
// 使用所需的任何参数设置容器
// 请参阅依赖注入页面以获取有关 PSR-11 的更多信息
$dice = new \Dice\Dice();
// 不要忘记使用 '$dice = ' 重新赋值变量!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
// 注册容器处理程序
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
// 像往常一样定义路由
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
Flight::route('/hello/@id', 'Greeting->hello');
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();

检查路由信息

如果您想检查匹配的路由信息,有两种方式可以做到:

  1. 您可以使用 Flight::router() 对象上的 executedRoute 属性。
  2. 您可以通过在路由方法中将第三个参数传递为 true 来请求将路由对象传递到您的回调。路由对象将始终作为传递到回调函数的最后一个参数。

executedRoute

Flight::route('/', function() {
  $route = Flight::router()->executedRoute;
  // 使用 $route 做些什么
  // 匹配的 HTTP 方法数组
  $route->methods;
  // 命名参数数组
  $route->params;
  // 匹配的正则表达式
  $route->regex;
  // 包含 URL 模式中任何 '*' 的内容
  $route->splat;
  // 显示 URL 路径...如果您真的需要它
  $route->pattern;
  // 显示分配给此的中介软件
  $route->middleware;
  // 显示分配给此路由的别名
  $route->alias;

注意: executedRoute 属性仅在路由执行后设置。如果您在路由执行前尝试访问它,将为 NULL。您也可以在 中间件 中使用 executedRoute!

在路由定义中传递 true

Flight::route('/', function(\flight\net\Route $route) {
  // 匹配的 HTTP 方法数组
  $route->methods;
  // 命名参数数组
  $route->params;
  // 匹配的正则表达式
  $route->regex;
  // 包含 URL 模式中任何 '*' 的内容
  $route->splat;
  // 显示 URL 路径...如果您真的需要它
  $route->pattern;
  // 显示分配给此的中介软件
  $route->middleware;
  // 显示分配给此路由的别名
  $route->alias;
}, true);// <-- 这个 true 参数就是让它发生的原因

路由分组和中间件

有时您可能想将相关路由分组在一起(例如 /api/v1 )。 您可以通过使用 group 方法来实现:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // 匹配 /api/v1/users
  Flight::route('/posts', function () {
    // 匹配 /api/v1/posts
});

您甚至可以嵌套组的组:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get() 获取变量,它不设置路由!请参阅下面的对象上下文
    Flight::route('GET /users', function () {
      // 匹配 GET /api/v1/users
    Flight::post('/posts', function () {
      // 匹配 POST /api/v1/posts
    Flight::put('/posts/1', function () {
      // 匹配 PUT /api/v1/posts
  Flight::group('/v2', function () {
    // Flight::get() 获取变量,它不设置路由!请参阅下面的对象上下文
    Flight::route('GET /users', function () {
      // 匹配 GET /api/v2/users
});

使用对象上下文的分组

您仍然可以使用以下方式与 Engine 对象一起使用路由分组:

$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
  // 使用 $router 变量
  $router->get('/users', function () {
    // 匹配 GET /api/v1/users
  $router->post('/posts', function () {
    // 匹配 POST /api/v1/posts

注意: 这是使用 $router 对象定义路由和组的首选方法。

使用中间件的分组

您也可以为路由组分配中间件:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // 匹配 /api/v1/users
}, [ MyAuthMiddleware::class ]); // 或 [ new MyAuthMiddleware() ] 如果您想使用实例

请参阅 组中间件 页面的更多细节。

别名基
Only 和 Except
中间件

流式响应

您现在可以使用 stream() streamWithHeaders() 向客户端流式传输响应。 这对于发送大文件、长时间运行的进程或生成大响应很有用。 流式传输路由的处理方式与常规路由略有不同。

注意: 流式响应仅在您将 flight.v2.output_buffering 设置为 false 时可用。

手动标头的流式传输

您可以通过在路由上使用 stream() 方法向客户端流式传输响应。如果您 这样做,您必须在向客户端输出任何内容之前手动设置所有标头。 这是使用 header() php 函数或 Flight::response()->setRealHeader() 方法完成的。

Flight::route('/@filename', function($filename) {
    $response = Flight::response();
    // 显然您会清理路径什么的。
    $fileNameSafe = basename($filename);
    // 如果您在路由执行后有额外的标头要设置
    // 您必须在回显任何内容之前定义它们。
    // 它们必须全部是 header() 函数的原始调用或 
    // Flight::response()->setRealHeader() 的调用
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    $response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    $filePath = '/some/path/to/files/'.$fileNameSafe;
    if (!is_readable($filePath)) {
        Flight::halt(404, 'File not found');
    // 如果您愿意,手动设置内容长度
    header('Content-Length: '.filesize($filePath));
    $response->setRealHeader('Content-Length: '.filesize($filePath));
    // 以读取的方式将文件流式传输到客户端
    readfile($filePath);
// 这是这里的魔法行
})->stream();

带标头的流式传输

您也可以使用 streamWithHeaders() 方法在开始流式传输之前设置标头。

Flight::route('/stream-users', function() {
    // 您可以在这里添加任何额外的标头
    // 您只需使用 header() 或 Flight::response()->setRealHeader()
    // 无论您如何拉取数据,仅作为示例...
    $users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
    echo '{';
    $user_count = count($users);
    while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
        echo json_encode($user);
        if(--$user_count > 0) {
            echo ',';
        // 这是在将数据发送到客户端时必需的
        ob_flush();
    echo '}';
// 这是您在开始流式传输之前设置标头的方式。
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // 可选状态代码,默认 200
    'status' => 200
]);

另请参阅

  • 中间件 - 使用中间件与路由进行身份验证、日志记录等。
  • 依赖注入 - 简化路由中的对象创建和管理。
  • 为什么使用框架? - 理解使用像 Flight 这样的框架的好处。
  • 扩展 - 如何使用自己的功能扩展 Flight,包括 notFound 方法。
  • php.net: preg_match - PHP 用于正则表达式匹配的函数。
  • 路由参数按顺序匹配,而不是按名称。确保回调参数顺序与路由定义匹配。
  • 使用 Flight::get() 不会定义路由;对于路由,请使用 Flight::route('GET /...') 或组中的 Router 对象上下文(例如 $router->get(...) )。
  • executedRoute 属性仅在路由执行后设置;在执行前为 NULL。
  • 流式传输需要禁用遗留 Flight 输出缓冲功能( flight.v2.output_buffering = false )。
  • 对于依赖注入,只有某些路由定义支持基于容器的实例化。

更新日志