路由

Slim 框架的路由建立在FastRoute组件之上,它速度快、十分稳定。虽然我们使用此组件来完成所有路由,但该应用程序的核心已完全脱离它,且已部署接口以便使用其他路由库。

如何创建路由

可以在Slim\App实例上使用代理方法来定义应用程序路由。Slim 框架为大部分常用 HTTP 方法提供了方法。

GET 路由

可以使用 Slim 应用程序的get()方法添加仅处理GETHTTP 请求的路由。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->get('/books/{id}', function ($request, $response, array $args) {
    // Show book identified by $args['id']
});

POST 路由

可以使用 Slim 应用程序的post()方法添加仅处理POSTHTTP 请求的路由。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->post('/books', function ($request, $response, array $args) {
    // Create new book
});

PUT 路由

可以使用 Slim 应用程序的put()方法添加仅处理PUTHTTP 请求的路由。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->put('/books/{id}', function ($request, $response, array $args) {
    // Update book identified by $args['id']
});

DELETE 路由

你可以添加一个仅处理 DELETE HTTP 请求的路由,使用 Slim 应用程序的 delete() 方法。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->delete('/books/{id}', function ($request, $response, array $args) {
    // Delete book identified by $args['id']
});

OPTIONS 路由

你可以添加一个仅处理 OPTIONS HTTP 请求的路由,使用 Slim 应用程序的 options() 方法。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->options('/books/{id}', function ($request, $response, array $args) {
    // Return response headers
});

PATCH 路由

你可以添加一个仅处理 PATCH HTTP 请求的路由,使用 Slim 应用程序的 patch() 方法。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->patch('/books/{id}', function ($request, $response, array $args) {
    // Apply changes to book identified by $args['id']
});

任何路由

你可以添加一个处理所有 HTTP 请求方法的路由,使用 Slim 应用程序的 any() 方法。它接受两个参数

  1. 路由模式(可包括命名占位符)
  2. 路由回调
$app->any('/books/[{id}]', function ($request, $response, array $args) {
    // Apply changes to books or book identified by $args['id'] if specified.
    // To check which method is used: $request->getMethod();
});

注意,第二个参数是回调。你可以指定一个类,它实现了 __invoke() 方法,而不是一个闭包。然后,你可以在其他位置进行映射

$app->any('/user', 'MyRestfulController');

自定义路由

你可以添加一个处理多个 HTTP 请求方法的路由,使用 Slim 应用程序的 map() 方法。它接受三个参数

  1. HTTP 方法数组
  2. 路由模式(可包括命名占位符)
  3. 路由回调
$app->map(['GET', 'POST'], '/books', function ($request, $response, array $args) {
    // Create new book or list all books
});

路由回调

上面描述的每个路由方法将其最终参数接受为回调例程。此参数可以是任何 PHP 可调用对象,默认情况下它接受三个参数。

  • Request 第一个参数是 Psr\Http\Message\ServerRequestInterface 对象,表示当前的 HTTP 请求。
  • Response 第二个参数是 Psr\Http\Message\ResponseInterface 对象,表示当前的 HTTP 响应。
  • Arguments 第三个参数是一个关联数组,它包含当前路由的命名占位符的值。

编写内容以响应

有两种方法可以编写内容以响应 HTTP

  1. 使用响应对象的 $response->getBody()->write('my content'); 方法。

  2. echo() 路由回调中的内容。如果你添加了 输出缓冲中间件,此内容将被追加或预先添加到当前的 HTTP 响应对象。

请注意,从 Slim 4 开始,你必须返回一个 Psr\Http\Message\ResponseInterface 对象。

闭包绑定

如果你使用 依赖容器Closure 实例作为路由回调,则 closure 的状态绑定到 Container 实例。这意味着你将有权访问 Closure 内部 的 DI 容器实例,方式是通过 $this 关键字

$app->get('/hello/{name}', function ($request, $response, array $args) {
    // Use app HTTP cookie service
    $this->get('cookies')->set('name', [
        'value' => $args['name'],
        'expires' => '7 days'
    ]);
});
提示!

Slim 不支持 static closure。

重定向帮助程序

