複数代入とは

みなさん、複数代入ってご存じでしょうか。

Laravelの公式のチュートリアルや多くのブログでこの言葉を見かけますが、なんのこっちゃわからない。これ自体からは、複数あるか知らんが何かしらの値を複数の変数に代入してるんじゃねーの、くらいしかわかりませんね。

いろいろ調べた結果、readouble.comの日本語の翻訳をされている川瀬裕久さんのブログであるkore1serverのEloquentの複数代入のリスクという記事がすごく参考になりました。

その記事ではこう説明されています。一部抜粋です。

フォームの入力名とデータベースのカラム名を同じにして、入力値をデータベース(もしくはORMがインスタンスのモデル)へ一気に代入してしまおうと言うアイデア

僕は、複数代入という言葉からイメージするのではなく、複数代入とはこういうものだと解釈したほうがいいと思います。しっくりくる日本語訳がなかったのが原因かなと思ったりもします。

そんな複数代入ですが、やっていることは説明にもある通り簡単ですね。

以下のような一般的な登録フォームがあるとします。

一般的な登録フォーム

コントローラーは次のようになります。

登録用のコントローラー

フォームから入力された値は、RequestクラスやInputクラスで取得できます。入力された値の配列をEloquentのcreate()やクエリビルダーのinsert()の引数に指定するとあら不思議。フィールド名とカラム名が一致さえしていれば後は自動的にDBに登録されます。

なるほど。これなら余計な事は考えずに、しかもワンライナーで書けます。あぁ、素晴らしい。

ですが、Laravel5はCSRFに関するミドルウェアが実装されていますので、POSTで通信する場合はCSRF対策のためのトークンを埋め込む必要があります。すると、入力された値のほかに_tokenが送られることになり、上記のようなコードは動かなくなります。

ちなみに、app/Http/Kernel.phpVerifyCsrfTokenがCSRFに関するミドルウェアになります。

リスク

ないとは思いますが、ミドルウェアを外してトークンも埋め込まなかったとしたら、そのまま登録されますね。仮にそのような雑なコードを書いていたとしましょう。

フォームのフィールド名とカラム名が一致しているんですよ?しかも、HTMLってF12を押してごにょごにょすると、構造を変えることができましたよね?察しのいい方ならもうお気づきですね。

必要のないフィールドまでも登録することができる。

例えば、ちょっとしたWebアプリケーションでユーザーを登録するフォームで考えましょう。ユーザーには権限がありその権限で使用できる機能が違うというものです。

フォーム側のフィールド

  • 名前(name)
  • パスワード(password)

データベースのカラム

  • 名前(name)
  • パスワード(password)
  • 権限(role)

実際に見てみましょう。まずはちょこっといじって権限の値をhiddenで埋め込みましょう。

フォームを改ざん

受け取ったリクエストが以下になります。

想定外のリクエスト

何も考えなければ名前とパスワードを登録するだけになりますが、上記のようにhiddenで権限のフィールドを作り送信するとどうなるでしょう。

なんということでしょう。不必要な値まで受け取って、ちゃっかりデータベースに登録まですることになります。

最初に紹介したようなワンライナーでの書き方にすると勝手に複数代入が行われるので、管理者の権限で登録される可能性がありますね。

といった具合に、意図していない挙動なのに正常に動作するという危険性があるということです。

対策

このリスクを防ぐためにLaravel4から導入されたのが、むやみに複数代入させないためのプロパティです。

このプロパティは$fillableという配列になり、Model(Illuminate/Database/Eloquent/Model )クラスが持っていますが、artisanコマンドで作成したモデルクラスはModelクラスを継承しています。

作成したモデルクラスでこの$fillableをオーバーライドして、複数代入を可能にするカラムを指定します。$fillableはホワイトリスト方式と呼ばれる指定方法で、ここで指定したフィールド名のみが複数代入されるようになります。

複数代入を指定するプロパティ

それとは別に、$guardedというプロパティもあり、これはブラックリスト方式になります。つまり、ここで指定したフィールド名"以外"が複数代入されるようになります。

$guardedを使う機会はほとんどないと思いますが、$fillable必ず指定するようにしましょう。そもそも指定しないとMassAssignmentExceptionというエラーが投げられます。

まとめ

複数代入は便利だけど、使い方を間違えるとガバガバwebアプリケーションになっちゃうゾ☆ってことですね。

とは言ったものの、Laravel5ならミドルウェアがあったり複数代入を抑制する仕組みが最初から実装されているのでそこまで気にしなくてもいいかもしれません。

ただ、Laravel5.2は少し変更されています。5.2より前のバージョンで自動的に読み込まれていた複数のミドルウェアクラスがwebというキーでまとめられているので、routes.phpでこのキーを指定して有効化する必要がありますね。

Laravel5.2でのweb用ミドルウェアの指定

追記

Laravel5.2.41で確認したところ、ミドルウェアはすでに実装されていました。私の勘違いだったようです。

Laravel5.2でのweb用ミドルウェアの指定の追記