Laravel入門 GETメソットとPOSTメソッドで学ぶLaravelでの処理の流れ(第5回)

2021-01-01
10 min read

今回は、GET メソッドと POST メソッドの 2 つで Laravel での処理の流れを見ていきます。 簡単なプログラムで処理の流れを見ていきましょう。

GETメソッドでのLaravelでの処理の流れ

GET メソッドというものはどのようなものでしょうか?

よく使われる場面としては、ブラウザのアドレスバーに URL を入力してエンターを押すときです。 その時に、ブラウザからサーバ側に GET メソッドのリクエスト(要求)が送信されます。 GET メソッドは、URL で指定したファイルを要求するものです。

たとえば、Yahoo!ニュースにおいて、ある記事の URL は下記となっています。

https://news.yahoo.co.jp/articles/13bb80852778d4d975b05126a51faf543c320357

「/」で区切られた最後の英数字「13bb80852778d4d975b05126a51faf543c320357」が、記事の ID になっていると思われます。サーバ側ではこの ID をもとに記事を取得して、ブラウザに渡しています。

これと同様のしくみのものを Laravel で用意しました。動作させて中身をみていきましょう。(slackにて配布)

ブラウザのアドレスバーにhttp://localhost/articles/10001と打ち込みエンターキーを押します。

すると、ブラウザに、「10001 の記事です」と表示されましたよね。

裏では次の処理が動いています。

  • ブラウザからサーバ側に要求(リクエスト)が送信されます。
  • サーバ側で返信(レスポンス)を作成して、ブラウザに返します。

サーバ側で返信(レスポンス)を作成する部分で、Laravel が動いています。 Laravel では次の動作が行われています。

  • ルータにて指定された URL に紐付いた主処理を呼び出す
  • コントローラにて主処理を行う
  • ビューにて主処理の結果を取り込んだ画面を作成する

詳細に説明していきます。

ルータ

送信されたリクエストは、サーバ側から Laravel に渡されて、Laravel では事前に定められた URL のarticlesに紐付いたコントローラを呼び出します。

URI(articles)とコントローラの紐付けは、routes/web.phpで行います。

Route::get('articles/{id}', 'BasicController@showArticle');

これは、GET メソッドでarticleが呼び出されたとき、BasicControllershowArticle メソッドを呼ぶという設定です。

また、articles/{id}の中括弧(braces,curly bracket)の部分は、パラメータを表しています。 このパラメータは、呼び出されるコントロールのメソッドの引数として自動で設定されます。

コントローラ

紐付けられたコントローラのshowArticleメソッドの処理は下記になります。

//app/Http/Controllers/BasicController.php
public function showArticle(Request $request,$id) {

    //本来ならデータベースなどの外部記憶媒体から$idで指定された記事を取得
    $data =['article' => "{$id}の記事です。" ];
    return view('show-article', $data);

}

Laravel は、ルートの設定に従って、showArticleメソッドを呼び出します。メソッドのパラメータである$request$idの引数(argument)は、Laravel が自動で設定します。

$idは、route で指定したパラーメーターのことです。$requestは、リクエスト情報をまとめたオブジェクトです。

Request の詳細は下記で参照できます。

https://laravel.com/api/7.x/Illuminate/Http/Request.html

このメソッドでは、引数で受け取った$id をキーとして、記事を取得する処理が実行されます。

ここでは説明を簡略化するために記事取得は省略しています。 その代わりに、"{$id}の記事です。“という文字列を表示します。

文字列を表示するために、articleをキーとして文字列を連想配列$dataに格納しています。 その後、$data と、show-article という文字列を引数としてviewヘルパというヘルパ関数を呼び出しています。

Laravel のヘルパ関数は、どこからでも呼び出せるグローバル関数です。 ユーティリティ的に使える関数がいくつも用意されています。 https://readouble.com/laravel/8.x/ja/helpers.html

View

view ヘルパは、"<第一引数で指定された文字列>.blade.php"というファイルを、 resources/views/以下で探します。

