ryunosuke/microute

基于 PHP 类的路由

v2.0.2 2024-08-01 09:10 UTC

README

描述

这是一个仅执行基于类的(控制器)派发的微型路由框架。

没有 MVC 的 MVC 功能。基本功能如下。

  • 将控制器的 PHP 命名空间直接映射到 URL(基本可以更改)
    • Hoge\Fuga\PiyoController::actionAction 将变为 hoge/fuga/piyo/action
    • CamelCase(HogeFugaController::FooBarAction) 转换为 chain-case(hoge-fuga/foo-bar)
    • 如果使用上述默认路由来配置控制器,则会自动进行路由。其他主动路由方式包括以下几种
      • 重写路由
        • “将一个 URL 重写到另一个 URL”的功能。与 Apache 中的“mod_rewrite”完全相同
      • 重定向路由
        • “将一个 URL 重定向到另一个 URL”的功能。与 HTTP 中的“重定向”完全相同
      • 正则表达式路由
        • “将匹配正则表达式的 URL 作为某个 Controller/Action 运行”的功能。类似于某些框架中的所谓“路由”
      • 别名路由
        • “将一个 URL 作为某个 Controller 运行”的功能。类似于 Apache 中的“mod_alias”
      • 作用域路由
        • “将某个前缀作为某个 Controller 运行”的功能。类似于 Apache 中的“mod_alias”(可以使用 URL 参数)
    • 有三种方式可以设置路由
      • 默认路由(将 CamelCase(HogeFugaController::FooBarAction) 转换为 chain-case(hoge-fuga/foo-bar))
      • 使用 #[Redirect('fromurl')]#[Regex('#pattern#')] 等属性进行路由
      • 使用 Router 实例的 redirect・regex 等方法进行单独路由
  • 按控制器单元处理错误
    • 当在动作方法中抛出异常时,可以在该控制器内的 catch 方法中捕获它
    • 在此之后,如果 catch 方法抛出异常,则将转到 DefaultController#errorAction
  • 常用功能已属性化
    • #[Ajaxable] 则只接受 AJAX 请求
    • #[Context('json')] 则接受 .json 扩展名访问
  • 动作方法的参数将根据方法定义自动参数化
    • 对于 hogeAction($id) 这样的动作,可以从请求参数中获取并映射 id
    • 数据源基于 #[Method]#[Argument] 属性
  • 可以使用 Symfony 的 BrowserKit 进行测试
    • 请参阅 tests

安装

{
    "require": {
        "ryunosuke/microute": "dev-master"
    }
}

演示

cd /path/to/microute
composer example
# access to "http://hostname:8000"

用法

配置适当的控制器,生成 Service 类,然后运行 run 即可。

$service = new \ryunosuke\microute\Service([
    /* オプション配列 */
    'debug'              => false,
    'controllerLocation' => [
        '\\namespace\\to\\controller' => '/directory/to/controller',
    ],
    // ・・・
]);
$service->run();

选项

