Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

はじめに

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

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

のページを見ながら進めてみました。
しかし、記事が書かれたのが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の「Command-line installation」参照)を行ったため、実行ファイルの名前がcomposer.pharになりました。
そのため、ファイル名をcomposerに変更するか、コマンド実行の説明部分でcomposercomposer.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ページ目

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

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

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

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

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

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

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

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

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

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

5ページ目に進むと

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

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

In RouteAction.php line 84:

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

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

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

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()メソッドの中を編集します。

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

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

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

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

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

3. Requestクラスの基本

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

POSTするとエラーになる?

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

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.phphelo.blade.phpとリネームすると良いです。これによってheloはBladeビューとなり、Bladeテンプレートが解釈されるようになります。

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 の出力部分も書き換えてしまいました(後でも出てきます)。

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

postIndex() の代わり

2ページ目に「コントローラにpostIndex()メソッドを追加する」旨書かれていますが、リソースコントローラとして呼び出すときは、同じことをstore()メソッドで行います。

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

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

クエリ文字列の取得

3ページ目のように、クエリで渡された値を取得するにはRequestクラスを使います。index()メソッドでRequestクラスのインスタンスを受け取るために、最初の行のように引数を追加しておきます。

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 しないと見えない)ファイルを書き換えると良いです。

.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()に読み替えてください)。

以下を参考にしました。

Bladeテンプレートを活用しよう(XSS対策にも!)

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

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)対策ですね。
PHPで同じことをするには、echoするときに出力をhtmlspecialchars()関数で囲む必要があります。いちいち書くの忘れそうだし、何より見た目が長くなってよろしくないですね。

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

5. ORM(Eloquent)の利用

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

ORMの作成方法

2ページ目

terminal
$ php artisan make model MyTable

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

terminal
$ php artisan make:model MyTable

ORMが呼べない?

3ページ目を見ながら

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

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

use App\MyTable;

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

6. ModelのCRUD

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

Bladeビューで作りましょう

2ページ目new.php というビューを作成していますが、例によって <?php 書きたくない病なので、代わりに new.blade.php を以下の内容で作ります。
そうそう、{{ csrf_field() }} を忘れないでくださいね。

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::getRoute::postを書くという方法が考えられます。
2ページ目の通りにコントローラにgetNew()postNew()のメソッドを追加します。1点だけ変わるのは、postNew()の戻り値が

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

になることです(リソースコントローラのやり方で呼びます)。
routes/web.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とコントローラのメソッド名をリソースコントローラの枠組みにすっぽり合わせてしまうというのもアリだと思います(むしろそのためにあるのでは?)。
リソースコントローラの説明を眺めてみると、意味的には

  • getNew → create
  • postNew → store

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

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に自動展開されるのでこの方が良いでしょう。

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ページ目以降、今度は/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メソッドとしては本来PUTPATCHを使うことになっていますが、HTMLフォームからはPOSTしか送れません。フォーム内に {{ method_field('PUT') }} を書いておくと、LaravelはPUTメソッドで投げられたものと思って処理してくれます。

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です。

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情報が反映されます。

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()になります。

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') }} も気にしなくていいみたいですね。詳しくは以下のページなどをご覧ください。

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

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

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

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のレコードだけが表示されることを確認しましょう。(完)

everylittle
PythonやWebプログラミングなどのTipsをメモ代わりに投稿しています。たまに機械学習の話題もあります。
https://www.every-little.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away