ここでは、show-article.blade.phpというファイルになります。

<!-- resources/views/show-article.blade.php -->
<html>
    <head>
        <title>show article</title>
    </head>
    <body>
        {{ $article }}
    </body>
</html>

これは Laravel のテンプレートエンジンである Blade で書かれたものになります。 テンプレートエンジンというのは、事前に用意したひな型(テンプレート)に、文字を差し込む(埋め込む)ものです。

このshow-article.blade.phpは、ほとんどが HTML ですが、一ヵ所だけ{{}}で囲まれた部分(6 行目)が文字を差し込む箇所です。 view ヘルパの第二引数として渡した連想配列 $data の中から、article を key として値を取得します。 data['article'] として記述するのではなく、$article として記述することになります。 テンプレートエンジン内部で、extract 関数で連想配列を展開しています。

レスポンス出力

コントローラーの処理終了後に、Laravel は、html を作成し、ヘッダを付与して responce としてサーバ経由でブラウザに返します。

レスポンスは、ヘッダ+コンテンツで構成されています。コンテンツはブラウザにて目視できますが、ヘッダは普段、見ることはありません。 Laravel がどのようなヘッダを返しているかを見てみましょう。

$ curl -I http://192.168.1.10:81/showname

HTTP/1.1 200 OK
Date: Wed, 11 Nov 2020 03:55:47 GMT
Server: Apache/2.4.38 (Debian)
X-Powered-By: PHP/7.4.0
Cache-Control: no-cache, private
Set-Cookie: XSRF-TOKEN=eyJpdiI6IkJsUXFNSTNCWUdGY1NOUHdiZ1hqRHc9PSIsInZhbHVlIjoiSkxVUDZIbkd0bTlkZGhKR21XUVVrcXdHNWRyUE50Zkc4TVdQcm1vdmwyV1pPbm9kNW1zVDJzSE4xaVNkcXFaR1FwR0R4Um5tT0czSGdiZ043TkJOUGZOa3B1c3IwdnRobXB5dzIzWGoyZVhPN28zM2ZJeGVyRHZWUG9BL0hxbEIiLCJtYWMiOiI0MTI1MDI0M2QwZmI2YzA1OTY0MDFmZGQ0MzQ1NDNmNTliYTljYTI4N2ZhZGYwMTY1YmE2NGQ4YmU0Yzg4YTM3In0%3D; expires=Wed, 11-Nov-2020 05:55:47 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6Ikw0NWVyUkdxeVovU2ZIL3doaTBqQ0E9PSIsInZhbHVlIjoiVytWeldKOGlaREJCdStDQlUvQWFEZXVHRGVHWWozMWxLVjZieFYrSlFWTGN5RUFUc2t3bEZFYnRJaGRLcldOaGo4K0xCK0M0bWlCUGtIUHJBTHp6MkN6a0NGa0dxb3lhYzdXNkZhTnB5RzZnVW5zcWVTdENXVmRKWWNESmNwWkIiLCJtYWMiOiI1ODhiMTU5MmYyM2FkOGQwZGYzNjc4NjFiMzA0OWYyYTE1M2UyZWY3ZTg2YzJkNTI2YTlkNzljOGU3ZWY4MGFlIn0%3D; expires=Wed, 11-Nov-2020 05:55:47 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Vary: Accept-Encoding
Content-Length: 127
Content-Type: text/html; charset=UTF-8

curl(カール)コマンドで、-I オプションを付与することでヘッダを取得できます。

POSTメソッドでのLaravelでの処理の流れ

POST メソッドは、新規作成のデータをサーバに送信するとき、使われます。 ちなみに更新時は PUT メソッドになります。

http://localhost/shownameにアクセスしてください。

showname画面

画面の入力欄に適当な文字を入力して SUBMIT をクリックすると、入力した文字が画面に表示されます。

このしくみを見ていきましょう。

ルータ