Service 的构造函数参数如下所示。

  • debug: bool
    • 指定调试标志
    • 设置为 true 则不使用缓存,或日志增多。开发时推荐设置为 true。
    • 默认为 false
  • cacher: \Psr\SimpleCache\CacheInterface
    • 指定内部使用的缓存实例
    • 默认为空。必填
  • logger: \Psr\Log\LoggerInterface
    • 指定用于记录操作的 psr3 LoggerInterface
    • 默认不记录日志
  • events: callable[][]
    • 指定每个事件执行的事件处理器
    • 默认不执行任何操作
  • priority: array
    • 指定路由的优先级
    • 默认为 ['rewrite', 'redirect', 'alias', 'regex', 'scope', 'default']
  • maintenanceFile: string
    • 指定维护页面的文件
    • 在此指定的文件存在时,所有响应都将包含该文件的结果,并返回状态 503
      • 触发器是“存在”。因此,预先将其包含在文件集中是不可能的
      • 最好使用别名包含,并在维护开始时使用 mv/cp
    • 维护文件可以返回 Response 类型。在这种情况下,将使用该 Response。通常不需要,但在需要输出 Retry-After 等头部时可能会有所帮助
    • 默认为空(无维护页面)
  • maintenanceAccessKey: string
    • 指定允许在维护期间访问的查询键
    • 在此指定的查询将被包含在内,以允许在维护期间访问
    • 指定该cookie的有效期
      • maintenanceAccessKey = hogera,通过请求 ?hogera=1234 的任意页面,可以在维护期间以正常方式访问 1234 秒
    • 默认为空(无查询)
  • router: \ryunosuke\microute\Router
    • 指定 Router 实例
    • 除非非常特殊,否则没有指定它的意义
  • dispatcher: \ryunosuke\microute\Dispatcher
    • 指定 Dispatcher 实例
    • 除非非常特殊,否则没有指定它的意义
  • resolver: \ryunosuke\microute\Resolver
    • 指定 URL 帮助器实例
    • 默认为 \ryunosuke\microute\Resolver
  • trustedProxies: array
    • 自动注册可信代理。原则上传递给 Request::setTrustedProxies 的 CIDR,但可以使用一些特殊的表示法
      • cidr: 注册指定的 CIDR
      • mynetwork: 注册自网络段
      • private: 注册所谓的私有网络
      • url: 通过访问 URL 并注册其结果。URL 只支持 file/http
      • name => [url => string, ttl => int, filter => callable]: 通过指定过滤器条件或 TTL 来访问 URL 并注册其结果
    • 键(name)用作缓存键。URL 指定以外没有意义
    • 默认为 []
  • controllerClass: string
    • 指定 Controller 的基类名称
    • 默认为 \ryunosuke\microute\Controller::class
    • 除非非常特殊,否则没有指定它的意义
  • controllerAutoload: 数组
    • 通过Controller的__get方法可以调用的对象的名字空间和构造函数参数
    • 如使用['name\\space' => [1, 2, 3]],则在Controller内部可以通过$this->Hoge获取到name\\space\\Hoge对象
    • 对象的生成只会进行一次,此时使用的构造函数参数为指定的数组(如上所述的[1, 2, 3]
  • requestFactory: 可调用函数
    • 指定请求对象的提供者
    • 此处指定的闭包将注册到Request::setFactory中
    • 除非非常特殊,否则没有指定它的意义
  • requestClass: 字符串
    • 指定请求对象的类名
    • 默认为 \ryunosuke\microute\http\Request::class
    • 除非非常特殊,否则没有指定它的意义
  • request: \ryunosuke\microute\http\Request
    • 指定请求对象
    • 默认为 \ryunosuke\microute\http\Request
    • 除非非常特殊,否则没有指定它的意义
  • requestTypes: callable[]
    • 指定基于内容类型的请求体如何解析
    • 默认为在json时使用json_decode
  • sessionStorage: \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface
    • 指定会话存储
    • 默认为 \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage
  • parameterContexts: array|callable
    • 指定不同扩展名的Content-Type
    • 如使用['json' => 'application/json'],则在访问/controller/action.json时,Content-Type将为application/json
    • 如果指定闭包,则将类似'json'的参数传递进来,并返回Content-Type
    • 默认为 []
  • authenticationProvider: array|callable
    • 指定认证信息提供者
    • 可以指定简单的['user1' => 'pass1', 'user2' => 'pass2']数组,或者接收$username并返回密码的闭包
    • 默认为 []
  • authenticationComparator: callable
    • 指定认证信息的比较方法
    • 指定一个callable,该callable接收期望的密码和输入的密码,并返回bool
    • 默认为 $valid_password === $password
  • authenticationNoncer: callable
    • 指定digest认证中nonce的增量机制
    • 指定一个callable,该callable接收nonce并返回nc
    • 默认为没有nc验证
  • controllerLocation: 数组|string
    • 指定启动应用程序的「名空间→目录」对应关系
    • 使用数组指定,例如['\\vendor\\app\\controller' => '/app/controller'](即psr4的格式)
      • 在这种情况下,自动加载器将自动注册
    • 或者指定基础控制器的类名
      • 在这种情况下,自动加载器不会自动注册。当想要使用composer的autoload时使用
    • 无论如何,都可以进行多次注册。如果多次注册,则按顺序尝试,因此可以分离配置没有任何关系的控制器。
    • 默认为空。必填

与基本操作相关的重要内容和必需或准必需的内容将以粗体显示。

例如,controllerLocation是必需的。如果没有它,则无法加载控制器,所有处理都会失败。debug等不是必需的,但如果不指定,则可能会影响性能或使开发变得不便。

所有元素通过传递闭包来执行,首次引用时执行,之后显示其结果。换句话说,如果想要设置callable,则需要指定「返回callable的Closure」(类似于Pimple等DI容器)。

规范

控制器层次结构

控制器具有以下层次结构。

  • /DefaultController
    • 顶级名空间默认控制器
    • 当访问/时,将触发defaultAction
    • 当访问/hoge时,将触发hogeAction
    • 当在顶级名空间中未捕获异常时,将触发errorAction
    • 至少需要拥有errorAction方法
  • /HogeController
    • 顶级名空间的用户实现控制器
    • 当访问/hoge/foo时,将触发fooAction
  • /Namespace/DefaultController
    • Namespace 名空间默认控制器
    • 当访问/namespace/时,将触发defaultAction
    • 当访问/namespace/hoge时,将触发hogeAction
    • 当在Namespace名空间中未捕获异常时,将触发errorAction
  • /Namespace/FugaController
    • Namespace 名空间用户实现控制器
    • 当访问/namespace/fuga/foo时,将触发fooAction

如果没有在当前名空间中找到DefaultController,则将查找上一级层次结构的DefaultController。在上面的例子中,如果/Namespace/DefaultController不存在,则未捕获异常的捕获将由/DefaultController执行。如果顶级没有DefaultController,则会产生错误。

但是,只会在errorAction中查找。defaultAction不会查找。相反,例如,如果访问/hoge/fuga,则将对应于Hoge\\FugaController#defaultAction。也可以说「对控制器的action无访问时对应于defaultAction」。

上述的

  • 在此名空间中,当访问/namespace/时,将触发defaultAction
  • 在此名空间中,当访问/namespace/hoge时,将触发hogeAction
  • 对控制器的action无访问时对应于defaultAction

是矛盾的。例如,URL“hoge/fuga/piyo”可以是

  • Hoge\FugaController#piyoAction (Hoge名空间中的Fuga控制器的piyo动作)
  • Hoge\Fuga\DefaultController#piyoAction (Hoge\Fuga名空间中的Default控制器的piyo动作)
  • Hoge\Fuga\PiyoController#defaultAction (Hoge\Fuga名空间中的Piyo控制器的default动作)
  • Hoge\Fuga\Piyo\DefaultController#defaultAction (Hoge\Fuga\Piyo名空间中的Default控制器的default动作)

的四种解释。在这种情况下,将按照从上到下的顺序进行优先选择。也可以说「尽可能不使用default优先」。另外,「是控制器的default还是动作的default」则控制器优先。

控制器的生命周期

请求将遵循以下生命周期。

  • route(非默认路由)的搜索
    • 检查是否是rewrite或redirect等要处理的URL
    • 如果匹配,则rewrite将重写路径,redirect将直接返回响应
  • dispatch
    • 检查是否存在控制器、方法是否为动作方法等,以确定要执行的Controller/Action
  • construct
    • 在控制器构造函数的直后调用
    • 与init方法的明确区分没有。描述应在构造函数中执行的操作,如字段初始化、赋值等
    • 但“调用construct但不调用init”是可能的情况(例如路由失败等)
    • 此外,__construct是final的,不允许继承
  • init
    • 调用控制器的init方法
    • init方法允许返回Response类型。如果返回Response类型,则生命周期在此结束,以下处理不会执行。
  • before
    • 调用控制器的before方法
    • 在这里编写公共视图变量或权限检查等
  • action
    • 调用已分派的动作方法
    • 在此编写动作特有的处理
  • after
    • 调用控制器的after方法
    • 在此阶段,Response已经确定,因此如果需要调整头(Content-type等),则在此处编写
  • finish
    • 调用控制器的finish方法
    • finish方法允许返回Response类型,但与after没有明确区别。从该方法开始,Response不会更改,因此可以用于最终日志记录或响应检查
  • catch
    • 在上面的init~finish流程中抛出的异常在此方法中被捕获
    • 传递过来的异常作为参数
    • 如果此方法进一步抛出异常,则将委派给DefaultController#errorAction
  • finally
    • 无论上述init~catch流程如何,都会始终调用此方法
    • 传递过来的Response作为参数

在上述init~finish流程中,也可以抛出名为ThrowableResponse的异常对象。正如其名所示,这是“可抛出的响应”处理,抛出它可以将后续处理跳过并确定响应。

由此,init,finish的“允许返回Response类型”的旧规范已不再有意义。未来,init,finish可能被废弃,只保留before,after。

其他特殊生命周期有subrequest。subrequest在内部请求执行时以原始控制器实例作为参数触发。那时可以针对内部请求进行微调。但就目前的实现而言,只有forward方法会触发内部请求。在大多数情况下,无需担心。

作为服务的事件处理

上述控制器生命周期与事件处理存在另一个维度

  • request: route之后。参数是Request
  • dispatch: dispatch之前。参数是Controller
  • error: 例外捕获之后。参数是Throwable
  • response: 发送响应之前。参数是Response

这些只会被调用一次。不会调用多次。此外,执行上下文是Service,可以使用$this访问设置信息。

当控制器生命周期可能被多次调用,或者需要处理不在控制器上下文中而是在应用程序中统一处理的过程时,这里更方便。

如果以数组指定,则所有都会被调用,但如果返回return false,则会在那里中断。此外,在任何时候返回Response,则它将成为最终响应,所有生命周期都会跳过。

控制器·动作方法的属性

属性按照以下顺序读取

  • 自身的属性
  • 自身的类属性
  • 父方法的属性
  • 父类的属性

也就是说,“父 < 自身”,“类 < 方法”这种优先级,更接近定义的东西会被优先考虑。最终,如果在根本的抽象控制器中定义属性,那么它将在所有下位控制器的所有方法中应用。

要停止此操作,请使用NoInheritance属性。如果在继承树中存在NoInheritance属性,则在那里读取将被切断。也就是说,向方法添加NoInheritance则将其作为完全独有的属性处理,无论类还是父类都不会查看。 NoInheritance属性可以指定要停止读取的属性名。省略时为所有属性。

属性类型如下。

  • #[Method]
    • 指定请求方法
    • 例如,#[Method('get', 'post')则只接受GET和POST请求
    • 未指定时为所有方法
    • 值省略时为所有方法
  • #[Argument]
    • 指定动作方法要传递的参数的类型和顺序
    • 例如,`#[Argument('get', 'post')]`则按顺序查找$_GET, $_COOKIE
      • 指定可以是get post file cookie attribute
      • 无论指定什么,#[Method]中指定的方法的参数始终包含
    • 未指定时只遵循#[Method]
    • 值省略时只遵循#[Method]
  • #[Origin]
    • 指定要接受的Origin头
    • 例如,#[Origin('http://example.com')则来自http://example.com以外的请求将被403拒绝(GET以外)。但调试时是可访问的
    • 头部可以使用fnmatch进行通配符匹配。指定多个时,只要匹配任何一个就会被允许
    • 未指定时不进行Origin头验证
    • 值省略时不进行Origin头验证
  • #[IpAddress]
    • 指定IP地址的允许/拒绝
    • 例如,#[IpAddress(['203.0.113.0/24'], true)则来自203.0.113.0/24以外的请求将被403拒绝。但调试时是可访问的
    • 例如,#[IpAddress(['203.0.113.0/24'], false)则来自203.0.113.0/24的请求将被403拒绝。但调试时是可访问的
    • 未指定时不进行IP限制
    • 值省略时不进行IP限制
  • #[Ajaxable]
    • 不接受Ajax请求以外
    • 例如,#[Ajaxable(403)则正常访问也会被403拒绝。但调试时是可访问的
    • 未指定时不进行请求限制
    • 值省略时为400
  • #[RateLimit]
    • 限制一定秒内的请求次数
    • 例如,#[RateLimit(30, 10)则“10秒内30个请求”
    • 例如,#[RateLimit(180, 60, 'attributes:id')则“60秒内180个请求”
    • 键指定可以是数组指定,复合使用。例如,#[RateLimit(30, 10, ['ip', 'attributes:id'])则使用IP和Request的attribute的id作为键
    • 如果有多个属性被附加,则按定义顺序处理。那时,如果指定键不存在,则忽略它
      • 例如,#[RateLimit(9, 9, 'get:id')#[RateLimit(3, 3, 'ip')同时附加时,如果有get:id则应用。如果没有,则回退到ip(“没有IP”的情况不可能存在,因此如果顺序出错,get:id将永远不会被处理)
    • 未指定时为无限制速率
    • 值省略时为IP
  • #[Context]
    • 指定要接受的上下文(即扩展名)
    • 例如,#[Context('json', 'xml')则通过controller/action.jsoncontroller/action.xml调用动作方法
      • 上下文可以通过$this->request->attribute->get('context')获得
    • #[Context('', 'json', 'xml')] とすると controller/action を活かしたまま controller/action.jsoncontroller/action.xml でもコールされるようになります
    • #[Context('*')] のように * を含めるとあらゆる拡張子アクセスを受け付けます
    • 未指定時は . 付きアクセスを受け付けません
    • 値省略時は . 付きアクセスを受け付けません
  • #[BasicAuth]/#[DigestAuth]
    • basic/digest 認証が行われるようになります
    • この属性による認証は簡易的なもので例えば下記の問題があります。用途はあくまで「簡易的にちょっとそのページを守りたい」程度です
      • アクションごとにユーザを使い分けるようなことは出来ない
      • いわゆる認可機構がない
      • digest 認証において nc のチェックを行わないのでリプレイ攻撃に弱い(一応フックポイントは用意してある)
  • #[Cache]
    • 指定秒数の間 http キャッシュが行われるようになります
    • #[Cache(10)] とすると 10 秒間の間 304 を返します
    • 未指定時はキャッシュを行いません
    • 値省略時は 60 です
  • #[WebCache]
    • レスポンスが公開ディレクトリに書き出され、以後のリクエストは web サーバーに委譲されます(htaccess に依存)
    • #[WebCache(10)] とすると 10 秒間の間リクエスト自体が飛びません(60 * 60 * 24 のような単純な数式が使える)
    • アプリを通らなくなるため、キャッシュを解除する術はありません(書き出されたファイルを消すしかない)
    • 未指定時はキャッシュを行いません
    • 値省略時は 60 です
  • #[Event('hoge', 1, 2, 3)]:hoge x, y, z
    • 追加イベントを指定します(後述)
    • アクション前後で hogeEvent(1, 2, 3) が呼ばれるようになります

下記はルーティング用属性です。

  • #[DefaultRoute(true)]
    • デフォルトルーティングの有効/無効を設定します(後述)
    • [DefaultRoute(false)] とするとデフォルトルーティングが無効になります
  • #[Route]
    • ルートに名前を付けます
    • #[Route('hoge')] とすると 'hoge' でリバースルーティングしたときにこのアクションの URL を返すようになります
  • #[Rewrite('/url')]
    • /url アクセス時に rewrite されてこのアクションへ到達します
    • 実態は preg_replace によるリクエストパスの書き換えです。あらゆる処理に先立って行われます
  • #[Redirect('/url', 302)]
    • /url アクセス時に redirect されてこのアクションへ到達します
    • 第2引数でリダイレクトのステータスコードが指定できます
  • #[Regex('#pattern#')]
    • pattern にマッチする時に regex されてこのアクションへ到達します
    • / から始まると絶対パスでマッチします
    • / 以外から始まると「本来そのコントローラが持つ URL(CamelCase -> chain-case のデフォルトルーティング)」からの相対パスでマッチします
  • #[Alias('/url')]
    • /url アクセス時に alias されてこのコントローラへ到達します
  • #[Scope('/pattern')]
    • /pattern アクセス時にキャプチャーされつつこのコントローラへ到達します

大抵の属性は複数記述できます。

    #[Redirect('/url1')] // /url1 アクセスで redirect されてこのアクションに到達します
    #[Redirect('/url2')] // /url2 アクセスで redirect されてこのアクションに到達します
    public function hogeAction() {}

上記は /url1 /url2 の両方が有効です。

Alias は「URL → Controller」のルーティングなのでメソッドではなくクラス属性として記述します。

#[Alias('/fuga')]
class HogeController extends \ryunosuke\microute\Web\Controller
{
    public function fooAction() {}
}

このようなクラス定義をすると /fuga/foo という URL はこのコントローラの fooAction へ行き着きます。 つまり php レイヤでクラス名を変更するのと同じ効果があります。

同様に Scope は「URL → Controller」のルーティングなのでメソッドではなくクラス属性として記述します。

#[Scope('(?<pref_id>\d+)/')]
class HogeController extends \ryunosuke\microute\Web\Controller
{
    public function fooAction($pref_id) {}
}

このようなクラス定義をすると /hoge/13/foo という URL はこのコントローラの fooAction へ行き着きます。 pref_id がキャプチャされる点と相対パスが使える点が alias と異なります。

foo だけではなく、他にアクションが生えていれば到達します。 つまり、「全アクションで #[Regex] して共通パラメータを定義」したと同様の振る舞いをしますし、そのような使い方を想定しています。

アクションメソッドに渡ってくるパラメータ

特殊なことをしなければ ?id=123 というクエリストリングでアクションメソッドの引数 $id が設定されます。 データソースは #[Argument] #[Method] などの属性に応じて変わります。 その際、下記の特殊な処理が走ります。

  • アクションメソッドが引数を持っていてかつマッチするパラメータが無い場合はルーティングに失敗し、 404 になります
    • ただし、引数がデフォルト値を持つ場合は 404 にはならず、デフォルト値でコールされます
  • #[Regex] でルーティングされた場合はマッチ結果が渡ってきます
    • 名前付きキャプチャの名前が一致するものが優先で、名前が見つからない場合はマッチ順でマップします
  • #[Scope] でルーティングされた場合はマッチ結果が渡ってきます
    • 名前付きキャプチャの名前が一致するものが優先で、名前が見つからない場合はマッチ順でマップします
    public function hogeAction($id, $seq)
    {
        // /hoge?id=foo でアクセスしても 404 になる(seq がマップできない)
    }

    public function fugaAction($id, $seq = 123)
    {
        // /fuga?id=foo でアクセスすると $id=foo, $seq=123 となる(seq はデフォルト値が使われる)
    }

    public function piyoAction(int $id, $seq = 123)
    {
        // /piyo?id=foo でアクセスすると 404 になる(foo を int 化できない)
    }

    #[Regex('/detail-(?<id>[a-z]+)/(\d+)')]
    public function testAction($id, $seq)
    {
        // /detail-foo/123 でアクセスすると $id=foo, $seq=123 となる(id は名前が一致、seq は名前がないが順番が一致)
    }

アクションメソッドの戻り値による挙動

  • string 型
    • 戻り値をそのままレスポンスボディとし、ステータスコードとして 200 を返します
    • あまり用途はないでしょう
  • Response 型
    • その Response をそのままレスポンスとします
    • リダイレクト、json 返却、ダウンロードヘッダなど、よく使うものは親メソッドに定義されているのでそれらを使う際に頻出します
  • 上記以外
    • Controller の render メソッドがコールされます
    • 大抵の場合はここでテンプレートエンジンによる html レスポンスを返すことになるでしょう
    • render は引数として action メソッドの返り値が渡ってくるのでそれを使用して json レスポンスを返すことなどもできます

#[Event('hoge')] によるイベントディスパッチ

#[Event('hoge', 1, 2, 3)] という属性を記述するとアクションメソッドの前後で hogeEvent がコールされるようになります。 具体的には下記のコードで

    #[Event('hoge', 10, 15)]
    public function testAction()
    {
        echo 'アクション本体';
    }
    
    public function hogeEvent($phase, $x, $y)
    {
        if ($phase === 'pre') {
            echo 'アクション前';
        }
        if ($phase === 'post') {
            echo 'アクション後';
        }
    }

「アクション前アクション本体アクション後」と出力されます。

イベントの仕様は下記です。

  • 複数イベントが指定できます
    • 上記のサンプルで言えば #[Event('hoge')] #[Event('fuga')] と記述すればその記述順でディスパッチされます
  • 第1引数は $phase['pre', 'post'] で、それ以降は属性の引数です
    • 上記のサンプルで言えば $x = 10, $y = 15 です
    • この 'pre', 'post' は将来拡張される可能性もあります
  • イベントは Response 型の返却を許します
    • Response 型を返した場合、以降のイベントや(preの場合)実際のアクション処理は実行されません
    • ただし、after/finish はコールされます。イベント処理はあくまでアクションに紐づくイベントだからです
  • イベント中の例外送出は通常通り catch でハンドリングされます

その他

ルーティング

あらゆるルーティングは、基本である「Controller の名前空間をそのまま URL へマッピング(Hoge\Fuga\PiyoController::actionActionhoge/fuga/piyo/action)」という前提を崩しません。 リダイレクトを設定しようと正規表現ルーティングを設定しようと上記の URL も生きていてアクセス可能です。 これを無効にするには #[DefaultRoute] 属性を使用する必要があります。

デフォルトルート名

すべてのアクションメソッドはデフォルトで ControllerName::ActionName という(仮想的な)ルート名を持ちます。 resolver で URL を生成する際に、$resolver->action($controller, $action) で生成するのではなく、$resolver->route("$controller::$action") で生成しておけば、そのアクションを変更したくなった時、ルート名の変更をせずに済みます(ルート名は明示的に登録されたルート名が優先されるため)。

とは言え存在しない "$controller::$action" を指定するのは気持ち悪いのであらかじめ #[Route] で指定しておくのがベストです。

urls 方法

路由器中新增了名为 urls 的方法。该方法会返回所有现有 URL 及其元数据。在检查路由、生成网站地图或 URL 列表时非常方便。

许可证

MIT