PHPのWebアプリフレームワークLaravelに入門してみたメモ（2018年版）

# はじめに

個人的にCakePHPでWebアプリを書いていたりしたんですが、最近は[Laravel](http://laravel.jp/)が来てるらしいというので、ぐちゃぐちゃになったアプリをLaravelで書き直してみるか、と思い立ちました。

何から始めればいいか分からないので、

- [初心者のためのLaravel入門](https://www.tuyano.com/index2?id=3766003)（掌田津耶乃氏）

のページを見ながら進めてみました。
しかし、記事が書かれたのが2016年とあって、その後のLaravelのバージョンアップに伴い、そのまま同じことをやっても動かない事態に。
というわけで、上の記事に沿ってLaravel 5.5の操作を進めるうえで、ハマったところをここに記録しておきます。
…というのが元々の動機だったのですが、調べていくとLaravelの流儀に従って実装した方がいいのではと思えるところもあって、後半はほぼそういう感じの話になっています。

なお、以下ではLaravelが新しくなって変わったところと、後述のようにLinuxで動かしているために変わるところが混ざっています。

# 前提条件

- Laravel 5.5
- OS: Linux (CentOS 7)
- PHP: 対応バージョン (7.0.23) をインストール済みで、パスが通っているとします。

# 1. Laravelをスタートしよう

https://www.tuyano.com/index3?id=11526003

## Composer

記事は、まずComposerを用意するところから始まります。
Windowsで動かすときはこの通りでOKなのでしょうが、Linuxでphpコマンドを使ったセットアップ（[Download Composer](https://getcomposer.org/download/)の「Command-line installation」参照）を行ったため、実行ファイルの名前が``composer.phar``になりました。
そのため、ファイル名を``composer``に変更するか、コマンド実行の説明部分で``composer``を``composer.phar``に読み替える必要がありました。

## 内蔵サーバーで動かす

``php artisan serve``コマンドでサーバーを立てて動作を試せます。本番環境だとApache使いたいですが、開発用には良いと思います。
デフォルトだとlocalhostでListen（待ち受け）しますが、LinuxのサーバーにSSHでログインしてプログラムを書くような場合だと、localhostで待ち受けられても困ります。というわけで

```:terminal
$ php artisan serve --host=192.168.123.45
```

みたいな感じで、--host オプションを付けましょう。
なお、ファイアウォールのチェックもお忘れなく。8000番を開けましょう。
``http://192.168.123.45:8000/`` にアクセスすると、デモページが出るはずです。

## appフォルダ

[5ページ目](https://www.tuyano.com/index3?id=11526003&page=5)に

> この「app」フォルダの中には、更にいくつものフォルダが用意されています。以下に整理しましょう。

と書いてあって、フォルダのリストが列挙されていますが、手元を見るとフォルダは「Console」「Exceptions」「Http」「Providers」しかないです。
まあ後で分からなくなったら調べればいいし、一番大事といわれているHttpフォルダは存在するので、とりあえずスルーしまして。
それで、その後を読むと

> 一番重要な「Http」フォルダの中には、更に複数のフォルダとファイルがあります。これらもざっとまとめておきましょう。
（中略）
とりあえず、「Controllers」フォルダと、routes.phpは、実際のプログラミングに入ったらすぐに使うことになります。この２つについては、頭に入れておきましょう。

とあるんですけど、なんと「routes.php」がありません。これは一大事の予感。

以下のページを見ると、5.3以降は``routes/web.php``に読み替えれば良いようです。

- https://atuweb.net/201609_where-did-router-laravel53/

以後、``routes.php``と書いてあったら``routes/web.php``だと思って読んでいきます。


# 2. ルーティングとコントローラー

https://www.tuyano.com/index3?id=5486003

前章の予告通り、いきなりroutes.phpが出てきましたが、これは先ほど調べたように``routes/web.php``だと思ってください。
``Route::group(○○);``の呼び出しなんて書いてないですが、とりあえず置いておきます。

## コントローラが呼び出せない

[5ページ目](https://www.tuyano.com/index3?id=5486003&page=5)に進むと

```php
Route::controller('helo', 'HeloController');
```

という記述が登場しますが、これを書くとサーバーを起動するときに

```
In RouteAction.php line 84:

  Invalid route action: [App\Http\Controllers\HeloController].
```

と言われました。えっなんで？？

実はLaravel 5.3で``Route::controller``は削除されたようです。
では5.5時点ではどう書けばいいかというと、以下のようにすればとりあえず動きます。

```php:routes/web.php
Route::get('helo', 'HeloController@getIndex');
```

## リソースコントローラ

但し、実際問題で考えると、他にもいろいろな機能を用意したいはずで、それぞれ``helo/show`` みたいな規則的なURIで呼び出せるようにしたいですね。
あまり``web.php``の記述量を増やしたくないし。

というわけで、「リソースコントローラ」を作ってみます。DBアクセスするアプリに必要になりそうな機能のベースを準備してくれるんだそうで。
最初のHeloController.phpはいったんリネームしておいて

```:terminal
$ php artisan make:controller HeloController --resource
```

というコマンドを叩くと、``app/Http/Controllers/HeloController.php`` ができます。
このファイルを開いて、``index()``メソッドの中を編集します。

```php:app/Http/Controllers/HeloController.php
    public function index()
    {
        return view('helo', ['message' => 'Hello!']);
    }
```

この中身は、最初のHeloController.phpで``getIndex()``メソッドに書いてあった中身と同じです。
そのうえで、web.phpの記述は

```php:routes/web.php
Route::resource('helo', 'HeloController');
```

とします。
``http://192.168.123.45:8000/helo`` にアクセスして、画面に Hello! と出ればOKです。

以下のページを参考にしました。

- https://laravel.com/docs/5.5/controllers

# 3. Requestクラスの基本

https://www.tuyano.com/index3?id=3786004

## POSTするとエラーになる？

最初のページでViewにフォームを追加していますが、このままだとPOSTしたときに怒られます。
以下のように、フォームのところで``csrf_field()``の結果を表示しなければいけません。

```php:resources/views/helo.php
<!doctype html>
<html>
<head>
    <title>Sample</title>
    <style>
    body { color:gray; }
    h1 { font-size:18pt; font-weight:bold; }
    </style>
</head>
<body>
    <h1>Sample</h1>
    <p><?php echo $message; ?></p>
    <form method="post" action="/helo">
        <?php echo csrf_field(); ?>
        <input type="text" name="str">
        <input type="submit">
    </form>
</body>
```

但し、検索して調べてみると、多くの資料で「Bladeテンプレート」を使って

```
{{ csrf_field() }}
```

と書くように説明されています。
このように書きたい場合は、``helo.php``を``helo.blade.php``とリネームすると良いです。これによって``helo``はBladeビューとなり、Bladeテンプレートが解釈されるようになります。

```php:resources/views/helo.blade.php
<!doctype html>
<html>
<head>
    <title>Sample</title>
    <style>
    body { color:gray; }
    h1 { font-size:18pt; font-weight:bold; }
    </style>
</head>
<body>
    <h1>Sample</h1>
    <p>{{ $message }}</p>
    <form method="post" action="/helo">
        {{ csrf_field() }}
        <input type="text" name="str">
        <input type="submit">
    </form>
</body>
```

ついでに ``$message`` の出力部分も書き換えてしまいました（後でも出てきます）。

以下が参考になりました。

- https://laravel.com/docs/5.5/csrf
- https://laravel.com/docs/5.5/blade
- https://stackoverflow.com/questions/46141705/the-page-has-expired-due-to-inactivity-laravel-5-5

## postIndex() の代わり

[2ページ目](https://www.tuyano.com/index3?id=3786004&page=2)に「コントローラに``postIndex()``メソッドを追加する」旨書かれていますが、リソースコントローラとして呼び出すときは、同じことを``store()``メソッドで行います。

```php:app/Http/Controllers/HeloController.php
    public function store(Request $request)
    {
        $res = "you typed: " . $request->input('str');
        return view('helo', ['message' => $res]);
    }
```

これでテキストを入力して「送信」ボタンを押したときに「you typed: ...」の表示が出れば成功。

## クエリ文字列の取得

[3ページ目](https://www.tuyano.com/index3?id=3786004&page=3)のように、クエリで渡された値を取得するにはRequestクラスを使います。``index()``メソッドでRequestクラスのインスタンスを受け取るために、最初の行のように引数を追加しておきます。

```php:app/Http/Controllers/HeloController.php
    public function index(Request $request)
    {
        $res = 'ID : ' . $request->id;
        return view('helo', ['message' => $res]);
    }
```

# 4. データベースアクセスの基本

https://www.tuyano.com/index3?id=7886003

## DB設定が反映されない？

``config/database.php`` は、5.5にもちゃんとありました。よかった。（笑）
でも、これを説明通りに書き換えても、設定が反映されませんでした。
代わりに、アプリケーションルートにある ``.env``（Linuxだと ``ls -a`` しないと見えない）ファイルを書き換えると良いです。

```conf:.env
DB_CONNECTION=sqlite
#DB_CONNECTION=mysql
#DB_HOST=127.0.0.1
#DB_PORT=3306
#DB_DATABASE=homestead
#DB_USERNAME=homestead
#DB_PASSWORD=secret
```

後は説明通りで動きます（但し``getIndex()``は``index()``に読み替えてください）。

以下を参考にしました。

- https://qiita.com/sutara79/items/f71bb5b93c91cd2c085

## Bladeテンプレートを活用しよう（XSS対策にも！）

動くといえば動くんですが、せっかくBladeビューなのに ``<?php`` とか書きたくないですよね。
というわけで、ビューをBladeテンプレートを使って書き直してみます。

```php:resources/views/helo.blade.php
<!doctype html>
<html>
<head>
    <title>Sample</title>
    <style>
    body { color:gray; }
    h1 { font-size:18pt; font-weight:bold; }
    th { color:white; background:#999; }
    td { color:black; background:#eee; padding:5px 10px; }
    </style>
</head>
<body>
    <h1>Sample</h1>
    <p>{{ $message }}</p>
    <table>
    <tr><th>ID</th><th>NAME</th><th>MAIL</th><th>AGE</th></tr>
    @foreach ($data as $val)
    <tr>
        <td>{{ $val->id }}</td>
        <td>{{ $val->name }}</td>
        <td>{{ $val->mail }}</td>
        <td>{{ $val->age }}</td>
    </tr>
    @endforeach
    </table>
</body>
</html>
```

この方法の良い点は、出力される値が自動的にエスケープされることです。例えば、DBの``name``フィールドに``<script>``と入力されていたとしても、自動的に``<``や``>``を``&lt;``や``&gt;``に置き換えて出力してくれます。[クロスサイトスクリプティング（XSS）](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0)対策ですね。
PHPで同じことをするには、``echo``するときに出力を``htmlspecialchars()``関数で囲む必要があります。いちいち書くの忘れそうだし、何より見た目が長くなってよろしくないですね。

ここでは以下を参考にしました。エスケープされるということも書いてあります。

- https://laravel.com/docs/5.5/blade

# 5. ORM（Eloquent）の利用

https://www.tuyano.com/index3?id=11546003

## ORMの作成方法

[2ページ目](https://www.tuyano.com/index3?id=11546003&page=2)に

```:terminal
$ php artisan make model MyTable
```

と実行しろとあるんですが、これだとエラーになってしまいました。
以下が正解。「コロン」を忘れずに。

```:terminal
$ php artisan make:model MyTable
```

## ORMが呼べない？

[3ページ目](https://www.tuyano.com/index3?id=11546003&page=3)を見ながら

```php:app/Http/Controllers/HeloController.php
    public function index(Request $request)
    {
        $data = MyTable::all();
        return view('helo', ['message' => 'MyTable List','data' => $data]);
    }
```

と書いたら、MyTableが見つからないぞ！と怒られてしまいました。

```php
use App\MyTable;
```

を追加しておきましょう。

# 6. ModelのCRUD

https://www.tuyano.com/index3?id=7896003

## Bladeビューで作りましょう

[2ページ目](https://www.tuyano.com/index3?id=7896003&page=2)で ``new.php`` というビューを作成していますが、例によって ``<?php`` 書きたくない病なので、代わりに ``new.blade.php`` を以下の内容で作ります。
そうそう、``{{ csrf_field() }}`` を忘れないでくださいね。

```php:resources/views/new.blade.php
<!doctype html>
<html>
<head>
    <title>Sample</title>
    <style>
    body { color:gray; }
    h1 { font-size:18pt; font-weight:bold; }
    th { color:white; background:#999; }
    td { color:black; background:#eee; padding:5px 10px; }
    </style>
</head>
<body>
    <h1>Sample</h1>
    <p>{{ $message }}</p>
    <table>
    <form method="post" action="/helo/new">
        {{ csrf_field() }}
        <tr><td>NAME:</td><td><input type="text" name="name"></td></tr>
        <tr><td>MAIL:</td><td><input type="text" name="mail"></td></tr>
        <tr><td>AGE:</td><td><input type="text" name="age"></td></tr>
        <tr><td></td><td><input type="submit"></td></tr>
    </form>
    </table>
</body>
</html>
```

## /helo/new の処理はどこに？

さて今までリソースコントローラがどうのと言ってきましたが、``/helo/new``はどうするのと。
リソースコントローラでは、アクセスさせるURIと実行される（コントローラ側＝PHP側の）メソッドの対応があらかじめ決まっていますので、``new``などという知らないものを扱うには工夫が要ります。

### 第1の方法

``routes/web.php``内で``Route::get``や``Route::post``を書くという方法が考えられます。
2ページ目の通りにコントローラに``getNew()``と``postNew()``のメソッドを追加します。1点だけ変わるのは、``postNew()``の戻り値が

```php
return redirect()->route('helo.index');
```

になることです（リソースコントローラのやり方で呼びます）。
``routes/web.php``については、こんな感じで。

```php:routes/web.php
Route::get('/', function () {
    return view('welcome');
});

Route::get('helo/new', 'HeloController@getNew');
Route::post('helo/new', 'HeloController@postNew');
Route::resource('helo', 'HeloController');
```

### 第2の方法

この際、URIとコントローラのメソッド名をリソースコントローラの枠組みにすっぽり合わせてしまうというのもアリだと思います（むしろそのためにあるのでは？）。
[リソースコントローラの説明](https://laravel.com/docs/5.5/controllers#resource-controllers)を眺めてみると、意味的には

- getNew → create
- postNew → store

に相当すると思われます。
ということで、それぞれのメソッドの中身をごっそり移動してしまいましょう。
（機能に対応するコントローラのメソッド名をカスタマイズすることはできるようですが、特に理由がなければフレームワークに従うのが良いかと…）

```php:app/Http/Controllers/HeloController.php
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('new', ['message' => 'MyTable Create']);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $name = $request->input('name');
        $mail = $request->input('mail');
        $age = $request->input('age');
        $data = array(
            'name' => $name,
            'mail' => $mail,
            'age' => $age
        );
        MyTable::create($data);
        return redirect()->route('helo.index');
    }
```

ビュー側は、``<form>`` メソッドに指定するURIだけが変わっています。
以下のように ``{{ route('helo.store') }}`` とすると、投げるべきURIに自動展開されるのでこの方が良いでしょう。

```php:resources/views/new.blade.php
<body>
    <h1>Sample</h1>
    <p>{{ $message }}</p>
    <table>
    <form method="post" action="{{ route('helo.store') }}">
        {{ csrf_field() }}
        <tr><td>NAME:</td><td><input type="text" name="name"></td></tr>
        <tr><td>MAIL:</td><td><input type="text" name="mail"></td></tr>
        <tr><td>AGE:</td><td><input type="text" name="age"></td></tr>
        <tr><td></td><td><input type="submit"></td></tr>
    </form>
    </table>
</body>
```

さらに、登録画面のURIが ``http://192.168.123.45:8000/helo/create`` に変わります。
このURIをわざわざ手入力させることは考えにくいですので、これでもいいのではないでしょうか。

## 同じくupdateも

[4ページ目](https://www.tuyano.com/index3?id=7896003&page=4)以降、今度は``/helo/update``を追加するとあります。
もちろん、先ほどの「第1の方法」と同じようにルーティングを追加するのもよいですが、せっかくなので「第2の方法」の流れでリソースコントローラの流儀に従って実装していきましょう。

意味を考えると、コントローラのメソッド名は

- getUpdate → edit
- postUpdate → update

でいいですかね。

ビューは例によってBladeビューとして作りましょう。
リソースコントローラでは、更新時のURIにID情報が含まれています。``{{ route('helo.update', ['id' => $data->id]) }}`` のように、IDを引数に付けて ``route()`` を呼び出すことによって、そのIDを更新するためにアクセスすべきURIに展開されます。URIでIDを書いているので、``<input type="hidden">``でIDを指定する必要はなくなっています。
また、この更新時のHTTPメソッドとしては本来``PUT``か``PATCH``を使うことになっていますが、HTMLフォームからは``POST``しか送れません。フォーム内に ``{{ method_field('PUT') }}`` を書いておくと、Laravelは``PUT``メソッドで投げられたものと思って処理してくれます。

```php:resources/views/update.blade.php
<!doctype html>
<html>
<head>
    <title>Sample</title>
    <style>
    body { color:gray; }
    h1 { font-size:18pt; font-weight:bold; }
    th { color:white; background:#999; }
    td { color:black; background:#eee; padding:5px 10px; }
    </style>
</head>
<body>
    <h1>Sample</h1>
    <p>{{ $message }}</p>
    <table>
    <form method="post" action="{{ route('helo.update', ['id' => $data->id]) }}">
        {{ csrf_field() }}
        {{ method_field('PUT') }}
        <tr><td>NAME:</td><td>
        <input type="text" name="name" value="{{ $data->name }}"></td></tr>
        <tr><td>MAIL:</td><td>
        <input type="text" name="mail" value="{{ $data->mail }}"></td></tr>
        <tr><td>AGE:</td><td>
        <input type="text" name="age" value="{{ $data->age }}"></td></tr>
        <tr><td></td><td><input type="submit"></td></tr>
    </form>
    </table>
</body>
</html>
```

さらに、コントローラはこんな感じに。``edit()``と``update()``を実装します。
いずれも、引数としてIDが渡ってくるので、これをそのまま使うように書けばOKです。 

```php:app/Http/Controllers/HeloController.php
    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $data = MyTable::find($id);
        $msg = 'MyTable Update [id = ' . $id . ']';
        return view('update', ['message' => $msg, 'data' => $data]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $data = MyTable::find($id);
        $data->name = $request->input('name');
        $data->mail = $request->input('mail');
        $data->age = $request->input('age');
        $data->save();
        return redirect()->route('helo.index');
    }
```

さて、編集画面のURIはどこに行ったでしょうか。
ID: 1を編集するときは ``http://192.168.123.45:8000/helo/1/edit`` になりますよ。
更新がちゃんと効けばOKです。

## 最後にdelete

この際、deleteもリソースコントローラの流儀で書きますよ。
その場合、さすがにHTTPの``GET``で、というわけにいかないので、delete用のフォームを準備します。
新しいビューを作るのも面倒なので、updateの画面を編集します。こんな感じでいいですかね。

updateと同様、deleteの場合も、``route()`` の引数としてIDを与えるとURIにID情報が反映されます。

```php:resources/views/update.blade.php
<body>
    <h1>Sample</h1>
    <p>{{ $message }}</p>
    <table>
    <form method="post" action="{{ route('helo.update', ['id' => $data->id]) }}">
        {{ csrf_field() }}
        {{ method_field('PUT') }}
        <tr><td>NAME:</td><td>
        <input type="text" name="name" value="{{ $data->name }}"></td></tr>
        <tr><td>MAIL:</td><td>
        <input type="text" name="mail" value="{{ $data->mail }}"></td></tr>
        <tr><td>AGE:</td><td>
        <input type="text" name="age" value="{{ $data->age }}"></td></tr>
        <tr><td></td><td><input type="submit"></td></tr>
    </form>
    <form method="post" action="{{ route('helo.update', ['id' => $data->id]) }}">
        {{ csrf_field() }}
        {{ method_field('DELETE') }}
        <tr><td></td><td><input type="submit" value="削除"></td>
    </form>
    </table>
</body>
```

対応するコントローラ側のメソッドは``destroy()``になります。

```php:app/Http/Controllers/HeloController.php
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $data = MyTable::find($id);
        $data->delete();
        return redirect()->route('helo.index');
    }
```

先ほどの ``http://192.168.123.45:8000/helo/1/edit`` にアクセスすると、削除ボタンが追加されています。この削除ボタンを押せば、ID: 1のレコードが消えます。
一覧画面にジャンプしますので、ID: 1が消えていることを確認しましょう。

# 最後に

なんか意外と長くなってしまったのですが、ここまでできれば何か作れるんじゃないかという気がしますね。まだ使っていない機能が大量にあると思いますが、それはドキュメントなどで調べながらやればいいので。

今回は各所で``<form>``タグを直書きしたのですが、URIを直書きするのも何か美しくないなあと思っていたら、やっぱり良さそうな方法がありました。
これを使うと ``{{ csrf_field() }}`` も ``{{ method_field('PUT') }}`` も気にしなくていいみたいですね。詳しくは以下のページなどをご覧ください。

- http://recipes.laravel.jp/recipe/224
- https://laravel10.wordpress.com/2015/03/08/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AElaravel-5-16-form%E3%81%AE%E4%BD%9C%E6%88%90/

DBの操作（CRUD）に関する所については、リソースコントローラとして書けば、何を実装していて何をまだ実装していないか分かりやすいし、複数人で開発するときにも混乱しなくて良いのではないでしょうか（と書きながら何かを思い出す）。

## …と、ここまで書いて気づいた

あっ、まだ``show()``を実装してなかった！

```php:app/Http/Controllers/HeloController.php
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $data = MyTable::where('id', $id)->get();
        return view('helo', ['message' => 'MyTable List', 'data' => $data]);
    }
```

``http://192.168.123.45:8000/helo/2`` にアクセスしてID: 2のレコードだけが表示されることを確認しましょう。（完）