GET メソッドの /showname のルーティングは下記となっています。

Route::get('/showname', function () {
    return view('testpost');
});

ここでは、コントローラを呼ばずに第 2 引数をクロージャ(無名関数)として、関数の中で VIEW ヘルパを呼んでいます。

view

testpost という VIEW は下記になります。

<html lang="ja">
    <head>
        <title>test post</title>
    </head>
    <body>
        <form method="POST" action="{{route('sendname')}}">
        @csrf
            <div>
                name:
                <input name="name" type="text" placeholder="Text input" >
            </div>
            <div>
                <button>Submit</button>
            </div>
        </form>
    </body>
</html>

submit ボタンをクリックすると、<form></form> で囲まれた中にあるフォームコントロール(INPUT,TEXT など)に入力された値が送信されます。 ここでは、input 要素の name という名前がついたフォームに入力された内容がサーバ側に送られます。 送信の際の HTTP メソッドは、POST 送信先は、{{route('sendname')}}になります。

レスポンス出力

この test-post.blade.php が、Laravel によって html 変換され、ブラウザでは下記が表示されます。

<html lang="ja">
  <head>
    <title>test post</title>
  </head>
    <body>
      <form method="POST" action="http://localhost/showname">
        <input type="hidden" name="_token" value="45TWw3dMuTfAHakN99KxUxKOUfLFIEbyTb1EGQlg">
            <div>
                name:
                <input name="name" type="text" placeholder="Text input">
            </div>
            <div>
                <button>Submit</button>
            </div>
        </form>
    </body>
</html>

