CakePHP 4 で HTTP メソッドの接続可否をルーティングで設定する方法

はじめに
CakePHP 4 のルーティングは、デフォルトで各アクションへの URL がある規則に従って自動的に設定されます。アクションを実装すれば即アクセス可能になるので便利ですが、さらに設定を追記することで任意の URL を割り当てることもできます。
ところで Web 開発ではアクセス可能な HTTP メソッドを限定したい場合があります。
CakePHP 4 ではコントローラでも実装可能ですが、ルーティングで制御することもできます。
今日は CakePHP 4 のルーティング設定で、任意のアクションに接続可能なHTTP メソッドを指定する方法をご紹介します
今回使用したのは CakePHP 4.1.5 (cakephp/app 4.1.1) です。
- 目次
1. 下準備
今回は SampleController の index アクション を用意し、ルーティング設定でここに接続可能なメソッドを指定します。
<?php
declare(strict_types=1);
namespace App\Controller;
class SampleController extends AppController
{
public function index()
{
$this->autoRender = false;
echo 'OK';
}
}
動作確認用に下記 req コマンドを用意します。
この req コマンドは各メソッドで sample/index に接続を行い、許可された HTTP メソッドだけを表示するものです。
<?php
declare(strict_types=1);
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Http\Client;
use Cake\Routing\Route\Route;
class ReqCommand extends Command
{
public function execute(Arguments $args, ConsoleIo $io)
{
// 適宜変更してください
$url = 'http://localhost/sample';
// 各メソッドで接続
$http = new Client();
foreach (Route::VALID_METHODS as $method) {
$func = strtolower($method);
$response = $http->{$func}($url);
// 成功したメソッドだけ表示
if ($response->getStatusCode() === 200) {
$io->out($method);
}
}
}
}
- Creating a Command (Command Objects - CakePHP 4.x Strawberry Cookbook)
- https://book.cakephp.org/4/en/console-commands/commands.html#creating-a-command
- Http Client - CakePHP 4.x Strawberry Cookbook
- https://book.cakephp.org/4/en/core-libraries/httpclient.html
上記コード内の Route::VALID_METHODS には使用可能な HTTP メソッドが格納されていて、/vendor/cakephp/cakephp/src/Routing/Route/Route.php の中で定義されています。
このままだと POST と PUT の際に CSRF トークン不一致エラーになるので、
今回はサンプル動作確認の簡略化のために無効化します。
(通常の開発時は、基本的には有効のままが良いです)
$middlewareQueue
...
// 下記をコメントアウトし、最後に ; (セミコロン) を追加
// ->add(new CsrfProtectionMiddleware([
// 'httponly' => true,
// ]));
;
- Cross Site Request Forgery (CSRF) Middleware (Middleware - CakePHP 4.x Strawberry Cookbook)
- https://book.cakephp.org/4/en/controllers/middleware.html#cross-site-request-forgery-csrf-middleware
2. 設定方法
接続可能な HTTP メソッドの指定には setMethods() を使うことができます。
下記のように routes.php を変更してください。
<?php
use Cake\Routing\RouteBuilder;
$routes->scope('/', function (RouteBuilder $builder) {
$builder->connect('/sample', ['controller' => 'Sample', 'action' => 'index'])
->setMethods(['GET', 'PUT']);
});
設定が完了したら、先の「1. 下準備」で作った req コマンドを入力して確認してください。
(接続可能なメソッドだけが表示されます。)
$ cd /path/to/cakephp4
$ bin/cake req
GET
PUT
指定可能な HTTP メソッドは以下の通りです。
- GET
- PUT
- POST
- PATCH
- DELETE
- OPTIONS
- HEAD
メソッドが一つだけの場合は下記のように書くこともできます。
<?php
use Cake\Routing\RouteBuilder;
$routes->scope('/', function (RouteBuilder $builder) {
// POST メソッドだけを許可
$builder->post('/sample', ['controller' => 'Sample', 'action' => 'index']);
});
- Routing - CakePHP 4.x Strawberry Cookbook
- https://book.cakephp.org/4/en/development/routing.html
- Matching Specific HTTP Methods (Routing - CakePHP 4.x Strawberry Cookbook)
- https://book.cakephp.org/4/en/development/routing.html#matching-specific-http-methods
3. 【注意】動作しないケース
例えば下記のように、対象スコープ内で DashedRoute や InflectedRoute で fallbacks() を実行すると setMethods() が動作しないようです。
<?php
// !!! 動作しません !!!
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
$routes->setRouteClass(DashedRoute::class);
$routes->scope('/', function (RouteBuilder $builder) {
$builder->connect('/sample', ['controller' => 'Sample', 'action' => 'index'])
->setMethods(['GET', 'PUT']);
$builder->fallbacks();
});
# 下記のように全てのメソッドが通ってしまいます
$ bin/cake req
GET
PUT
POST
PATCH
DELETE
OPTIONS
HEAD
この fallbacks() を外せば setMethods() が効くようになりますが、
URL からコントローラとアクションを自動判別しなくなるため、ルーティングの手動設定が必要になります。
fallbacks() については下記公式ドキュメントで紹介されていて、
関数は /vendor/cakephp/cakephp/src/Routing/RouteBuilder.php にあります。
- Fallbacks Method (Routing - CakePHP 4.x Strawberry Cookbook)
- https://book.cakephp.org/4/en/development/routing.html#fallbacks-method
4. おわりに
CakePHP のルーティング設定は、デフォルトで自動的に URL を生成してくれるので、最初はあまり触る機会がなかったりするのですが、細かい設定が可能で奥が深いんですよね。公式ドキュメントの文章量も多いです。
今回ご紹介した setMethods() 等を使えば Laravel でのルーティングのように、意図しないアクセスをはじくことが出来ます。ただし、前述の fallbacks() があると動作しない点につきましてはご注意ください。