前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spiral 详细上手指南之路由规则

Spiral 详细上手指南之路由规则

原创
作者头像
小李刀刀
修改2020-03-04 16:50:14
1.4K0
修改2020-03-04 16:50:14
举报
文章被收录于专栏:PHP 开发

在上一篇《Spiral 详细上手指南之安装与配置》中,我们已经基于官方的 WEB 项目模板创建了自己的本地项目 "myapp" 并且已经配置好了数据库连接和用于开发的进程参数。

通过这整个系列,最终将会开发完成一个简化版的博客 APP. 在这次的文章中,暂时不会涉及数据库操作和领域模型相关的开发,而是聚焦于 Spiral 框架的路由(route)和控制器(controller)部分。

实践目标

我们首先要为博客文章创建路由和控制器,包含以下的路由:

  • GET "/posts": 文章列表页
  • GET "/posts/<id>": 文章详情页
  • POST "/posts": 创建文章的 API
  • PUT "/posts": 保存文章修改的 API
  • DELETE "/posts/<id>": 删除文章的 API

这些路由都会指向我们创建的 PostController 控制器中的对应方法。

Spiral 路由绑定介绍

前文提到过,由 Spiral 的 WEB 项目模板创建的项目中,系统已经定义了两组路由规则:

  • /<action>.html 默认指向 HomeController 下对应的方法
  • /<controller>/<action> 指向对应的控制器和方法

两组路由都有默认值,controller 的默认值是 "HomeController",action 的默认值是 "index", 以上一节列出来要创建的路由为例,如果我们想另外定义路由,那么基于系统的默认路由,我们的路径会这样解析:

  • /blogs: 调用 BlogsControllerindex 方法(包括 GETPOSTPUTPATCHDELETE等所有动词都统一映射到这里)
  • /blogs/123: 无匹配

Spiral 的路由是不可变的,注册之后禁止修改,所以应该在引导程序中进行注册。我们项目下已经有一个专门负责注册路由的引导程序 RoutesBootloader,打开项目下的 app/src/Bootloader/RoutesBootloader.php 文件,就能看到系统默认注册的路由:

代码语言:txt
复制
namespace App\Bootloader;

use App\Controller\HomeController;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Router\Route;
use Spiral\Router\RouteInterface;
use Spiral\Router\RouterInterface;
use Spiral\Router\Target\Controller;
use Spiral\Router\Target\Namespaced;

class RoutesBootloader extends Bootloader
{
    /**
     * @param RouterInterface $router
     */
    public function boot(RouterInterface $router): void
    {
        // named route
        $router->addRoute(
            'html',
            new Route('/<action>.html', new Controller(HomeController::class))
        );

        // fallback (default) route
        $router->setDefault($this->defaultRoute());
    }

    /**
     * Default route points to namespace of controllers.
     *
     * @return RouteInterface
     */
    protected function defaultRoute(): RouteInterface
    {
        // handle all /controller/action like urls
        $route = new Route(
            '/[<controller>[/<action>]]',
            new Namespaced('App\\Controller')
        );

        return $route->withDefaults([
            'controller' => 'home',
            'action'     => 'index'
        ]);
    }
}

可以看到通过 RouterInterface 提供的 addRoute 方法来定义路由规则。这里要说明一下,addRoute 这个方法已经弃用,应该使用 setRoute 替代。如果你使用时官方的项目模板还没更新,我们可以自己修改一下:

