Taglibro de miyacorata

Vi fine falos en la marĉon.

草を生やしてP生活を向上させる話 Laravel編

この記事は「NITKC Prolab Advent Calendar 2019」の12月16日の記事として書かれており、また「Except imas (エクマス) Advent Calendar 2019」の12月12日の記事の続編です。

ん?12月16日?

お誕生日おめでとうございます

今日は中谷育さんのお誕生日です!おめでとうございます🎂🎉

ちなみに中谷育ちゃんについてはこちらをご覧いただければ幸いです。

[blogcard url="https://millionlive.miyacorata.net/idol?name=ikunakatani"]

さて、アドカレの話に戻りましょう。

昨日の記事はキョウくんの「Kotlinでのエラーハンドリング」でした。

[blogcard url="https://kyoutoday.qrunch.io/entries/64IYC8Ye81WyzA5L"]

エラーハンドリングはクソほど重要です。ちょっと意味合いが変わりますがPHPは特にやらかしたときに真っ白いページだけ帰ってくることが割とよくあります。つらい。

ウェブページの場合は403(見る権限がない)なのか404(ページがない)なのか503(サーバがつらい)なのか500(プログラムかサーバがおかしい)なのかちゃんとユーザに伝えるべきです。真っ白になるのはだいたい開発者がポカをしている場合なので頑張りましょうって感じですね、お前のことやぞみやこらた。

前回のあらすじ

[blogcard url="https://blog.miyacorata.net/tsurezure/2019/12/producer-life-with-php-and-db/"]

PHPとデータベースに魔法をかけてわんだほー!なウェブサイトをつくるのです!」

本題

MillionLivePortalの次期WebシステムOuranosを例にお話ししていきます。

[blogcard url="https://github.com/project-alisa/Ouranos"]

Laravelを知る

LaravelとはMVCPHPのWebアプリケーションフレームワークです。フレームワークというのは要は枠組みのことで、本来であれば1からぜんぶ書かなければならない標準的な実装をまとめあげ使いやすくしたものを指します。今回OuranosはLaravelを採用しました。

MVCとは

LaravelはMVC、ModelとViewとControllerの3つの要素で動くアーキテクチャ(仕組み)を採用しています。

簡略図

ユーザからリクエストを受けたLaravelアプリケーションはリクエストから適切なコントローラへルーティングを行い、コントローラがさらに受け持つ処理によってモデルへデータ操作要求などを行い、モデルはデータの取得や変更などを行い、データをコントローラへ返します。データを受け取ったコントローラはそれを加工したりしなかったりした後、ビューにデータを固めて送り描画を指示します。ビューはデータを予め用意されたテンプレートに則りページを構築し、ユーザの画面で表示されるというわけです。

artisanを活用する

LaravelにはartisanというCUIツールが用意されています。artisanとは英語で職人を指す単語であるのはトェイク5億の皆さんには周知の事実かと思いますが、こいつはまさしく職人です。

artisanではデータベースのマイグレーション(データベースの更新とか)、コントローラ・モデルなどの新規作成、Composer(PHPのパッケージ管理システム)でインストールしたLaravel向けパッケージを展開・自動設定したりもしてくれます。artisanと仲良くなれたらLaravelでの開発はよりスピードアップできます。

Laravelでアプリを作る

さっきのMVCの3要素とルーティングを軸に作っていきます。今回はOuranosの中でアイドルに関するページの処理を受け持つControllerとModel、そしてViewを作っていきます。開発環境はXAMPPかLAMPお好きな方でウェイと構築しちゃってください。Composerも忘れずに。

Controllerを作る

処理の司令塔Controllerを作ります。早速artisanの手を借りましょう。

php artisan make:controller IdolController

このコマンドで IdolController という名前のコントローラを作成できます。

ところで、CRUDに沿ったコントローラがあればよりアプリを作りやすそうです。もちろんartisanはCRUDルールに則ったリソースコントローラも作成してくれます。上記のコマンドに --resource というフラグを渡すだけです。今回ユーザーに対してCRUDのうち必要なのはR(Read)だけですがなんとなくリソースコントローラを採用したほうがルーティングがすっきりするのでこれを使うことにします。

次の実装でModelをうんぬんとかをする前に、Modelを作りましょう。

Modelを作る

といっても今回あんまりモデルを実装していません(だめじゃん) Modelは以下のコマンドで作成できます。

php artisan make:model Idol

LaravelのModelには命名規則が一応あり、Modelは単数、DBのテーブル名は複数形であることとされています(プロパティ$tableをオーバーライドすることで規則に準じない名前を指定することも可能です)。

