【Laravel】コードリーディングで理解する Request::get() InteractsWithInput::input() の違い
はじめに
最近、Pull Request のレビューをしていて、Laravel でHTTPリクエストから任意のパラメータを取得する方法として、Illuminate\Http\Request::get()
(以降、Request::get()
)の誤用が多いと感じました。
調べてみると、同内容の記事は多くWeb上に存在していましたが、ソースを追いながら解説している記事はなさそうだったので、この記事では、コードリーディングを通して、なぜ Illuminate\Http\Concerns\InteractsWithInput::input()
(以降、InteractsWithInput::input()
) を使用するべきかを明らかにしていきます。ソースコードは Laravel 12 を使用します。
Request::get()のコードリーディング
Illuminate\Http\Request::get()
/**
* This method belongs to Symfony HttpFoundation and is not usually needed when using Laravel.
*
* Instead, you may use the "input" method.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
#[\Override]
public function get(string $key, mixed $default = null): mixed
{
return parent::get($key, $default);
}
PHPDoc コメント
This method belongs to Symfony HttpFoundation and is not usually needed when using Laravel.
Instead, you may use the "input" method.
(翻訳)
このメソッドはSymfony HttpFoundationに属しており、Laravelを使用する場合には通常必要ありません。
代わりに、「input」メソッドを使用できます。
明示的に InteractsWithInput::input()
の使用が推奨されています。
処理としては、親クラスのget()
を呼び出すのみですので、そちらも読んでいきましょう。
Symfony\Component\HttpFoundation\Request::get()
/**
* Gets a "parameter" value from any bag.
*
* This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
* flexibility in controllers, it is better to explicitly get request parameters from the appropriate
* public property instead (attributes, query, request).
*
* Order of precedence: PATH (routing placeholders or custom attributes), GET, POST
*
* @internal use explicit input sources instead
*/
public function get(string $key, mixed $default = null): mixed
{
if ($this !== $result = $this->attributes->get($key, $this)) {
return $result;
}
if ($this->query->has($key)) {
return $this->query->all()[$key];
}
if ($this->request->has($key)) {
return $this->request->all()[$key];
}
return $default;
}
処理
このクラスのプロパティ$this->attributes
は Symfony の ParameterBag
オブジェクト型でパスパラメータが格納されます。また、$this->query
はクエリパラメータが、$this->request
はリクエストボディのパラメータが格納されます。
つまり、以下の優先順位で値を探し、最初に見つかったものを返す処理となっています。
- パスパラメータ
- クエリパラメータ
- リクエストボディのパラメータ
また、各々のパラメータの配列の最上位層のキーしか指定できないので、ネストされた値を直接取得できません。
コメント
Gets a "parameter" value from any bag.
This method is mainly useful for libraries that want to provide some flexibility. If you don't need the flexibility in controllers, it is better to explicitly get request parameters from the appropriate public property instead (attributes, query, request).
Order of precedence: PATH (routing placeholders or custom attributes), GET, POST
Internal:use explicit input sources instead
(翻訳)
任意のバッグから「パラメータ」値を取得します。このメソッドは、柔軟性を提供したいライブラリで主に有用です。コントローラーで柔軟性を必要としない場合は、代わりに適切なパブリックプロパティ(属性、クエリ、リクエスト)からリクエストパラメータを明示的に取得する方が適切です。
優先順位:PATH(ルーティングプレースホルダまたはカスタム属性)、GET、POST
内部:代わりに明示的な入力ソースを使用してください
コメントにも、リクエストパラメータの取得に優先順位があることの注意書きがされています。
例えば、/users/123?id=abc
のようなURLに POSTリクエストが送られた場合、id
としてクエリパラメータの'abc'
を期待していても、パスパラメータの'123'
を返してしまいます。
この挙動を必要としない場合は、より限定的なプロパティから必要なパラメータを明示的に取得することが推奨されています。
まとめ
Request::get()
のコメントには、代わりにinput()
を推奨する旨が書かれている- 呼び出される Symfony の
Request::get()
はリクエストパラメータの取得に優先順位があり、使用側はどの入力ソースから値を取得するのかを明示的に指定する方が安全で確実 - ネストされた値を直接取得できない
InteractsWithInput::input()のコードリーディング
Illuminate\Http\Concerns\InteractsWithInput::input()
/**
* Retrieve an input item from the request.
*
* @param string|null $key
* @param mixed $default
* @return mixed
*/
public function input($key = null, $default = null)
{
return data_get(
$this->getInputSource()->all() + $this->query->all(), $key, $default
);
}
InteractsWithInput
はトレイトで Illuminate\Http\Request
で使用されます。
data_get()
はLaravelのヘルパ関数で、「ドット(.
)」表記を使用して配列またはオブジェクトから項目を取得します。例えば、data_get($products, 'products.0.name', 'デフォルト名')
などとして、ネストされたデータにアクセスできます。
Illuminate\Http\Request::getInputSource()
/**
* Get the input source for the request.
*
* @return \Symfony\Component\HttpFoundation\InputBag
*/
protected function getInputSource()
{
if ($this->isJson()) {
return $this->json();
}
return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
}
$this->getInputSource()
で呼び出される、Request::getInputSource()
は、リクエストのHTTPメソッドが GET か HEAD の場合はクエリパラメータ、それ以外の場合はボディのパラメータを取得します。レスポンスの型はInputBag
型です。
$this->query
はクエリパラメータが格納されるプロパティで型は同じくInputBag
型です。
そして、その両方に->all()
としてParameterBag::all()
を呼び出し、配列として取得しています。InputBag
はParameterBag
を継承しています。
取得した配列同士を配列結合演算子(+
)で結合しています。これは、両方の配列に存在するキーについては左側の配列の要素が優先され、 右側の配列にあった同じキーの要素は無視されます。よって、リクエストのHTTPメソッドが GET, HEAD 以外の場合は、ボディのパラメータが優先されます。
つまり、以下の優先順位で値を探し、最初に見つかったものを返す処理となっています。
- リクエストボディのパラメータ(リクエストボディを含めることができるHTTPメソッドの場合)
- クエリパラメータ
パスパラメータは含まれないので、Illuminate\Http\Request::route()
を使用して明示的に取得します。
まとめ
InteractsWithInput::input()
は内部でdata_get()
を呼び出すので、キーに.
を使用してネストしたデータにアクセスできる- 「リクエストボディが存在するHTTPリクエストの場合、URLのクエリパラメータよりも優先して取得する」処理になっている
全体まとめ
Request::get()
とInteractsWithInput::input()
はどちらも、キーの取得に優先順位を持ち、「よしなに」パラメータを取得してくれるメソッドである点は似ています。ただ、Request::get()
のパス > クエリ > ボディの優先順位は複雑です。パスパラメータがクエリやボディのパラメータを上書きしてしまう挙動はバグの温床になりえます。対して、InteractsWithInput::input()
の優先度はシンプルであり、フォーム送信のような一般的なユースケースを想定するとリクエストボディのパラメータを優先することは自然な挙動と言えると思います。また、.
でネストしたデータにアクセスできる点でも優れています。これらの優位性があるため、InteractsWithInput::input()
の使用が推奨されています。
このように、ソースコードで理解すれば、普段のコーディングにさらに自信がもてるし、難解なドキュメントを読む必要がなくなりますね!(なにより楽しい)