Laravel 5.1 入門記 その3(URL Routing 編)
Routing から再開。
HTTP Routing - Laravel - The PHP Framework For Web Artisans
Basic Routing
URL のルーティングは、 app/Http/routes.php に書いていくらしい。この route.php はApp\Providers\RouteServiceProvider が読み込むらしい。よくわかんないけど!
Laravel の基本的なルーティングは URI とクロージャ(無名関数のことね) でシンプルに記述できるらしい。まずはドキュメントに載っているコードを app/Http/routes.php に追記してみます。
Route::get('/', function () {
return 'Hello World';
});
元々あった、 '/' の定義はコメントアウトしちゃいました。 ブラウザで http://localhost:8080/ にアクセスすると、「Hello World」と表示されました\(^o^)/
シンプルだけど、実際のアプリ開発で、routes.php に処理を書いてしまうのは、ちょっと辛そう。他のクラスに委譲するパターンはそのうち出てくるんでしょう。
Registering A Route For Multiple Verbs
どうやら、get/postを同じ定義にまとめることもできるらしい。書き方はこんな感じ。
Route::match(['get', 'post'], '/', function () {
return 'Hello World';
});
Route::match を使うのがポイントみたい。さらにどんな HTTP method でも受け取るエンドポイントを作りたいときは
Route::any('foo', function () {
return 'Hello World';
});
Route::any を使って定義する。
さて次、次
Generating URLs To Routes
Django の reverse 関数と同じかな。
$url = url('foo');
少し重要な機能なので試してみます。route.php を以下のように書き換え
Route::get('/', function () {
return 'Hello World. ' . url('/');
});
Route::get('/helloworld', function () {
return 'Hello World. ' . url('/helloworld');
});
そして、 http://localhost:8080/ にアクセスすると
Hello World. http://localhost:8080
と表示され、 http://localhost:8080/helloworld にアクセスすると
…
404 Not Found !! orz
原因を調べてみたら、使っていた ubuntu 14.04 では Apache module の mod_rewrite が有効になっていなかった。
$ cd /etc/apache2/mods-enabled
$ sudo ln -s ../mods-avaialble/rewrite.load ./rewrite.load
$ sudo apachectl restart
以上を行って、もう一度 http://localhost:8080/helloworld にアクセスすると、今度こそ
Hello World. http://localhost:8080/helloworld
\(^o^)/\(^o^)/\(^o^)/
2行のセクションなのに、はまって長かった。次に行きます。
Route Parameters
Required Parameters
ここからは RESTful なものを実装するときに重要なやつですね。
URL上の文字を正規表現で引っかけて、クロージャに渡すことが出来るらしい。
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
これを routes.php に追記して、 http://localhost:8080/user/tnamao にアクセスすると
User tnamao
と表示されました。 ちなみに id は必須パラメータなので user/ まででアクセスしても必須パラメータなので例外が出力されてしまいました。
続いて、複数のパラメータの場合は次のように書くみたい。
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
return 'post: ' . $postId . ' commentId: ' . $commentId;
});
これを追記して、 http://localhost:8080/posts/aaa/comments/bbb にアクセスすると
post: aaa commentId: bbb
と表示されました。これ、パラメータの順番は変えられるのかな、と思って次の設定も試してみた。
Route::get('posts2/{post}/comments/{comment}', function ($commentId, $postId) {
return 'post: ' . $postId . ' commentId: ' . $commentId;
});
これで、 http://localhost:8080/posts/aaa/comments/bbb にアクセスしたら、
post: bbb commentId: aaa
さっきのと同じ結果を期待したのですが、残念! orz やっぱり Django / Python みたいには柔軟にいかないようです。Python の kwargs の仕組みは強いなぁ、と。
あと余談ですが、この Route parameter にはハイフンは使えないとのこと。
Optional Parameters
少し前に ちなみに id は必須パラメータなので user/ まででアクセスしても必須パラメータなので例外が出力 の解決策っぽい。
Route::get('user/{name?}', function ($name = null) {
return $name;
});
この例の様に、? を付ければ良いらしい。早速、さっきの定義に追加して、以下の様にしてみた。
Route::get('user/{id?}', function ($id=null) {
return 'User '.$id;
});
これで http://localhost:8080/user/ にアクセスすると、エラーが出なくなった\(^o^)/
Regular Expression Constraints
恐らくやりたいことの本命が来た。
where メソッドで正規表現を使った複雑なパラメータ指定ができるらしい。
Route::get('user/{id}', function ($id) {
return return 'User '.$id;
})
->where('id', '[0-9]+');
Route::get('user/{id}/{name}', function ($id, $name) {
return 'User '.$id.' Name '.$name;
})
->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
うーん、イマイチ感が…。ま、仕方ない。
Global Constraints
id などあちこちで定義するようなパラメータの正規表現は、Global レベルの定義をできるらしい。
RouteServiceProvider の boot 関数の中で以下のように書けばよいみたい。
public function boot(Router $router)
{
$router->pattern('id', '[0-9]+');
parent::boot($router);
}
これを使うには
Route::get('user/{id}', function ($id) {
// Only called if {id} is numeric.
});
この定義で大丈夫らしい。でも、グローバルなのかローカルなのか、違いが無いのでグローバルな定義は使うときに気をつけたほうが良さそう。
Named Routes
名前付きのルーティングらしい。 Django にもあったけど、URLを逆引きするときの設定ぽい。
Route::get('user/profile', ['as' => 'profile', function () {
//
}]);
なんか、まんまですね。恐らく使うときの本命はこっち。
Route::get('user/profile', [
'as' => 'profile', 'uses' => 'UserController@showProfile'
]);
ここで初めて、他のクラスに処理を委譲する方法が出て来た。無名関数で処理を定義していた時はよかったけど、同じクラスを異なるURLでルーティングするときに、URLが引けない問題があるんだろうなぁ、と。
憶測なので外れているかもしれませんが…。 ただ、URLとは異なる別名指定しておくと、コードのメンテナンスは色々楽かもしれない。URLは変わるものだしね…。
Route Groups & Named Routes
ルーティング定義をグルーピングするための機能らしい。
Route::group(['as' => 'admin::'], function () {
Route::get('dashboard', ['as' => 'dashboard', function () {
// Route named "admin::dashboard"
}]);
});
なるほど。機能単位でまとめるものなのね。ちょっと試してみたら、URL の階層化は定義されていないのね。 URL はどうやって取るのだろうと思って以下のコードも試してみた。
Route::group(['as' => 'admin::'], function () {
Route::get('dashboard', ['as' => 'dashboard', function () {
return 'dashboard. ' . url('admin::dashboard');
}]);
});
結果は
dashboard. http://localhost:8080/admin::dashboard
なんか違う…。どうも次のセクションにやり方が書いてあるっぽい。
Generating URLs To Named Routes
名前付きでルーティング設定しておくと、URL生成とかリダイレクトが route 関数で出来ちゃうよ、と言うことらしい。
$url = route('profile');
$redirect = redirect()->route('profile');
こんな感じで出来るらしい。リダイレクトのところがちょっとよく分からないけど…。
パラメータが必要なURLは、以下の様にパラメータも与えれば URL を生成できるみたい。
Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
//
}]);
$url = route('profile', ['id' => 1]);
では、さっき出来てなかったコードを修正してみます。
Route::group(['as' => 'admin::'], function () {
Route::get('dashboard/{id}', ['as' => 'dashboard', function ($id) {
return 'dashboard. ' . route('admin::dashboard', ['id' => $id]);
}]);
});
オマケで id パラメータを塚用にしちゃった。そして、何となく予想して route 関数の一つ目の引数は admin::dashboard にしてみた。そして、ブラウザで http://localhost:8080/dashboard/1 にアクセスすると
dashboard. http://localhost:8080/dashboard/1
できたー\(^o^)/
ややこしいけど、url と route を使い分けないとダメですね。しかし、url 関数はアプリケーションの接頭辞を付けるだけの関数なんだろうか。いまのところは、イマイチ存在意義が分かっていない。
Route Groups
Route Group を使うと ルーティング属性を共有出来るらしい。middleware / namespace / route-prefix なんかが共通設定として定義できるらしい。まだ入門前の middleware とか難しいのは置いといて、route-prefix で試してみた。
Route::group([
'prefix' => 'admin',
'as' => 'admin::'
], function () {
Route::get('dashboard/{id}', ['as' => 'dashboard', function ($id) {
return 'dashboard. ' . route('admin::dashboard', ['id' => $id]);
}]);
});
こんな感じに dashboard の定義を修正して、 'prefix' => 'admin' を追加してみた。そして、ブラウザで http://localhost:8080/admin/dashboard/1 にアクセスすると
dashboard. http://localhost:8080/admin/dashboard/1
アクセス先も変わったし、route 関数が返す URL も変わりました\(^o^)/
Route Group はうまく使えば設定を簡略化できる。でもそのためには、URL 設計をしっかりやろうってことか。URLだけじゃなくて、名前空間もかな。恐らく機能単位で名前空間は分けた方が良さそう。
CSRF Protection
ルーティングのところで CSRF に触れるのか…。
Laravel の CSRF Protection はセッションにトークンを生成して、自動的に処理が行われるらしい。トークンはセッションに保存され、フィールドとしては _token が自動的に使われる。 csrf_field helper 関数でトークンの値は取得できる。
HTML Form に設定する時は以下のいずれかの方法で実装する。
<?php echo csrf_field(); ?>
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
{!! csrf_field() !!}
一番最後は Blade template エンジンというのを使った時のパターンらしい。
HTTP メソッドの POST / PUT / DELETE でアクセスがあったときは、自動的に CSRF token のチェックが行われる。VerifyCsrfToken HTTP middleware が、入力とセッションのtokenをチェックしているらしい。
Excluding URIs From CSRF Protection
CSRF チェックを外すことも出来るらしい。以下のコードを参照
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'stripe/*',
];
}
$except に CSRF チェックを外すURLを定義するらしい。Ajax 用の API とかだったら、CSRF チェックを外すというか、Cookie で何とかしたいよね…。
と、思ったら、Ajax の場合は X-XSRF-TOKEN ヘッダーから取得して使えって書いてあるな。じゃ、何の問題もない。
Form Method Spoofing
HTTP メソッドが通常の FORM では GET / POST しかない問題への対応で、 以下の様に hidden で _method パラメータを作ってそこに DELETE / PUT / PATCH を設定するらしい。
<form action="/foo/bar" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
Throwing 404 Errors
404エラーを投げるときは、以下のコード
abort(404);
詳しくは、errors を見ろとのこと。
とりあえず、 routing に関してはこれくらいで、次回は Middleware に行きます。