# Laravel 02 ## 今日のコンテンツ - 復習(環境構築、ターミナル操作、MVC、ルーティング、マイグレーション) - CRUD実装(続き) - ルート定義からコントローラに処理コードを移行 - バリデーション - ページネーション ## 今日のゴール - 1回目よりもMVC設計の流れがわかる - CRUD処理のコードの書き方になれる - Laravel Loveになる --- :::danger 【注意】 - ファイルを編集するときは「自動保存」のチェック or 「Ctrl+S(win) / Command+S(mac)」を忘れずに - php artisan コマンドは **cms階層**で行う ::: :::info Paiza Cloudおすすめ設定 - タブウィンドウモード(箱アイコンのメニューから選択可能) - ターミナル2つ(1つはLaravelのWebサーバー用、もう一つはコマンド入力用) - 「phpMyAdmin」と「プレビュー画面(ポート8000)」は、「新しいウィンドウで開く」で、ブラウザのタブに常駐させておく ::: ↓こんな感じ ![](https://i.imgur.com/JO2fcSY.png) ↓phpMyAdminとプレビュー画面をブラウザのタブには表示するには、このアイコンをクリック ![](https://i.imgur.com/Im9nLkq.png) ## 【復習1】 環境構築 ### ① サーバーの作成 下記設定で、「新規サーバー作成」 :::info サーバ名:デフォルトでOK web開発:PHP,Laravel データベース:MySQL,phpMyAdmin その他は設定不要 ::: ### ② Laravelプロジェクトの作成 1. ターミナル上でLaravelプロジェクト作成コマンドを入力 `composer create-project laravel/laravel cms 6.* --prefer-dist` 2. プロジェクトディレクトリに移動 `cd cms` 3. Composerコマンドを実行 `sudo composer update` 4. BuiltInのLaravelサーバーを起動 `php artisan serve` 5. 動作確認 `Starting Laravel development server: http://127.0.0.1:8000 ` とターミナル上で表示され、ブラウザ(8000番ポート)をクリックしてLaravelのホーム画面が表示されていれば成功! ### ③ データベース作成&接続 1. データベースを作成 * phpMyAdminで作成する場合 ①左タブからphpMyAdminのタブ内「Open phpMyAdmin」をクリック ②phpMyAdminの管理画面で `c9` というDBを作成 ※照合順は`utf8mb4_general_ci`を選択 * ターミナルでDB作成する場合 ターミナル上で下記の手順で実行 ``` mysql -u root -p root [Enterキー] create database c9; exit; ``` 2. 隠しファイルを表示 エディタ左側のファイル階層で右クリック→「隠しファイルを表示」 3. .envファイルを編集 cms直下にあるenvファイル内の同じファイルを上書き ``` DB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=c9 DB_USERNAME=root DB_PASSWORD= ``` 4. Webサーバーを再起動 :::danger 超重要!!!**.envを変更後はWebサーバーを再起動**する ::: ``` # 起動中のwebサーバーを停止 Ctl + C # cmsディレクトリにいることを確認後、サーバー起動 php artisan serve ``` ### ④ Laravelの/app/Providers/AppServiceProvider.php ファイルを修正 ```php // この行を冒頭付近に追加(use Illuminate\Support\ServiceProvider;の下) use Illuminate\Support\Facades\URL; public function boot() { URL::forceScheme('https'); //この行をboot(){...}内に追加 } ``` --- ## 【復習2】ログイン認証 1. マイグレーションを実行(cmsディレクトリにいるか確認!) `php artisan migrate` 2. laravel/ui パッケージをインストール `composer require laravel/ui:^1.0 --dev` 3. artisan コマンドを実行 `php artisan ui vue --auth` 4. npmパッケージをインストール `npm install` 5. パッケージをビルド `npm run dev` --- ## 【復習3】CRUDの登録と読込 ### ①ルーティング(コントローラーの役割含む)を作成 1. /routes/web.phpに以下の記述を追加 先頭部 ```php use App\Book; use Illuminate\Http\Request; ``` 中間 ```php // 本のダッシュボード表示(books.blade.php) Route::get('/', function () { $books = Book::orderBy('created_at', 'asc')->get(); return view('books', ['books' => $books]); //return view('books',compact('books')); //も同じ意味 }); // 新「本」を追加 Route::post('/books', function (Request $request) { // Eloquentモデル(登録処理) $books = new Book; $books->item_name = $request->item_name; $books->item_number = $request->item_number; //ここを変更 $books->item_amount = $request->item_amount; //ここを変更 $books->published = $request->published; //ここを変更 $books->save(); return redirect('/'); }); //「本」の更新画面表示 Route::post('/booksedit/{book}', function (){ // }); // 本を削除 Route::delete('/book/{book}', function () { // }); ``` ### ②DB-テーブルを作成 1. booksテーブルを作成(マイグレーションファイル作成) `php artisan make:migration create_books_table --create=books` 2. database/migrationsの直下のbooks_table.phpに以下追記 ```php public function up() { Schema::create('books', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('item_name'); //追加 $table->integer('item_number'); //追加 $table->integer('item_amount'); //追加 $table->date('published'); //追加 $table->timestamps(); }); } ``` 3. マイグレーションを実行(テーブル作成) ターミナル上でコマンド実行 ``` php artisan migrate ``` 4. phpMyAdminでテーブルが作成されているか確認 ### ③Modelを作成(テーブルを簡単に扱えるようにする機能) 1. ターミナルでコマンドを実行 ``` php artisan make:model Book ``` 2. /app/Book.php に作成されたことを確認 ### ④Viewを作成(登録&表示のためのページ) 1. /resources/views/books.blade.php を作成 以下コードを貼り付け ```htmlmixed @extends('layouts.app') @section('content') <!-- Bootstrapの定形コード… --> <div class="card-body"> <div class="card-title"> ブックマーク </div> <!-- ↓バリデーションエラーの表示に使用--> <!-- ↑バリデーションエラーの表示に使用--> <!-- 本登録フォーム --> <form action="{{ url('books') }}" method="POST" class="form-horizontal"> @csrf <!-- 本のタイトル --> <div class="form-group col-md-6"> <label for="item_name" class="col-sm-3 control-label">タイトル</label> <input type="text" name="item_name" class="form-control" id="item_name"> </div> <!-- 冊数 --> <div class="form-group col-md-6"> <label for="item_number" class="col-sm-3 control-label">冊数</label> <input type="text" name="item_number" class="form-control" id="item_number"> </div> <!-- 金額 --> <div class="form-group col-md-6"> <label for="item_amount" class="col-sm-3 control-label">金額</label> <input type="text" name="item_amount" class="form-control" id="item_amount"> </div> <!-- 公開日 --> <div class="form-group col-md-6"> <label for="published" class="col-sm-3 control-label">公開日</label> <input type="date" name="published" class="form-control" id="published"> </div> <!-- 本 登録ボタン --> <div class="form-group"> <div class="col-sm-offset-3 col-sm-6"> <button type="submit" class="btn btn-primary"> Save </button> </div> </div> </form> </div> <!-- Book: 既に登録されてる本のリスト --> <!-- 現在の本 --> @if (count($books) > 0) <div class="card-body"> <table class="table table-striped task-table"> <!-- テーブルヘッダ --> <thead> <th>本一覧</th> <th>&nbsp;</th> </thead> <!-- テーブル本体 --> <tbody> @foreach ($books as $book) <tr> <!-- 本タイトル --> <td class="table-text"> <div>{{ $book->item_name }}</div> </td> <!-- 本: 削除ボタン --> <td> </td> </tr> @endforeach </tbody> </table> </div> @endif @endsection ``` :::warning 最後に、登録&表示ができているかプレビュー画面で確認しましょう。 前回までの内容は以上です。 ::: --- ## ⑦ CRUD機能実装(削除) :::success 【大まかなステップ】 1. [view]一覧画面から削除ボタンを作成(view) 3. [routing/controller]削除処理をroutesに追加 ::: 1. books.blade.phpに削除ボタンを追加 < !-- 本: 削除ボタン -->下の<td></td>要素内に追加 ```htmlmixed <form action="{{ url('book/'.$book->id) }}" method="POST"> @csrf @method('delete') <button type="submit" class="btn btn-danger"> 削除 </button> </form> ``` :::info 【解説】 削除ボタンクリック時には,コントローラのdestroy()関数にリクエストが送られる 削除の処理を行うにはDELETEメソッドでリクエストを送る必要があるが、form からは GET または POST でしか送れない `@method('delete')`を記述することで、DELETEメソッドで送信できる ※`{{ method_field('delete') }}` も同じ役割 引用元:taroosg先生 ::: 2. プレビューで削除ボタンが追加されているか確認 ![](https://i.imgur.com/4i8ilCA.png) 3. web.phpに「本を削除」のルート定義と削除処理を追加編集 Viewからformでリクエストを送るところまでできました。 では次に、`/book/{book}` のルート定義のところに削除処理を書きましょう ```php Route::delete('/book/{book}', function (Book $book) { $book->delete(); //追加 return redirect('/'); //追加 }); ``` :::info 【+α / 暗黙の結合 - `$book->delete()`で自動的に削除できるのはなぜ?】 `{book}`のところで渡ってくるidを使って、Bookモデルがbooksテーブルから対象となるレコード(一つのデータの塊)を自動で取ってきてくれます より詳しく知りたい方は↓ > https://readouble.com/laravel/master/ja/routing.html#route-model-binding ※授業のコードでは`(App\Book $book)`ではなく`(Book $book)`と書けるのは、web.phpの先頭でBookモデルを読み込んでいる(`use App\Book;`)からです ::: ## ⑧ CRUD機能実装(更新) この章では更新処理を実装します :::success 【大まかなステップ】 1. [view]一覧画面から更新画面へのリンクボタンを作成(view) 2. [view]更新画面のviewを新規作成 3. [routing/controller] 更新画面の表示 & 更新処理をroutesに追加 ::: 1. books.blade.phpに更新ボタンを追加 削除ボタンの<td></td>タグの下 ```htmlmixed <!-- 本: 更新ボタン --> <td> <a href="{{ url('booksedit/'.$book->id) }}"> <button type="submit" class="btn btn-primary">更新</button> </a> </td> ``` 2. プレビューで更新ボタンが追加されていることを確認 ![](https://i.imgur.com/zKsJAqB.png) 3. booksedit.blade.phpをview配下に新規作成し、以下コードを貼り付け ```htmlmixed @extends('layouts.app') @section('content') <div class="row container"> <div class="col-md-12"> <!-- ↓バリデーションエラーの表示に使用--> <!-- ↑バリデーションエラーの表示に使用--> <form action="{{ url('books/update') }}" method="POST"> <!-- item_name --> <div class="form-group"> <label for="item_name">タイトル</label> <input type="text" name="item_name" class="form-control" id="item_name" value="{{$book->item_name}}"> </div> <!--/ item_name --> <!-- item_number --> <div class="form-group"> <label for="item_number">冊数</label> <input type="text" name="item_number" class="form-control" id="item_number" value="{{$book->item_number}}"> </div> <!--/ item_number --> <!-- item_amount --> <div class="form-group"> <label for="item_amount">金額</label> <input type="text" name="item_amount" class="form-control" id="item_amount" value="{{$book->item_amount}}"> </div> <!--/ item_amount --> <!-- published --> <div class="form-group"> <label for="published">公開日</label> <input type="date" name="published" class="form-control" id="published" value="{{$book->published}}"> </div> <!--/ published --> <!-- Save ボタン/Back ボタン --> <div class="well well-sm"> <button type="submit" class="btn btn-primary">Save</button> <a class="btn btn-link pull-right" href="{{ url('/') }}"> Back</a> </div> <!--/ Save ボタン/Back ボタン --> <!-- id 値を送信 --> <input type="hidden" name="id" value="{{$book->id}}" /> <!--/ id 値を送信 --> <!-- CSRF --> @csrf <!--/ CSRF --> </form> </div> </div> @endsection ``` 3. [routing] web.phpに更新画面表示と更新処理を追加 ```php //「本」の更新画面表示 Route::get('/booksedit/{book}',function(Book $book){ return view('booksedit', ['book' => $book]); }); //「本」の更新処理 Route::post('books/update',function(Request $request){ // Eloquent モデル $books = Book::find($request->id); $books->item_name = $request->item_name; $books->item_number = $request->item_number; $books->item_amount = $request->item_amount; $books->published = $request->published; $books->save(); return redirect('/'); }); ``` :::success 以上で基本的なCRUD処理は終了です :raised_hands: ::: --- ## ルート定義からコントローラに処理コードを移行 今まではルート定義(web.php)にコントローラの役割を持たせていました。 ※例:この`function(){}`の部分に、URL`'/'`が呼ばれた時の処理を書いていました ```php Route::get('/', function () { $books = Book::orderBy('created_at', 'asc')->get(); return view('books', ['books' => $books]); }); ``` 機能が限られているサービスなら、ルート定義にfunction()部分を直接書いても記述量が少なく、一つのファイルで完結するのでわかりやすい面も。 しかし、一般的なサービスでは数十~数百のルート定義があるのはあたりまえ。 そうなるとルート定義のファイルが肥大化して逆にわかりにくいコードになってしまいます。 この章ではルート定義に持たせていた処理の役割をコントローラに移行しましょう。 ![](https://i.imgur.com/zfEaxck.png) 左が・・・右になります!どちらが見通しがいいですか? ![](https://i.imgur.com/0lQJqQZ.png) :::success 【本章の大まかなステップ】 1. BooksControllerを作成 2. BooksControllerとModelを関連付け 3. ルート定義の処理の部分をControllerに移行 4. ルート定義に、移行したControllerの場所を記述 ::: ### ①BooksControllerを作成 1. BooksControllerを作成 ControllerもMigrationやModelと同じでコマンド一発で作成できます `php artisan make:controller BooksController --resource` :::info 【+α / --resourceってなに?】 コントローラで行う処理は大体決まっています。あらかじめよく使う処理のテンプレートを用意し簡潔に書けるようにしてくれるのが**リソースコントローラ**です。 Controllerを作るときに `--resource`のオプションを付けると作成できます もっと詳しく学びたい方はこちら > https://readouble.com/laravel/6.x/ja/controllers.html ::: :::info 【+α / Model/Migration/Controllerを便利に作りたい!】 Modelを作る場合、たいてい対応するテーブルのMigrationとControllerを作ります。Laravelにはそれらをいっぺんに作るコマンドがあります。 もっと学びたい方はこちら > https://readouble.com/laravel/8.x/ja/eloquent.html > https://qiita.com/niisan-tokyo/items/9c799989cb535489f201 ::: 2. BooksControllerが作成されたか確認 ![](https://i.imgur.com/vzBHEBF.png) ### ②BooksControllerとModelを関連付け 1. BooksControllerとModelを関連付け 6行目に追加 ```php=6 use App\Book; //Book モデルを使えるようにする ``` ### ③ルート定義の処理の部分をControllerに移行 まずは表示処理をやってみましょう ```php:web.php // web.php // 本のダッシュボード表示(books.blade.php) Route::get('/', function () { // ↓ここから $books = Book::orderBy('created_at', 'asc')->get(); return view('books', ['books' => $books]); // ↑ここまでをBooksControllerの index()に移行 }); ``` ```php:BooksController.php // BooksController.php public function index() { // ↓ここから $books = Book::orderBy('created_at', 'asc')->get(); return view('books', ['books' => $books]); // ↑ここまでを貼り付け } ``` ### ④ルート定義に、移行したControllerの場所を記述 web.phpの切り取ったルート定義に、移行したコントローラの場所を記述しましょう ```php Route::get('/','BooksController@index'); ``` ### ⑤他のルート定義も同様に、③と④を繰り返しましょう :::info ルート定義と対応するリソースコントローラのfunctionの名前 | Routing | Controller| | ------------------------| -------- | | 登録処理( ダッシュボード表示) | index | | 登録処理(「本」を追加) | store | | 更新画面(「本」の更新画面表示) | edit | | 更新処理(「本」の更新処理) | update | | 削除処理(「本」を削除) | destroy | ::: :::danger ※重要!:functionの引数がある場合は、それも移行しましょう 例: `function (Request $request){}` ← 括弧の中もControllerに移行 ::: ### ⑥完成したら、CRUD処理が無事できるか確認 View から Controllerに処理の部分を移した後でもしっかり動くか確認しましょう。 ### 完成すると下記のようになります <web.php> ```php // 本のダッシュボード表示 Route::get('/', 'BooksController@index'); // 登録処理(新「本」を追加) Route::post('/books','BooksController@store'); // 更新画面(「本」の更新画面表示) Route::get('/booksedit/{book}','BooksController@edit'); // 更新処理(「本」の更新処理) Route::post('/books/update','BooksController@update'); // 削除処理(本を削除) Route::delete('/book/{book}','BooksController@destroy'); ``` <BooksController.php> ```php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Book; class BooksController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $books = Book::orderBy('created_at', 'asc')->get(); return view('books', ['books' => $books]); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store (Request $request) { // Eloquentモデル(登録処理) $books = new Book; $books->item_name = $request->item_name; $books->item_number = $request->item_number; $books->item_amount = $request->item_amount; $books->published = $request->published; $books->save(); return redirect('/'); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit(Book $book) { return view('booksedit', ['book' => $book]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request){ // Eloquent モデル $books = Book::find($request->id); $books->item_name = $request->item_name; $books->item_number = $request->item_number; $books->item_amount = $request->item_amount; $books->published = $request->published; $books->save(); return redirect('/'); } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy(Book $book) { $book->delete(); return redirect('/'); } } ``` --- ## バリデーション **バリデーションとは入力チェック**のことで、ルール通り正しい形式で書かれているか確認することです。この章では今まで作った機能(登録&更新)に、バリデーションを追加しましょう。 今回は`Validatorファサード`というLaravelに備わっている機能を使用し、バリデーションを実装していきましょう :::success 【大まかなステップ】 1. コントローラでValidatorを使えるようにする 2. コントローラにバリデーションのルールを記述 3. コントローラにバリデーションが失敗した時の処理を記述 4. エラー表示のビューを作成 5. エラー表示のビューを読み込み(@include) 6. エラーの場合、入力した値をviewで再表示 これを登録処理と更新処理の両方で行います ::: ### ① コントローラでValidatorを使えるようにする <BooksController.php> ```php= <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Book; use Validator; //ここを追加 ``` ### ② コントローラにバリデーションのルールを記述 ```php public function store (Request $request) { //バリデーション(ここを追加) $validator = Validator::make($request->all(), [ 'item_name' => 'required | min:3 | max:255', 'item_number' => , 'item_amount' => 'required | max:6', 'published' => 'required', ]); // Eloquentモデル(登録処理) // ... } ``` * item_numberのバリデーションを完成させてみましょう ``` * 必須項目 * 最低1文字 * 最高3文字 ``` * 答え :::spoiler `'item_number' => 'required | min:1 | max:3',` ::: -- :::info 【解説 / Validatorのmakeメソッド】 makeメソッドの第1引数は、バリデーションを行うデータが入ります。ここでは`$request->all()`になっているので、formで送られた全データをチェックしています。 第2引数はそのデータに適用するバリデーションルールです。 ::: ### ③ コントローラにバリデーションが失敗した時の処理を記述 ```php public function store (Request $request) { //バリデーション $validator = Validator::make($request->all(), [ // 省略 ]); //バリデーション:エラー (ここを追加) if ($validator->fails()) { return redirect('/') ->withInput() ->withErrors($validator); } // Eloquentモデル(登録処理) // ... } ``` ### ④ エラー表示のビューを作成 /resources/views/common/errors.blade.php を作成します 1. `/resources/views/`の配下に`common`というフォルダを作り、そこに`errors.blade.php`を作成 2. 下記を貼り付け ```htmlmixed <!-- resources/views/common/errors.blade.php --> @if (count($errors) > 0) <!-- Form Error List --> <div class="alert alert-danger"> <div><strong>入力した文字を修正してください。</strong></div> <div> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> </div> @endif ``` ### ⑤ エラー表示のビューを読み込み(@include) ```php <!-- バリデーションエラーの表示に使用--> @include('common.errors') <!-- バリデーションエラーの表示に使用--> ``` ここまでかけたら、バリデーションエラーが表示されるか確認してみましょう。 <books.blade.php> ![](https://i.imgur.com/pA0vn1I.png) ### ⑥ エラーの場合、入力した値をviewで再表示 inputに下記のoldヘルパーを記述すると、前に入力した値が保持される。 1. 各inputタグに下記を追記 ```php value="{{ old(変数名) }}" ``` 例)本のタイトルのinputタグ ```htmlmixed <!-- 本のタイトル --> <div class="form-group col-md-6"> <label for="item_name" class="col-sm-3 control-label">タイトル</label> <input type="text" name="item_name" class="form-control" id="item_name" value="{{old('item_name')}}"> </div> ``` ### [更新処理]のバリデーション(Controller + View) 新規登録のバリデーションとエラー表示の機能は実装できました。 もう一つ、更新処理を同様に仕上げましょう。 <BooksController.php> storeメソッドの中に追加 ```php //バリデーション $validator = Validator::make($request->all(), [ 'id' => 'required', // storeに対しての追加分 'item_name' => 'required|min:3|max:255', 'item_number' => 'required|min:1|max:3', 'item_amount' => 'required|max:6', 'published' => 'required', ]); //バリデーション:エラー if ($validator->fails()) { return redirect('/booksedit/'.$request->id) ->withInput() ->withErrors($validator); } ``` <booksedit.blade.php> ```php <!-- ↓バリデーションエラーの表示に使用--> @include('common.errors') <!-- ↑バリデーションエラーの表示に使用--> ``` :::info どんなバリデーションルールが用意されているのか検索しながら実装しましょう 参考:https://laravel.com/docs/6.x/validation#available-validation-rules ::: :::info 【+α / バリデーションの種類は他にも!】 Laravelでのバリデーションの実装方法は数種類あります。興味のある人は`ValidateRequests`や`FormRequest`など、探求してみましょう。 > https://readouble.com/laravel/6.x/ja/validation.html ::: --- ## ページネーション :::info > ページネーションでは1ページに表示する制限数を定めることができます。表示制限を設定した場合に「表示できないページ」がでてきますが、「2ページ」「3ページ」とリンクが自動で生成されます。この機能を「ページネーション」と呼んでいます。 > 出典:「はじめてのLaravel6 入門」(著:山崎学校長) ::: さっそく実装してみましょう。 :::success 【ステップ】 1. コントローラでpaginate()を使ってDBからデータを取得 2. viewにページネーションを埋め込む ::: 1. コントローラでpaginate()を使ってDBからデータを取得 `記述式) モデル名::paginate(1ページあたりの表示数)` `記述例) $books = Book::paginate(1ページあたりの表示数);` ※`get();` `all()`; の データ取得メソッドに代えて使います 。 BooksControllerのindexの、`get()`の部分を`paginate(数字)`に変更します ```php public function index() { $books = Book::orderBy('created_at', 'asc')->paginate(3); } ``` 2. ページネーションをテンプレートに埋め込む `{{$変数->links()}}` をview(blade.php)に記述する必要があります。 books.blade.phpの`@endif`の下、`@endsection`の上にコードを追加 ```htmlmixed <div class="row"> <div class="col-md-4 offset-md-4"> {{ $books->links() }} </div> </div> ``` プレビュで確認するとこのようにページネーションボタンが表示されます ![](https://i.imgur.com/2mOGVq8.png) 3. App\Http\Middleware\TrustProxiesを変更 変更前 ```php=15 protected $proxies; ``` 変更後 ```php=15 protected $proxies = '*'; ``` :::success Laravel02_授業はここまでです。おつかれ様でした! 【Special Thanks】 :clap: 山崎学校長 :clap: taroosg先生 :clap: 中野先生 :::