続いてデータベースを使う準備をしましょう。Laravelではマイグレーションファイルを書くことでartisanがデータベースをすべてセットアップしてくれます。予めconfig/database.phpあるいは.envに接続情報を書くことを忘れずに。マイグレーションファイルもartisanがひな形を作ってくれます。

php artisan make:migration create_idols_table
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateIdolsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('idols', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name',30);
            $table->string('name_y',30);
            $table->string('name_r',50)->unique();
            $table->string('subname',30)->nullable();
            $table->integer('name_separate')->nullable();
            $table->integer('name_y_separate')->nullable();
            $table->integer('name_r_separate')->nullable();
            $table->string('type',30);
            $table->date('birthdate');
            $table->integer('age');
            $table->integer('height');
            $table->double('weight');
            $table->enum('bloodtype',['A','B','O','AB']);
            $table->string('handedness',2);
            $table->integer('bust');
            $table->integer('waist');
            $table->integer('hip');
            $table->string('birthplace',30);
            $table->string('hobby',50)->nullable();
            $table->string('skill',50)->nullable();
            $table->string('favorite',50)->nullable();
            $table->string('cv',30)->nullable();
            $table->string('color',7)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('idols');
    }
}

マイグレーションファイルには2つのメソッド up() と down() があります。up()は新たな変更を、down()にはそれをロールバックする処理を書きます。

このマイグレーションファイルはup()でスキーマを元にidolsテーブルを新規設定するコードになります。down()はテーブルのドロップですね。SQLを直書きしなくて良く、コードも直感的でわかりやすく、型とカラム名と制約とかをほいほい書けます。とてもいいですね。

これができたらartisanにマイグレーションを頼みましょう

php artisan migrate

これでデータベースを使う準備が整いました。

Modelに関して、よく使う共通の処理があればModelに予め実装してしまうのが良いですが、今回はとりあえずしないことにします。よいこのみんなはコードを再利用できるようにするため以外の目的で別のソースにコピペしちゃだめだぞ。

Viewを作る

では続いてViewを作ります。ページ作りですね。LaravelはBladeという"シンプルながらパワフル"(原文ママ)な超有能テンプレートエンジンを使うことができます。

BladeはシンプルながらパワフルなLaravelのテンプレートエンジンです。他の人気のあるPHPテンプレートエンジンとは異なり、ビューの中にPHPを直接記述することを許しています。全BladeビューはPHPコンパイルされ、変更があるまでキャッシュされます。つまりアプリケーションのオーバーヘッドは基本的に0です。Bladeビューには.blade.phpファイル拡張子を付け、通常はresources/viewsディレクトリの中に設置します。

Bladeテンプレート https://readouble.com/laravel/5.8/ja/blade.html

こいつは非常に優秀で、例えば {{ $hoge }} という具合に書くと変数 $hoge を適切にエスケープ(EScape!?!?!?)した上でHTMLとして出力できます。二重波括弧がPHPでいう echo htmlspecialchars(~~~) を担っています。

注釈:ご存知の方も多いかとは思いますが、Webアプリケーションの世界でもXSS(クロスサイトスクリプティング)など悪意のあるコードを出力してしまいユーザに対し悪影響を及ぼさないよう、HTMLの出力にあたってDBやフォームの値などに含まれるブラウザで特定の動作を起こす文字列(HTML要素のタグなど)を無力化するエスケープという処理が必要です。詳しくはXSSで検索。

もちろんこの中には三項演算子エルビス演算子などを用いた式を入れることも可能です。値がnullである場合はその旨をユーザに示す"N/A"などの文字列を変わりに出力させることが可能です。

Bladeは非常に便利なディレクティブをもっており、ifやswitchはもちろん、for、foreach、またforeachに要素数が0の場合も記述できるforelseなど、PHPを記述するよりこれらのディレクティブを使ったほうが書きやすいことが多いです。これらは先程の引用の通りLaravelによってPHPコンパイルされます。

@if (count($records) === 1)
    1レコードある!
@elseif (count($records) > 1)
    複数レコードある!
@else
    レコードがない!
@endif
@for ($i = 0; $i < 10; $i++)
    現在の値は: {{ $i }}
@endfor

@foreach ($users as $user)
    <p>これは {{ $user->id }} ユーザーです。</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>ユーザーなし</p>
@endforelse

@while (true)
    <p>無限ループ中</p>
@endwhile

またテンプレートの継承とセクションが使えるのも非常に大きな魅力の一つです。

例えばOuranosの場合、画面上のヘッダーメニューとフッターに入れるものは決まっているわけで、ならそこはそこで作っておいて本文エリアは別ファイルを読みたいですね?