代码语言:txt
复制
@@ -27,7 +27,7 @@ class RoutesBootloader extends Bootloader
     public function boot(RouterInterface $router): void
     {
         // named route
-        $router->addRoute(
+        $router->setRoute(
             'html',
             new Route('/<action>.html', new Controller(HomeController::class))
         );

RouterInterface 接口

Spiral 的路由规则是根据 PSR-15 规范来实现的,在任何一个引导程序中,我们都可以通过依赖 RouterInterface 这个接口,并借助它来注册新的路由规则。这个接口提供了以下方法:

  • setRoute(string $name, Spiral\Router\RouteInterface $route): void: 定义路由规则
  • setDefault(Spiral\Router\RouteInterface $route): void: 定义默认路由规则
  • getRoute(string $name): Spiral\Router\RouteInterface: 通过名称取回路由规则实例
  • getRoutes(): array: 取回所有已注册的路由规则集合
  • uri(string $name, array $parameters = []): Psr\Http\message\UriInterface: 生成 uri

可以看到其中setRoute 方法接受两个参数,第一个是字符串,指定路由的名称,第二个是 Spiral\Router\RouteInterface 接口的具体实现,在 Spiral 中 Spiral\Router\Route 类实现了这个接口,并且提供了一些方便使用的方法。

Route 类

RouteInterface 接口用来创建具体的路由规则,实现它的 Route 类的构造函数签名如下:

代码语言:txt
复制
/**
  * @param string $pattern 网址路径匹配模式
  * @param string|callable|RequestHandlerInterface|TargetInterface $target 可调用的路由目标
  * @param array $defaults 匹配模式参数的默认值
  */
public function __construct(string $pattern, $target, array $defaults = [])

可以看到,第一个参数是字符串,用来匹配网址,第二个参数是路由目标,我们上面用到的是 TargetInterface 类型,但 Spiral 遵循 PSR-15 规范,因此这个参数可以是任何一个实现 Psr\Http\Server\RequestHandlerInterface 接口的对象。比如直接用闭包函数来实现:

代码语言:txt
复制
new Route(
    '/<name>',
    function (ServerRequestInterface $request, ResponseInterface $response) {
        $response->getBody()->write("响应内容");

        return $response;
    }
)

但在实际项目中可能用得更多的是以下几种:

  • Spiral\Router\Target\Group: 控制器组(通常在 Restful API 中使用比较多)
  • Spiral\Router\Target\Controller: 控制器(之前被删掉的自带路由就是这种)
  • Spiral\Router\Target\Action: 控制器方法(我们前面添加的所有规则都是这种)
  • Spiral\Router\Target\Namespaced: 命名空间(系统自带的默认规则属于这种)

稍后会对这几种不同的路由目标分别介绍。在构造函数之外,Route 类还有几个比较常用的实例方法:

  • withDefaults(array $defaults): RouteInterface: 给路由设定参数默认值
  • withVerbs(string ...$verbs): RouteInterface: 指定路由可用的 HTTP 动词
  • withMiddleware(...$middleware): 给路由绑定中间件

所以如果需要让某个路由只用于特定的 HTTP 方法(动词),可以在创建了路由实例之后,用 withVerbs 方法实现:

代码语言:txt
复制
$route = new Route('/foo', new Controller('App\Controller\FooController'));
$route = $route->withVerbs('post', 'PUT'); // 动词不区分大小写

路由匹配顺序

Spiral 的路由是按照定义它们的先后顺序依次匹配,一旦匹配到任何一条规则,就不再向下。因此务必把更具体的匹配模式放到前面,否则就会失效,比如有两条匹配路径的顺序如下:

  • "/<action>"
  • "/blog"

如果按照这样的顺序定义路由,那么 "/blog" 这个路径就会被第一条 "/<action>" 规则匹配,而第二条规则永远不会被命中。

路由参数

在路径匹配模式字符串中,用[] 来指定可选参数,用<> 来指定参数,参数可以用 : 接正则表达式来接参数的格式,例如:

  • "/<controller>/<action>": 匹配 "/user/add", "/blog/view", "/article/list" 这样的路径,controller 和 action 都是必须的,缺少任何一个不会匹配
  • "/<controller>[/<action>]: 同上,但是这里 action 是可选参数,通常这种情况下需要为 action 指定默认值,不指定的话系统默认是 index
  • "/[<controller>[/<action>]]": 同上,但这里 controller 和 action 都是可选的,请注意两个可选参数是嵌套定义的
  • "/article/<action:list|add|save>": 这个匹配 "/article/list", "/article/add", "/article/save",在 ":" 后面可以直接列出允许的值,用 "|" 分隔
  • "/articles/<id:\d+>": 这个匹配 "/articles/1", "/articles/22" 这样的路径,id 参数限制必须是数字
  • "/posts[/<id:\d+>]": 这个匹配 "/posts", "/posts/222" 这样的路径,跟上一个的区别在于 id 是可选参数

路由指向控制器

如果要把一条路由规则指向具体的控制器,就可以用到上面提到的 Spiral\Router\Target\Controller 这个 target,例如:

代码语言:txt
复制
use Spiral\Router\Target\Controller;

$route = new Route(
    '/posts[/<action>[/<id:\d+>]', // 匹配模式
    new Controller(
        'App\Controller\PostController', // 目标控制器
        0, // 是否 Restful 风格(可选参数,默认值:0)
        "index" // 默认的 action,可选参数(默认值:"index")
    )
);

这个实例定义了一条路由规则,可以匹配以下路径:

  • "/posts": 会调用 PostController::index(int $id = null) 方法,传入参数 $id = null
  • "/posts/list": 会调用 PostController::list(int $id = null) 方法,传入参数 $id = null
  • "/posts/show/32": 会调用 PostController::show(int $id = null) 方法,传入参数 $id = 32

上面的代码中创建 Controller 的时候,一共传入了四个参数,后两个稍后再介绍。

路由指向控制器方法

如果希望把路由明确地指向具体的控制器方法而不是整个控制器,那么可以使用 Spiral\Router\Target\Action 这个目标:

代码语言:txt
复制
use Spiral\Router\Target\Action;

// 匹配 "/posts/2019", "/posts/2019/12"
$route = new Route(
    '/posts/<year:\d{4}>[/<month:\d{2}>]', // 匹配模式
    new Action(
        PostController::class, // 目标控制器
        'archive', // 目标方法
        0 // 是否 Restful 模式(可选参数,默认值 0)
    )
);

// 匹配 "/posts/create", "/posts/edit", "/posts/save"
$route = new Route(
    '/posts/<action>', // 匹配模式
    new Action(
        PostController::class, // 目标控制器
        ['create', 'edit', 'save'], // action 参数的可用值
        0 // 是否 Restful 模式(可选参数,默认值 0)
    )
);

这里举了两种使用示例,第一种是直接指向明确的某一个控制器方法,第二种是同时制定多个控制器方法。

路由指向控制器组

这个有点像是把多个指向控制器的路由简化成一组的写法,使用的 target 是 Spiral\Router\Target\Group

代码语言:txt
复制
use Spiral\Router\Target\Group;

// 匹配 "/home/*", "/demo/*"
$route = new Route(
    '/<controller>/<action>',
    new Group(
        [
           'home' => HomeController::class,
           'demo' => DemoController::class
        ],
        0, // 是否 Restful 风格(可选参数,默认值 0)
        'index' // 默认 action(可选参数,默认值 "index")
    )
);

所以这个基本上不用做多少解释,基本上就是跟指向控制器的定义一样的,只是可以一次定义多个控制器匹配而已,要说明的是最后一个参数(指定默认 action)是只有把 <action> 指定为可选参数才有意义。

指向命名空间

这个就是系统用来定义默认控制器的方法,通常借助这个,可以实现给自己的项目的路由划分 "module",从而实现 HMVC 结构。例如:

代码语言:txt
复制
use Spiral\Router\Target\Namespaced;

// 匹配 "/foo/bar",指向 "App\Controller\FooController::bar()"
$route = new Route(
    '/<controller>[/<action>]',
    new Namespaced(
        'App\Controller', // 目标命名空间
        'Controller', // 控制器类的类名后缀(可选参数,默认值 "Controller")
        0, // 是否 Restful 风格(可选参数,默认值 0)
        'index' // 默认 action(可选参数,默认值 "index")
    )
);

// 匹配 "/admin/foo/bar",指向 "App\Controller\Admin\FooController::bar()"
$route = new Route(
    '/admin/<controller>[/<action>]',
    new Namespaced(
      'App\Controller\Admin', // 目标命名空间
      'Controller', // 控制器类名后缀(可选参数,默认值 "Controller")
      0, // 是否 Restful 风格(可选参数,默认值 0)
      'index' // 默认 action(可选参数,默认值 "index")
    )
);

可以看到,我们可以借助这个工具,给前端、后端的路由各设置不同的默认值。

Restful 风格控制器方法

前面一直有提到一个 "是否 Restful 风格" 的参数,这个参数主要为了方便实现 Restful 风格的路由(把相同路径的不同动词请求分开)。如果在创建路由实例的时候指定这个参数为 1,那么 Spiral 会在解析控制器方法的时候自动把 HTTP 动词加到方法名称前。比如要请求的控制器方法是 foo,那么 POST 请求会指向 postFoo,GET 请求会指向 getFoo.

为了演示这种用法,首先创建一个控制器:

代码语言:txt
复制
namespace App\Controller;

class FooController
{
    public function getBar(int id) {}
    public function postBar(int id) {}
    public function putBar(int id) {}
    public function deleteBar(int id) {}
}

然后定义一个路由规则:

代码语言:txt
复制
use App\Controller\FooController;
use Spiral\Router\Target\Controller;

$fooRoute = new Route(
    '/foo/<id:\d+>',
    new Controller(
        FooController::class,
        1, // 这里改为 1,或者 Controller::RESTFUL 常量
    ),
    ['action' => 'bar'] // 默认值
);

$router->setRoute(
    'foo.restful',
    $fooRoute
);

这样当我们以 GET 方法请求 /foo/222 的时候,会执行 getBar 方法,用 DELETE 方法请求 /foo/222 的时候,会请求 deleteBar 方法。

实现我们需要的路由

经过以上这么细致(或者说啰嗦)的介绍之后,回头来看我们要定义的路由,会发现在路径只有两种形式:/posts/posts/<id>,如果把 id 变成可选参数,那么就只有一种形式:/posts[/<id>],而动词有四种:GET, POST, PUT, DELETE. 很显然,有很多种方案可以实现我们的实践目标。不过个人觉得最简洁的当然是 “路由指向控制器 + Restful 风格”。

创建控制器

首先,创建 PostController,可以在 app/src/Controller 目录下自己创建这个类,也可以借助脚手架工具,在命令行执行:

代码语言:txt
复制
$ php app.php create:controller post

控制器的代码如下:

代码语言:txt
复制
<?php
/**
 * File: App\Controller\PostController.php
 */

declare(strict_types=1);

namespace App\Controller;

class PostController
{
    public function getPost(int $id = null): string
    {
        return is_int($id) ? "查看文章 $id" : "文章列表";
    }

    public function postPost($id = null): string
    {
        return "创建文章";
    }

    public function putPost(int $id = null): string
    {
        return "编辑文章";
    }

    public function deletePost(int $id = null): string
    {
        return is_int($id) ? "删除文章 $id" : "参数缺失";
    }
}

定义路由规则

然后打开 app/src/Bootloader/RoutesBootloader.php,在 boot 方法中注册我们的路由(注意要把我们的规则放到最前面):

代码语言:txt
复制
--- a/app/src/Bootloader/RoutesBootloader.php
+++ b/app/src/Bootloader/RoutesBootloader.php
@@ -12,6 +12,7 @@ declare(strict_types=1);
 namespace App\Bootloader;

 use App\Controller\HomeController;
+use App\Controller\PostController;
 use Spiral\Boot\Bootloader\Bootloader;
 use Spiral\Router\Route;
 use Spiral\Router\RouteInterface;
@@ -26,8 +27,17 @@ class RoutesBootloader extends Bootloader
      */
     public function boot(RouterInterface $router): void
     {
+        $router->setRoute(
+            'posts',
+            new Route(
+                "/posts[/<id:\d+>]",
+                new Controller(PostController::class, Controller::RESTFUL),
+                ['action' => 'post']
+            )
+        );
+
         // named route
-        $router->addRoute(
+        $router->setRoute(
             'html',
             new Route('/<action>.html', new Controller(HomeController::class))
         );

重要提醒:如果应用服务器是运行中的,请执行 ./spiral http:reset 重设 HTTP 工作进程,或者直接停止再重新运行 spiral 应用服务器。

验证一下

脚手架提供了一个命令可以让我们查看所有已经注册了的路由规则:

代码语言:txt
复制
$ php app.php route:list
+--------+----------------------------+------------------------------+
| Verbs: | Pattern:                   | Target:                      |
+--------+----------------------------+------------------------------+
| *      | /posts[/<id:\d+>]          | Controller\PostController->* |
| *      | /<action>.html             | Controller\HomeController->* |
| *      | /[<controller>[/<action>]] | Controller\*Controller->*    |
+--------+----------------------------+------------------------------+

然后我们可以通过 curl 来验证一下:

代码语言:txt
复制
$ curl http://localhost:8080/posts
文章列表

$ curl http://localhost:8080/posts/2
查看文章 2

$ curl -X POST http://localhost:8080/posts
创建文章

$ curl -X PUT http://localhost:8080/posts
编辑文章

$ curl -X DELETE http://localhost:8080/posts/33
删除文章 33

不足之处

至此,我们本次的实践目标就达到了。当然,严格来说还有一点不足之处,POSTPUT 路由严格来说不应该支持 <id> 参数,但现在 [POST|PUT] /posts/333[POST|PUT] /posts 都是一样的。如果要严格限制的话,可以把我们的路由拆成两条,一条包含必备参数 <id>,一条不含 <id> 参数。或者直接不使用 Restful 风格的路由定义,通过 withVerbs 方法自行绑定路由允许的动词。

如果您有兴趣,可以自行尝试。

在本文中原计划是要把路由和控制器一并介绍给大家,但是写下来发现仅仅是路由的部分就占用了大量的篇幅,而控制器又涉及到了请求和响应两个方面的处理,同样篇幅不短,因此我决定把控制器的部分放到下一篇文章中,详细介绍 Spiral 框架中的请求和响应。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实践目标
  • Spiral 路由绑定介绍
    • RouterInterface 接口
      • Route 类
        • 路由匹配顺序
          • 路由参数
            • 路由指向控制器
              • 路由指向控制器方法
                • 路由指向控制器组
                  • 指向命名空间
                    • Restful 风格控制器方法
                    • 实现我们需要的路由
                      • 创建控制器
                        • 定义路由规则
                          • 验证一下
                            • 不足之处
                            相关产品与服务
                            云服务器
                            云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档