使用 Slim 应用程序的 redirect() 方法,你可以增加重定向 GET HTTP 请求到不同 URL 的路由。该方法接受三个参数

  1. 重定向 from 的路由模式(带可选命名占位符)
  2. 重定向 to 的位置,该位置可以是 stringPsr\Http\Message\UriInterface
  3. 要使用的 HTTP 状态代码(可选;如果未设置则为 302
$app->redirect('/books', '/library', 301);

redirect() 路由响应请求的状态代码,并将 Location 标头设置为第二个参数

路由策略

路由回调签名由路由策略决定。默认情况下,Slim 期待路由回调接受请求、响应以及一个路由占位符参数数组。这称为 RequestResponse 策略。然而,你只需使用不同的策略即可更改预期的路由回调签名。比如,Slim 提供替代策略,该策略名为 RequestResponseArgs,它接受请求和响应,以及每个路由占位符作为一个独立的参数

以下是使用此替代策略的一个示例

<?php
use Slim\Factory\AppFactory;
use Slim\Handlers\Strategies\RequestResponseArgs;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

/**
 * Changing the default invocation strategy on the RouteCollector component
 * will change it for every route being defined after this change being applied
 */
$routeCollector = $app->getRouteCollector();
$routeCollector->setDefaultInvocationStrategy(new RequestResponseArgs());

$app->get('/hello/{name}', function ($request, $response, $name) {
    $response->getBody()->write($name);
    
    return $response;
});

你也可以在每个路由的基础上设置不同的调用策略

<?php
use Slim\Factory\AppFactory;
use Slim\Handlers\Strategies\RequestResponseArgs;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();
$routeCollector = $app->getRouteCollector();

$route = $app->get('/hello/{name}', function ($request, $response, $name) {
    $response->getBody()->write($name);
    
    return $response;
});
$route->setInvocationStrategy(new RequestResponseArgs());

通过实现 Slim\Interfaces\InvocationStrategyInterface 即可提供你自己的路由策略。

路由占位符

上面描述的每个路由方法接受与当前 HTTP 请求 URI 相匹配的 URL 模式。路由模式可以使用命名占位符动态匹配 HTTP 请求 URI 段

格式

路由模式占位符以 { 开头,后跟占位符名称,以 } 结束。这是一个名为 name 的示例占位符

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
// ...

$app->get('/hello/{name}', function (ServerRequestInterface $request, ResponseInterface $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");
    
    return $response;
});

可选段

要让一部分可选项,只需用方括号括起来

$app->get('/users[/{id}]', function ($request, $response, array $args) {
    // responds to both `/users` and `/users/123`
    // but not to `/users/`
    
    return $response;
});

通过嵌套可以支持多重可选参数

$app->get('/news[/{year}[/{month}]]', function ($request, $response, array $args) {
    // responds to `/news`, `/news/2016` and `/news/2016/03`
    // ...
    
    return $response;
});

对于“无限”可选参数,你可以这样做

$app->get('/news[/{params:.*}]', function ($request, $response, array $args) {
    // $params is an array of all the optional segments
    $params = explode('/', $args['params']);
    // ...
    
    return $response;
});

在这个示例中,URI /news/2016/03/20 的结果是 $params 数组包含三个元素:['2016', '03', '20']

正则表达式匹配

默认情况下占位符中写有 {},并且可以接受任何值。但是,占位符也可以要求 HTTP 请求的 URI 与特定正则表达式匹配。如果当前的 HTTP 请求 URI 与占位符的正则表达式不匹配,将不会调用路由。这是名为 id 的占位符示例,它需要一个或多个数字。

$app->get('/users/{id:[0-9]+}', function ($request, $response, array $args) {
    // Find user identified by $args['id']
    // ...
    
    return $response;
});

路由名称

可以为应用程序路由指定一个名称。如果您希望使用 RouteParser 的 urlFor() 方法按编程方式生成特定路由的 URL,这非常有用。上面描述的每个路由方法均返回一个 Slim\Route 对象,而该对象会公开一个 setName() 方法。

$app->get('/hello/{name}', function ($request, $response, array $args) {
    $response->getBody()->write("Hello, " . $args['name']);
    return $response;
})->setName('hello');

您可以使用应用程序 RouteParser 的 urlFor() 方法为命名的路由生成 URL。

$routeParser = $app->getRouteCollector()->getRouteParser();
echo $routeParser->urlFor('hello', ['name' => 'Josh'], ['example' => 'name']);

// Outputs "/hello/Josh?example=name"

RouteParser 的 urlFor() 方法接受三个参数

  • $routeName 路由名称。可以通过 $route->setName('name') 设置路由的名称。路由映射方法返回 Route 的一个实例,因此您可以在映射路由后直接设置名称。例如:$app->get('/', function () {...})->setName('name')
  • $data 路由模式占位符和替换值的关联数组。
  • $queryParams 要附加到生成的 URL 的查询参数的关联数组。

路由组

为将路由组织成逻辑组提供帮助,Slim\App 还提供了 group() 方法。每个组的路由模式都预先加到该组中包含的路由或组前面,并且组模式中的任何占位符参数最终都会提供给嵌套的路由

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('/users/{id:[0-9]+}', function (RouteCollectorProxy $group) {
    $group->map(['GET', 'DELETE', 'PATCH', 'PUT'], '', function ($request, $response, array $args) {
        // Find, delete, patch or replace user identified by $args['id']
        // ...
        
        return $response;
    })->setName('user');
    
    $group->get('/reset-password', function ($request, $response, array $args) {
        // Route for /users/{id:[0-9]+}/reset-password
        // Reset the password for user identified by $args['id']
        // ...
        
        return $response;
    })->setName('user-password-reset');
});

组模式可以是空模式,从而能够对不共享公共模式的路由进行逻辑分组。

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('', function (RouteCollectorProxy $group) {
    $group->get('/billing', function ($request, $response, array $args) {
        // Route for /billing
        return $response;
    });
    
    $group->get('/invoice/{id:[0-9]+}', function ($request, $response, array $args) {
        // Route for /invoice/{id:[0-9]+}
        return $response;
    });
})->add(new GroupMiddleware());

注意,在组闭包内部,Slim 将闭包绑定到容器实例。

  • 在路由闭包内部,$this 绑定到 Psr\Container\ContainerInterface 实例

路由中间件

您还可以将中间件附加到任何路由或路由组。

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('/foo', function (RouteCollectorProxy $group) {
    $group->get('/bar', function ($request, $response, array $args) {
        // ...
        return $response;
    })->add(new RouteMiddleware());
})->add(new GroupMiddleware());

路由表达式缓存

可以通过 RouteCollector::setCacheFile() 启用路由器缓存。请参见以下示例。

<?php
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

/**
 * To generate the route cache data, you need to set the file to one that does not exist in a writable directory.
 * After the file is generated on first run, only read permissions for the file are required.
 *
 * You may need to generate this file in a development environment and committing it to your project before deploying
 * if you don't have write permissions for the directory where the cache file resides on the server it is being deployed to
 */
$routeCollector = $app->getRouteCollector();
$routeCollector->setCacheFile('/path/to/cache.file');

容器解析

您不必为路由定义一个函数。在 Slim 中,还有几种不同的方式可以定义路由操作函数。

除一个函数外,您还可以使用

  • container_key:method
  • Class:method
  • 实现 __invoke() 方法的类
  • container_key

此功能是由Slim的Callable Resolver类启用的。它将字符串条目转换为函数调用。示例

$app->get('/', '\HomeController:home');

或者,您还可以利用PHP的::class运算符,它适用于IDE查找系统,并生成相同的结果

$app->get('/', \HomeController::class . ':home');

您还可以传递一个数组,其第一个元素将包含类名,第二个元素将包含调用的方法名

$app->get('/', [\HomeController::class, 'home']);

在上面的代码中,我们定义了一个/路由,并告诉Slim执行HomeController类上的home()方法。

Slim首先在容器中查找HomeController条目,如果找到该条目,它将使用该实例,否则它将使用容器作为第一个参数调用其构造函数。创建类实例后,它将使用您定义的任何策略调用指定的方法。

使用容器注册控制器

使用home操作方法创建控制器。构造函数应该接受所需的依赖关系。例如

<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;

class HomeController
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }
    
    public function home(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
      // your code here
      // use $this->view to render the HTML
      // ...
      
      return $response;
    }
}