こういう時、共通部分であるテンプレート app.blade.php を作成し、その中で @yield ディレクティブで継承読み出しができるようにします。

GitHubこのあたりにありますが、layouts/app.blade.php にcontentという名前のyield(どういう訳が適切かわからん)があり、idol/show.blade.php(アイドルの情報ページのview) から継承呼び出しをしたりしてます。詳しくはGitHubリポジトリ見てみてくださいね(丸投げ)

Controllerを作り込む

テンプレートができたところでコントローラを作り込みましょう。

アイドルの情報に関するページを作るためのコントローラですが、これをリソースコントローラとして定義していますのであらかじめCRUDに応じたメソッドが定義されています。ところが、今回はアイドルの情報は管理者以外に編集させるわけにはいきませんし、管理画面は別に用意してしまったので今回実装する必要があるのはR(Read 読み取り)メソッドだけです。index(一覧)とshow(詳細表示)のメソッドをさっと作りましょう。

<?php

/* いらんコードと本筋に関係ないコードを端折っているので全部見たい人は
 * https://github.com/project-alisa/Ouranos/blob/master/app/Http/Controllers/IdolController.php
 * を見てね(中国語韓国語対応コードと無効化したメソッドの記述とかある)
 */

namespace App\Http\Controllers;
use Illuminate\Http\Request;
class IdolController extends Controller
{
    /**
     * アイドルのリストだすよ
     */
    public function index()
    {
        $idols = \App\Idol::all();
        return view('idol.index',compact('idols'));
    }

    /**
     * アイドルの詳細情報出すよ
     *
     * @param  string  $name_r
     */
    public function show($name_r)
    {
        $idol = \App\Idol::where('name_r', 'like', $name_r)->firstOrFail();
        return view('idol.show',compact('idol'));
    }
}

ここでModelでなんも書いてないのにデータをウェイソイヤして取得していることにお気づき律子かと思います。Eloquentという超使い勝手の良いModelからSQLっぽくデータを取得したりできるFacadeがあるのでこれを使います。

Facade(ファサード)とはフランス語に由来する建築用語で「建物の正面」を指します。(Laravelのドキュメントでは「入り口」と表現されていますが) 具体的に何をしてるかと言うと複数のあるいは複雑なサブシステム群に対して一つの窓口(インターフェース)を提供しています。

これはすげえ強いことで、例えばEloquentやDBなどのファサードの場合、DBMSMySQLであってもPostgreSQLでもSQLiteでもファサード使えば同じコードで処理が行えます。やさしい。

ルーティングを張る

一通り3要素出来上がったところでルーティングを作成します。Laravelくんはルーティングを明示的に定義してコントローラへ処理を投げさせる必要があるのでそれを作りましょう。

といってもやることは簡単、routes/web.php に規則を書くだけです。

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'InfoController@home');

Route::get('/clock', 'InfoController@clock');

Route::get('/search', 'SearchController@search');

Route::get('/about', 'InfoController@about');

Route::resource('idol','IdolController',['only' => ['index','show']]);

IdolControllerはリソースコントローラとして作成しました。リソースコントローラはルーティング定義において Route::resource() を用いることでCRUDに沿ったルーティングを張ってくれますのでめちゃくちゃ優秀です。この時第1引数がパスになり、Webブラウザ上で /idol/ でリストが、/idol/{アイドル名} で詳細ページという具合にルーティングされています。

リソース(CRUDを用いる)でない普通のコントローラも Route::get() でルーティングを張れます。第1引数がパスになるのはRoute::resource()と同じです。トップページや検索などはこちらの方法でルーティングしています。

できた

システムの基幹部分は完成しました!

ん?

そうです。基幹部分しか完成していません。

実際にはこれにCSSによるスタイリングとデータベースへのデータの流し込みとかが必要なのでもうちょっといろいろないろいろが必要なのですが、それを記すにはこの記事の余白というかぼくの可処分時間が短すぎるので気になる人は自分で調べるかぼくに賄賂を送るなりすればまた続編として書くかもしれません。っていうかGitHub見て。

ちなみにこれらを全部ひっくるめるとこんなのができます。

IdolController::index() | /idol

IdolController::show() | /idol/{name}

Ouranosはもうちょっと手直しをしてから年越しを目処にリリースしたいなって感じです。もうちょっとお待ち下さいね。

明日のNITKC Prolab Advent Calendarはとばりくんの「HTTF本選三か木とか」です。大丈夫でしょうか。たぶん大丈夫でしょう。

みなさまもよいクリスマス、よい年越しをお迎えくださいね。ぼくはクリスマスまでタスク祭りです。やめたい。

Laravelぜひやってみてね~

おわりだよ~(o・∇・o)

みやこらた