太陽がまぶしかったから

C'etait a cause du soleil.

PHP 初心者による Laravel 入門 その1 ルーティングとアクション

PHP 初学者による Laravel 基礎入門

 上記で作成した Docker 開発環境を用いて、Laravel に入門してみる。ちなみに筆者は PHP どころか Web 開発の経験がほぼないため、初歩の初歩からとなる。

 基本的には上記書籍を参考にしながら手を動かしている段階。

ルーティング機能を理解する

 Webサービスを作るのにあたって、最初に理解すべきなのが URL パターンと起動処理の対応定義であろう。 Laravel では routes/web.php に、作成されているとのこと。

<?php

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

 一般的にこの手の定義は XML や JSON で作られると思われるが、web.php の中身はプログラムそのもの。この例では / に GET メソッドでアクセスしたら view('welcome') の結果を返却する。routes/channels.php などもあるが、まずは web.php を使えばよさそう

テンプレートエンジン blade を使ってみる

 welcome を引数に view 関数を利用すると blade という Laravel 組み込みのテンプレートエンジンが起動され、 laravelapp/resources/views/welcome.blade.php をレンダリングした結果を返す。ディレクトリやファイル名は規約的に決まっているようだ。 welcome.blade.php の中身の一部は以下の通り。

   @if (Route::has('login'))
                <div class="top-right links">
                    @auth
                        <a href="{{ url('/home') }}">Home</a>
                    @else

 html を中心としながら if 文や変数展開がある感じ。Webアプリケーションは html を出力する際の print 文地獄を回避するために、テンプレートエンジンが整備される必然性があるのだろう。

ルーティングとテンプレートエンジンを組み合わせる

 上記を踏まえて hello.blade.php というテンプレートを追加してみる。

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <style>
        .flex-center {
                align-items: center;
                display: flex;
                justify-content: center;
            }
        </style>
    </head>

    <body>
        <div class="flex-center">Hello World !!</div>
    </body>
</html>

 このテンプレートエンジンの起動と /hello というパスを web.php で紐付ける。

<?php

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

 ファイルをサーバーに反映して http://localhost:50000/hello にアクセス。

f:id:bulldra:20180501204422p:plain

 でてきた。こういうのって素直に嬉しい。view 関数の結果はあくまで文字列なので、 web.php 内で定義する関数の戻り値に直接 <html>〜 とやってもよいが地獄なので試さない。

ルートパラメータの利用

 ルートを定義する際には {パラメータ名} でにはURL文字列の一部をパラメータとして利用することができる。 {パラメータ名?} とすることで必須項目ではなくなり、関数側でデフォルト値が定義可能。パラメータは view の第二引数に連想配列として渡すことで、テンプレートエンジンから利用できる。

<?php

Route::get('/hello/{target?}', function($target='world') {
    return view('hello', ['target' => $target]);
});
    <body>
        <div class="flex-center">Hello {{ $target }} !!</div>
    </body>

f:id:bulldra:20180501205759j:plain

 URL文字列を変数としてスムーズに扱えると、いわゆる RESTful API が作りやすくなるので、なかなかよさそう。

コントローラーを作成する

model: アプリケーションデータ、ビジネスルール、ロジック、関数
view: グラフや図などの任意の情報表現
controller: 入力を受け取りmodelとviewへの命令に変換する

Model View Controller - Wikipedia

 自分の知ってるMVCモデルではビジネスロジックはモデルの責務だと考えているが、Laravel における MVC モデルにおいてはモデルはあくまでDBアクセスに特化してコントローラー側に処理を書いていくのが一般的rしい。php artisan make:controllerコマンドでコントローラーの雛形が作成可能。バックグラウンド起動している Docker コンテナに命令するには docker exec を利用する。

$ docker exec phpapp php artisan make:controller HelloController
$ docker cp phpapp:/var/www/laravelapp/app laravelapp/

 上記のコマンドで、HelloController というコントローラークラスを生成してをローカルに取得。 確認するとapp/Http/Controllers/HelloController.php が作成されている。大したファイルではないので、手動で作った方が早いか。

コントローラーをルーティングに割り当てる

 HelloController.php を以下のように編集。追加した関数の責務も html の返却なので、 view を使って blade を起動させる。ついでに、現在日付を表示させてみる。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HelloController extends Controller
{
    public function hello($target='world') {
        return view('hello', ['target' => $target, 'date' => date("Y/m/d")]);
    }
}

 web.phpクラス名@関数名 形式で URLに対応して起動するアクションを定義。

<?php

Route::get('/hello/{target?}', 'HelloController@hello');

 URLに定義したパラメータは対応する関数を実行する際の引数となる。

シングルアクション定義

 クラスに定義される関数が一種類の場合には __invoke 関数として定義することで、ルーティング定義をクラス名だけに省略できる。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HelloController extends Controller
{
    public function __invoke($target='world') {
        return view('hello', ['target' => $target, 'date' => date("Y/m/d")]);
    }
}
<?php

Route::get('/hello/{target?}', 'HelloController@hello');

Laravel で HTTP リクエストと HTTP レスポンスを利用する

 ブラウザとサーバーの間でやりとりされるリクエストとレスポンスは、引数の型指定でそれぞれ、RequestResponse を追加しておくことで、 web.php で特に引数定義しなくても利用可能となる。ちょっとキモい。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;

class HelloController extends Controller
{
    public function __invoke(Request $req, Response $res, $target='world') {
        return view('hello', ['target' => $target, 'url' => $req->url(), 'status' => $res->status()]);
    }
}
<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <style>
        .flex-center {
                align-items: center;
                display: flex;
                justify-content: center;
            }
        </style>
    </head>

    <body>
        <div class="flex-center">{{ $status }} Hello {{ $target }} !!</div>
        <div class="flex-center">{{ $url }}</div>
    </body>
</html>

f:id:bulldra:20180501221601j:plain

 View が返しているのも厳密には html ではなく、レンダリング結果が格納されたResponse オブジェクトだそう。ここまでの開発でブラウザからのリクエストを受け取り、それに応じて処理を切り分けることが可能となった。次回はさらに踏み込んだ機能を使ってみる。