在使用依赖关系实例化控制器的容器中创建一个工厂

use Psr\Container\ContainerInterface;
// ...

$container = $app->getContainer();

$container->set(\HomeController::class, function (ContainerInterface $container) {
    // retrieve the 'view' from the container
    $view = $container->get('view');
    
    return new HomeController($view);
});

这允许您利用容器进行依赖项注入,以便您可以将特定依赖项注入控制器。

允许Slim实例化控制器

或者,如果类在容器中没有条目,那么Slim将把容器的实例传递给构造函数。您可以使用许多操作而不是仅处理一个操作的可调用类来构建控制器。

<?php

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class HomeController
{
   private $container;

   // constructor receives container instance
   public function __construct(ContainerInterface $container)
   {
       $this->container = $container;
   }

   public function home(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
   {
        // your code to access items in the container... $this->container->get('');
        
        return $response;
   }

   public function contact(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
   {
        // your code to access items in the container... $this->container->get('');
        
        return $response;
   }
}

您可以使用您的控制器方法,如下所示。

$app->get('/', \HomeController::class . ':home');
$app->get('/contact', \HomeController::class . ':contact');

使用可调用类

您不必在路由可调用函数中指定一个方法,只可以将它设置为一个可调用的类,例如

<?php

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class HomeAction
{
   private $container;

   public function __construct(ContainerInterface $container)
   {
       $this->container = $container;
   }

   public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
   {
        // your code to access items in the container... $this->container->get('');
        
        return $response;
   }
}

您可以使用此类,如下所示。

$app->get('/', \HomeAction::class);

再次,与控制器一样,如果您使用容器注册类名,则可以创建一个工厂,并将您需要的特定依赖项注入到操作类中。

路由对象

有时在中间件中,您需要路由的参数。

在此示例中,我们首先检查用户是否已登录,其次检查用户是否有权查看他们尝试查看的特定视频。

$app->get('/course/{id}', Video::class . ':watch')
    ->add(PermissionMiddleware::class);
<?php

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext;

class PermissionMiddleware
{
    public function __invoke(Request $request, RequestHandler $handler)
    {
        $routeContext = RouteContext::fromRequest($request);
        $route = $routeContext->getRoute();
        
        $courseId = $route->getArgument('id');
        
        // do permission logic...
        
        return $handler->handle($request);
    }
}

从路由中获得基本路径

要从路由中获取基本路径,只需执行以下操作

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteContext;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->get('/', function(Request $request, Response $response) {
    $routeContext = RouteContext::fromRequest($request);
    $basePath = $routeContext->getBasePath();
    // ...
    
    return $response;
});