先程の{{route('sendname')}}が [http://localhost/showname] に変換されています。

submit後のルータの処理

このように変換されるのは、routes/web.phpにて下記のようにname('sendname')として指定しているからです。

Route::post('/showname', 'BasicController@showName')->name('sendname');

それぞれのルートは上記のように名前をつけることができます。url が長くなるときは、名前をつけておくて便利です。

/shownameのアクセスは、BasicControllerクラスのshowNameメソッドに渡されます。

{{route('sendname')}}以外にも、@csrf というものが、<input type="hidden" name="_token" value="45TWw3dMuTfAHakN99KxUxKOUfLFIEbyTb1EGQlg"> に変換されています。これはのちほど詳しく解説します。

submit後のcontrollerの処理とレスポンス出力

ルートと紐付けられたBasicControllershowNameではどのような処理を行っているのでしょうか?

  public function showName(Request $request) {

      $name = $request->name;
      return  $name;

  }

リクエストから、nameと名付けられたデータを取り出します。 それをレスポンスとしてブラウザに返します。

GETメソッドとPOSTメソッドの違い

GET メソッドでは、リクエスト URL にパラーメーターを付け足すことでデータを送信します。 一方、POST メソッドでは、リクエストのボディ部(メッセージボディ)に FORM で記載されたデータを載せて送信しています。

方法が異なるとはいえ、どちらともデータを送信できます。 GET メソッドと POST メソッドの使い分けはどのようにすればよいのでしょうか?

インターネットの標準化団体 IETF(The Internet Engineering Task Force)が発行している、技術仕様があります。 RFC 文書のRFC2616 にそれぞれの用途が記述されています。

要約すると下記になります。

  • GET メソッドは、データの要求をするもの。
  • POST メソッドは、データの追加を要求するもの。

GET メソッドと POST メソッドでの用途が明確に違うようです。データの修正や追加などを行うときには GET メソッドではなくて POST メソッドを使うことになります。

まとめ

このように Web システムというのは、クライアントからの要求に対して返信をするものです。 要求をリクエスト(request)と言い、返信をレスポンス(responce)と言います。

responce というのは、html ファイルや画像などクライアンに返すものです。 responce を動的に作るのが、PHP の処理です。

Laravel は request と responce を処理する枠組みや便利なツールを提供しています。 Laravel の内部の動きは、下記で説明されています。 https://readouble.com/laravel/7.x/ja/lifecycle.html

CSRF防止について

Laravel は、POST メソッドのリクエストで、送信データに CSRF「トークン」がない場合、419 エラーを返します。 これは、サイバー攻撃の一種であるクロス・サイト・リクエスト・フォージェリ(CSRF)を防止するためのものになります。

CSRF「トークン」

Laravel では、CSRF「トークン」は、@csrf にて、付与できます。

Laravel は、サーバ側の CSRF「トークン」と要求元から送られてきたクライアント側の CSRF「トークン」を比較して、一致して初めて処理を行います。一致しない場合は、419 エラーを返し処理を中断します。

以下は、Laravel で CSRF「トークン」を比較しているメソッドです。

// Illuminate\Foundation\Http\Middleware\VerifyCsrfToken
/**
 * Determine if the session and input CSRF tokens match.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return bool
 */
protected function tokensMatch($request)
{
    $token = $this->getTokenFromRequest($request);

    return is_string($request->session()->token()) &&
           is_string($token) &&
           hash_equals($request->session()->token(), $token);
}

$this->getTokenFromRequest($request) で、request の中にあるクライアント側の CSRF「トークン」を取得しています。 また、$request->session()->token()でセッションに保存されているサーバ側の CSRF「トークン」を取得しています。その両者は、hash_equals で等しいかを確認しています。

CSRF「トークン」有無での動作の違い

実際に動作を見てみましょう。

CSRF「トークン」なしでのPOSTリクエスト

トークンを付与せずに POST メソッドでリクエストします。 curl(カール)というコマンドラインから Http リクエストを送信できるコマンドを利用します。

$ curl -b cookie.txt -X POST "http://localhost/showname"  -d "name=test"

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Page Expired</title>

        <!-- Fonts -->
        <link rel="dns-prefetch" href="//fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

        <!-- Styles -->
        <style>
            html, body {
                background-color: #fff;
                color: #636b6f;
                font-family: 'Nunito', sans-serif;
                font-weight: 100;
                height: 100vh;
                margin: 0;
            }

            .full-height {
                height: 100vh;
            }

            .flex-center {
                align-items: center;
                display: flex;
                justify-content: center;
            }

            .position-ref {
                position: relative;
            }

            .code {
                border-right: 2px solid;
                font-size: 26px;
                padding: 0 15px 0 15px;
                text-align: center;
            }

            .message {
                font-size: 18px;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            <div class="code">
                419            </div>

            <div class="message" style="padding: 10px;">
                Page Expired            </div>
        </div>
    </body>
</html>

419 のエラーになりました。

CSRF「トークン」付与でのPOSTリクエスト

次に CSRF「トークン」を取得してから、リクエストを送ります。

まずは、CSRF「トークン」を取得します。

$ curl -c cookie.txt http://192.168.1.10:81/showname

<html lang="ja">
    <head>
        <title>test post</title>
    </head>
    <body>
        <form method="POST" action="http://192.168.1.10:81/showname">
        <input type="hidden" name="_token" value="fGBm5KFPXH8PCqv9N58Bw7JO7sMgBYp69SYwv3mH">            <div>
                name:
                <input name="name" type="text" placeholder="Text input" >
            </div>
            <div>
                <button>Submit</button>
            </div>
        </form>
    </body>
</html>

上記レスポンスの_tokenvalue の値が、CSRF「トークン」になります。 _tokenの値をコピーして下記の XXXXXXXX と入れ替えます。

$ curl -b cookie.txt -X POST "http://192.168.1.10:81/showname"  -d "_token=XXXXXXXX&name=test"

test

test と文字が表示されましたよね。 CSRF「トークン」がサーバ側のものと一致したため処理が行われました。 CSRF「トークン」は、リクエストの度に異なるものが送信されサーバ側で保持する CSRF「トークン」も同様に書き換ります。また、有効期限は、セッションの保持期間と同じなります。

古い CSRF「トークン」を使用するとエラーになることを確認してみてください。