はじめに
Django経験者が、既存DjangoアプリをLaravelで再現するチュートリアル形式の記事を書くことでLaravelを学びます。
題材とさせていただいたDjangoアプリはこちらです。
Djangoチュートリアル - 汎用業務Webアプリを最速で作る - Qiita
チュートリアル全体の構成
-
テーブルとCRUD画面を作る(本記事)
-
認証機能を作る(作成予定)
CRUD画面の構成
機能 | HTTP動詞 | URI | アクション | ビューファイル |
---|---|---|---|---|
一覧画面 | GET | / | index | index.blade.php |
登録画面 | GET | /items/create | create | create.blade.php |
登録処理 | POST | /items/ | store | - |
詳細画面 | GET | /items/{id} | show | create.blade.php |
編集画面 | GET | /items/{id}/edit | edit | edit.blade.php |
編集処理 | PATCH | /items/{id} | update | - |
削除確認画面 | GET | /items/{item}/delete | delete | delete.blade.php |
削除処理 | DELETE | /items/{item} | destroy | - |
一覧画面はこのような内容です。
ソースコード
環境
- macOS High Sierra 10.13.6
- php 7.2.16
- Laravel 5.5.45
- PostgreSQL 9.6.2
2.1. テーブルの作成
前回の記事から引き続き、コンテナの中で作業を行います。
もし、コンテナから出てしまっていたら、再び入り直してください。
laradock $ docker-compose exec workspace bash
2.1.1. マイグレーションファイルの作成
今回作成するテーブル名は、items
とします。
まず、Laravelプロジェクトのディレクトリsrc
にて、items
テーブルを作成するためのマイグレーションファイル作成コマンドを実行します。
/var/www# $ cd src
/var/www/src# $ php artisan make:migration create_items_table --create=items
-
create_items_table
の部分が、マイグレーションファイルのファイル名の一部となります。 -
--create=items
の部分にて、items
という名前のテーブルを新規作成することを意味します。
実行すると以下の結果が表示されます。
Created Migration: Created Migration:yyyy_mm_dd_hhmiss_create_items_table
2.1.2. マイグレーションファイルの編集
作成されたマイグレーションファイルは、database/migrations/
に存在します。
続いて、このファイルに以下のようにテーブルとして持たせる項目を追記します。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->increments('id');
$table->string('name'); // 追加
$table->integer('age')->nullable(); // 追加
$table->integer('sex')->nullable(); // 追加(1:男性, 2:女性, 3:指定なし)
$table->text('memo')->nullable(); // 追加
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('items');
}
}
2.1.3. マイグレーションの実行
マイグレーションファイルの編集が終わったら、マイグレーションを実行し、テーブルを作成します。
/var/www/src# php artisan migrate
実行に成功すると、以下が表示されます。
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrating: yyyy_mm_dd_hhmiss_create_items_table
Migrated: yyyy_mm_dd_hhmiss_create_items_table
今回初めてのマイグレーション実行であったため、Laravel標準のusers
テーブルやpassword_resets
テーブルも作成されます。
2.1.4. 作成されたテーブルの確認
データベース(PostgreSQL)に接続し、テーブルが作成されていることを確認します。
まず、PostgreSQLに接続します。
/var/www/src# psql -h postgres -d sample -U default
Password for user default:
次に、items
テーブルの構成を確認します。
sample=# \d items
以下の通り、items
テーブルが作成されていることを確認できました。
Table "public.items"
Column | Type | Modifiers
------------+--------------------------------+----------------------------------------------------
id | integer | not null default nextval('items_id_seq'::regclass)
name | character varying(255) | not null
age | integer |
sex | integer |
memo | text |
created_at | timestamp(0) without time zone |
updated_at | timestamp(0) without time zone |
Indexes:
"items_pkey" PRIMARY KEY, btree (id)
-
created_at
およびupdated_at
の2つのカラムは、マイグレーションファイル内の$table->timestamps();
によって生成されたものとなります。
items
テーブルの確認を終えたので、PostgreSQLへの接続を終了します。
quickstart=# \q
続いて、モデルの作成を行います。
2.2. モデル
LaravelではEloquentモデルを通して、テーブル中のデータを操作します。
Eloquent ORMはLaravelに含まれている、美しくシンプルなアクティブレコードによるデーター操作の実装です。それぞれのデータベーステーブルは関連する「モデル」と結びついています。モデルによりテーブル中のデータをクエリできますし、さらに新しいレコードを追加することもできます。
利用を開始するには、まずEloquentモデルを作成しましょう。
2.2.1. モデルの作成
以下コマンドでitems
テーブルに対応するItem
モデルが作成されます。
/var/www/src# php artisan make:model Item
2.2.2. 作成されたモデルの確認
作成されたモデルは、app/
に存在します。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
//
}
-
$fillable
(詳細後述)の指定は後ほど行います。
テーブルとモデルの作成まで完了したので、CRUD画面を順次作成していきます。
2.3. 一覧画面の作成
最初に作る画面は、items
テーブルのデータを一覧表示する画面にします。
画面のイメージは以下になります。
2.3.1. ルート定義
まず、routes/web.php
を編集し、一覧画面へのルートを定義します。
<?php
Route::get('/', 'ItemController@index');
- 一覧画面にはGETでリクエストするので、
get
とします。 - 第一引数にURLを指定します。ここにアクセスすると第二引数の関数が実行されます。
2.3.2. コントローラの新規作成
次に、先ほどのルート定義で登場したItemController
を以下コマンドで新規作成します。
/var/www/src# php artisan make:controller ItemController
2.3.3. コントローラの編集
続いて、/app/Http/Controllers/
に新規作成されたItemController.php
を以下の通り編集します。
<?php
namespace App\Http\Controllers;
use App\Item;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ItemController extends Controller
{
/**
* 一覧表示
*
* @param Request $request
* @return Response
*/
public function index(Request $request){
$items = Item::orderBy('created_at', 'desc')->get();
return view('items.index', [
'items' => $items
]);
}
}
- view()について
- 第一引数にビューを指定しています。
'items.index'
と指定することで、後ほど作成するresources/views/items/index.blade.php
がビューとして使用されます。 - 第二引数にビューに渡すデータを連想配列で指定します。ここでは
Item
モデルから登録日の降順で取り出したデータを渡しています。
- 第一引数にビューを指定しています。
2.3.4. ビューの作成
ビューは/resources/views/
に配置します。
2.3.4.1. レイアウト(親ビュー)の新規作成 〜 @yield
の利用 〜
一覧画面本体のビューを作成する前に、全画面で共通する部分を持った、レイアウトとなるビューを新規作成します。
レイアウトのビューファイルはbase.blade.php
という名前にすることにします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>アプリケーション名</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">アプリケーション名</a>
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#Navber" aria-controls="Navber" aria-expanded="false"
aria-label="ナビゲーションの切替">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="Navber">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<!-- TODO:リンク先追加 -->
<a class="nav-link" href="">管理サイト</a>
</li>
<li class="nav-item">
<!-- TODO:リンク先追加 -->
<a class="nav-link" href="">ログアウト</a>
</li>
</ul>
</div>
</nav>
@yield('content')
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</body>
</html>
-
管理サイト
やログアウト
のリンク先は別記事で指定することとし、いったんのところhref=""
としておきます。 -
@yield('content')
の部分に、本記事内で今後作成する一覧画面や登録画面といった子画面のビューが展開されます。
2.3.4.2. 一覧画面(子ビュー)の新規作成 〜 @extends
の利用 〜
続いて、一覧画面のビューとして、index.blade.php
を新規作成します。
こちらは、先ほど作成した共通レイアウトから見て、子のビューとなります。
なお、今回の記事では一覧画面やその他の子ビューの保存場所として、items
ディレクトリを新規に作成することにしました。
src/resources/views/
├── base.blade.php
└── items
└── index.blade.php
一覧画面のビューファイルは以下になります。
@extends('base')
@section('content')
<div class="container">
<div id="myModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">検索条件</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
<span aria-hidden="true">×</span>
</button>
</div>
<form id="filter" method="get">
<div class="modal-body">
<!-- TODO:検索フォーム -->
</div>
</form>
<div class="modal-footer">
<a class="btn btn-outline-secondary" data-dismiss="modal">戻る</a>
<button type="submit" class="btn btn-outline-secondary" form="filter">検索</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<a class="btn btn-secondary filtered" style="visibility:hidden" href="/?page=1">検索を解除</a>
<div class="float-right">
<!-- TODO リンク先追加 -->
<a class="btn btn-outline-secondary" href="">新規</a>
<a class="btn btn-outline-secondary" data-toggle="modal" data-target="#myModal" href="#">検索</a>
</div>
</div>
</div>
<div class="row" >
<div class="col-12">
<!-- TODO:ページネーション -->
</div>
</div>
<div class="row">
<div class="col-12">
<ul class="list-group">
@empty(count($items))
<li class="list-group-item">
対象のデータがありません
</li>
@endempty
@foreach($items as $item)
<li class="list-group-item">
<div class="row">
<div class="col-3">
<p>名前</p>
</div>
<div class="col-9">
<p>{{ $item->name }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>登録日</p>
</div>
<div class="col-9">
<p>{{ $item->created_at }}</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="float-right">
<!-- TODO:リンク先追加 -->
<a class="btn btn-outline-secondary " href="">詳細</a>
<!-- TODO:リンク先追加 -->
<a class="btn btn-outline-secondary " href="">編集</a>
<!-- TODO:リンク先追加 -->
<a class="btn btn-outline-secondary " href="">削除</a>
</div>
</div>
</div>
</li>
@endforeach
</ul>
</div>
</div>
<div class="row" >
<div class="col-12">
<div class="float-right">
<!-- TODO:リンク先追加 -->
<a class="btn btn-outline-secondary" href="">新規</a>
<a class="btn btn-outline-secondary" data-toggle="modal" data-target="#myModal" href="#">検索</a>
</div>
</div>
</div>
</div>
@endsection
-
@extends('base')
と指定することで、共通レイアウトとして作成したbase.blade.php
がベースとして呼び出されます。 -
@section('content')
〜@endsection
の部分が、base.blade.php
から見た@yield('content')
の部分に対応します。 -
@empty(count($items))
〜@endempty
で、コントローラから渡された$items
が0件であるときの表示を行っています。 -
@foreach($items as $item)
〜@endfor
で、コントローラから渡された$items
からデータを取り出し、明細として順次表示しています。 -
{{ $item->name }}
のように{{
と}}
で囲われた部分で、コントローラから渡されたデータを出力できます。
ブラウザでlocalhost
にアクセスすると、以下画面が表示されます。
items
テーブルにはまだデータが1件も登録されていないので、@empty(count($items))
〜@endempty
の部分が表示され、@foreach($items as $item)
〜@endfor
の部分は何も表示されません。
続いて、登録画面を作成します。
2.4. 登録画面の作成
items
テーブルにデータを新規登録するための画面を作成します。
2.4.1. ルート定義
まず、登録画面へのルートを定義します。
URLは、/items/create
としました。
Route::get('/', 'ItemController@index');
Route::get('/items/create', 'ItemController@create'); // 追加
2.4.2. コントローラーの編集
次に、ItemController
に、登録画面を表示するためのcreate
メソッドを追加します。
//略
class ItemController extends Controller
{
//略
/**
* 登録画面の表示
*
* @param Request $request
* @return Response
*/
public function create(Request $request){
return view('items.create');
}
}
- view()の第一引数に
'items.create'
と指定することで、後ほど作成するresources/views/items/create.blade.php
がビューとして使用されます。
2.4.3. ビューの新規作成 〜 csrf_field()
の利用 〜
続いて、登録画面のビューとして、create.blade.php
を新規作成します。
@extends('base')
@section('content')
<div class="container">
<div class="row">
<div class="col-12">
<h2 class="text-center">データ入力</h2>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="float-right">
<!-- TODO: route()を使う -->
<a class="btn btn-outline-secondary" href="/">戻る</a>
<button class="btn btn-outline-secondary save" type="submit" form="myform">保存</button>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<!-- TODO: actionの指定 -->
<form method="POST" action="" id="myform">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach( $errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{ csrf_field() }}
<div class="form-group">
<label>名前</label>
<input type="text" name="name" placeholder="記入例:山田 太郎" class="form-control">
</div>
<div class="form-group">
<label>年齢</label>
<input type="number" name="age" class="form-control">
</div>
<div class="form-group">
<label>性別</label>
<div class="form-check">
<input type="radio" name="sex" id="sex-male" value="1" class="form-check-input">
<label class="form-check-label" for="sex-male">男性</label>
</div>
<div class="form-check">
<input type="radio" name="sex" id="sex-female" value="2" class="form-check-input">
<label class="form-check-label" for="sex-female">女性</label>
</div>
<div class="form-check">
<input type="radio" name="sex" id="sex-unspecified" value="3" class="form-check-input">
<label class="form-check-label" for="sex-unspecified">指定なし</label>
</div>
</div>
<div class="form-group">
<label>備考</label>
<textarea name="memo" class="form-control"></textarea>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="float-right">
<!-- TODO: route()を使う -->
<a class="btn btn-outline-secondary" href="/">戻る</a>
<button class="btn btn-outline-secondary save" type="submit" form="myform">保存</button>
</div>
</div>
</div>
</div>
@endsection
-
@if ($errors->any())
〜@endif
の部分で、登録画面からのリクエストでバリデーションエラーがあった場合のエラーメッセージを表示します。 -
<form>
タグ内に{{ csrf_field() }}
を指定することでCSRF対策となるトークン(下記)が埋め込まれます。
<input type="hidden" name="_token" value="IIEeVyeOLWFLHpkNaOO59GeVKD5CTG7nrrEkwMdZ">
ブラウザでlocalhost/items/create
にアクセスすると、以下画面が表示されます。
2.4.4. 一覧画面から登録画面へのリンクを追加する 〜 action()
の利用 〜
一覧画面の新規
をクリックしたら、登録画面へ遷移するようにします。
//略
<a class="btn btn-outline-secondary" href="{{ action('ItemController@create') }}">新規</a>
//略
-
action()
にコントローラ名@アクション名
を指定することで、ルート定義(下記)に沿って、紐付くURLを導出できます。
//略
Route::get('items/create', 'ItemController@create');
- 他には
route()
を使う方法もあります(後述)。
続いて、登録画面で保存
をクリックした後の処理を作成します。
2.5. 登録処理の作成
ここからは、登録画面で保存
をクリックした後のPOSTリクエストに対する処理を作成します。
2.5.1. ルート定義
まず、登録処理へのルートを定義します。
<?php
Route::get('/', 'ItemController@index');
Route::get('/items/create', 'ItemController@create');
Route::post('/items', 'ItemController@store'); //追加
2.5.2. フォームリクエストによるバリデーション 〜 rules()
の利用 〜
次に、登録画面に入力された各項目のバリデーションを行うための、フォームリクエストを作成します。
下記コマンドで、ItemRequest
を新規作成します。
/var/www/src# php artisan make:request ItemRequest
app/Http/Requests/
に新規作成されたItemRequest.php
を以下の通り編集します。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ItemRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:30',
'age' => '',
'sex' => 'required|integer',
'memo' => 'max:100'
];
}
}
-
authorize()
メソッドは、認証されたユーザーがitems
テーブルにデータを登録することが認可されているかどうか判定するために使用します。現時点ではこのWebアプリに認証や認可の機能は持たせていないので、必ずtrue
を返すようにしています。 -
rules()
メソッドに、バリデーションのルールを記述します。-
max
で最大文字数を指定できます。数値の場合は最大桁数となります(最大値ではありません)。 -
integer
では、マイナス値も入力可能です。
-
なお、age
やsex
に記述したバリデーションは厳密ではなく改善の余地があるかと思いますが、いったんこのままチュートリアルを進めます。
2.5.3. コントローラーの編集
続いて、ItemController
に、登録処理を行うためのstore
メソッドを追加します。
<?php
namespace App\Http\Controllers;
use App\Item;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Requests\ItemRequest; //忘れずに追加すること
class ItemController extends Controller
{
// 略
/**
* 登録処理
*
* @param ItemRequest $request
* @return Response
*/
public function store(ItemRequest $request){
$item = new Item();
$item->name = $request->name;
$item->age = $request->age;
$item->sex = $request->sex;
$item->memo = $request->memo;
$item->save();
return redirect()->action('ItemController@index');
}
}
-
return redirect()->action('ItemController@index');
とすることで、登録処理が完了したら、ItemControlle@index
に紐付くURL、つまり/
にリダイレクトされます。
2.5.4. 登録画面から登録処理へのリンクを名前付きルートで指定する 〜 name()
とroute()
〜
登録画面で保存
をクリックした後の遷移先を指定します。
//略
<form method="POST" action="{{ action('ItemController@store') }}" id="myform">
//略
上記のように、これまで通りaction()
関数を使っても良いのですが、ルーティングに名前を付けて、その名前を指定する方法もあります。
まず、下記のようにname()
メソッドで、ルーティングに名前を付けます。
//略
Route::post('/items', 'ItemController@store')->name('store');
その上で、route()
関数に先ほど付けた名前を渡すと、そのルーティングに紐付くURL(ここでは/items/
)が展開されます。
//略
<form method="POST" action="{{ route('store') }}" id="myform">
//略
これで、登録画面からの保存
が可能となり、データ保存後は一覧画面へ遷移するようになりました。
2.5.5. 画面サンプル
ここまで作成した登録画面と一覧画面は以下のようになります。
2.5.5.1. 登録画面サンプル
2.5.5.2. 登録処理完了後の一覧画面サンプル
2.5.6. バリデーションエラーメッセージの表示
登録画面の入力内容に対してバリデーションエラーが発生すると、登録画面へリダイレクトされ、登録画面のビューの下記処理により、エラーメッセージが表示されます。
//略
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach( $errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
//略
例えば、登録画面で項目に何も入力せずに、保存
をクリックすると下記の表示になります。
デフォルトでは以下の通り英語ですが、次はこれを日本語化します。
- The name field is required.
- The sex field is required.
2.5.7. バリデーションエラーメッセージの日本語化
2.5.7.1. 項目名の日本語化
まず、登録画面からのPOSTリクエストの各項目に対して、日本語の名前を付けます。
それには、ItemRequest
のattrebutes()
メソッドで下記のように指定します。
//略
class ItemRequest extends FormRequest
{
//略
/**
* 項目名
*
* @return array
*/
public function attributes()
{
return [
'name' => '名前',
'age' => '年齢',
'sex' => '性別',
'memo' => '備考'
];
}
}
これにより、先ほどのバリデーションエラーメッセージは以下のように変化します。
- The 氏名 field is required.
- The 性別 field is required.
項目名は日本語化されたものの、定型文は英語なので次はこれも日本語化します。
2.5.7.2. 定型文の日本語化
次に定型文を英語化していきます。
まず、/config/app.php
を以下の通り編集します。
<php?
return [
//略
'locale' => 'ja',
//略
];
その上で、/resources/lang
配下にja
ディレクトリを作成し、validation.php
を作成します。
/resources/
├── en
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
└── ja
└── validation.php # 新規作成
これにより、バリデーションエラーメッセージの定型文は、ja/validation.php
が使用されるようになります。
ja/validation.php
の中身についてですが、
Laravel公式の日本語サイトには、Laravel標準の/en/validation.php
を日本語化したものが掲載されていますので、その内容の通りとします。
validation.php言語ファイル 5.5 Laravel
<?php
return [
/*
|--------------------------------------------------------------------------
| バリデーション言語行
|--------------------------------------------------------------------------
|
| 以下の言語行はバリデタークラスにより使用されるデフォルトのエラー
| メッセージです。サイズルールのようにいくつかのバリデーションを
| 持っているものもあります。メッセージはご自由に調整してください。
|
*/
'accepted' => ':attributeを承認してください。',
'active_url' => ':attributeが有効なURLではありません。',
'after' => ':attributeには、:dateより後の日付を指定してください。',
//略
以上により、バリデーションエラーメッセージは定型文も含め、日本語化されました。
2.6. 詳細画面
ここからは、items
テーブルのデータ1件を詳細表示するための画面を作成します。
2.6.1. 詳細画面のルート定義と一覧画面・登録画面のルート名追加 〜 where()
の利用 〜
まず、詳細画面へのルートを定義します。
併せて、今後各ビューからのリンクをroute()
でも指定できるよう、既存の一覧画面と登録画面のルートにも、name()
にてルート名を追加することにします。
//略
<?php
Route::get('/', 'ItemController@index')->name('index');
//ルート名追加
Route::get('/items/create', 'ItemController@create')->name('create'); //ルート名追加
Route::post('/items', 'ItemController@store')->name('store');
Route::get('/items/{id}', 'ItemController@show')->name('show')->where('id', '[0-9]+'); //新規追加
-
詳細画面のURLは、
/items/{id}
としました。 -
{id}の部分で、
items
テーブルのレコードのid
を指定します。 -
メソッド名やルート名は
show
としました。 -
where
メソッドで、id
の値を正規表現にて制約をかけることができます。
2.6.2. コントローラの編集 〜 findOrFail()
の利用 〜
次に、ItemController
に、詳細画面を表示するためのshow
メソッドを追加します。
//略
class ItemController extends Controller
{
//略
/**
* 詳細画面の表示
*
* @param string $id
* @return Response
*
*/
public function show(string $id){
$item = Item::findOrFail($id);
return view('items.show')->with('item', $item);
}
//略
}
-
findOrFail
メソッドに、取得したいレコードのid
カラムの値を渡すと、そのレコードがテーブルから取得されます。 -
view()
の第一引数に'items.show'と指定することで、後ほど作成するresources/views/items/show.blade.phpがビューとして使用されます。 -
ビューにデータを渡す方法としては、
view()
の第二引数に連想配列で渡す方法(下記)以外に、上記のようにwith()
を使う方法もあります。
return view('items.show', ['item' => $item]);
2.6.3. ビューの新規作成
続いて、詳細画面のビューとして、show.blade.php
を新規作成します。
@extends('base')
@section('content')
<div class="container">
<h2 class="text-center">詳細表示</h2>
<div class="row">
<div class="col-12">
<a class="btn btn-outline-secondary float-right" href="{{ route('index') }}">戻る</a>
</div>
</div>
<div class="row">
<div class="col-3">
<p>名前</p>
</div>
<div class="col-9">
<p>{{ $item->name }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>年齢</p>
</div>
<div class="col-9">
<p>{{ $item->age }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>性別</p>
</div>
<div class="col-9">
@if($item->sex === 1)
<p>男性</p>
@elseif($item->sex === 2)
<p>女性</p>
@elseif($item->sex === 3)
<p>指定なし</p>
@else
<p></p>
@endif
</div>
</div>
<div class="row">
<div class="col-3">
<p>備考</p>
</div>
<div class="col-9">
<p>{{ $item->memo }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>登録日</p>
</div>
<div class="col-9">
<p>{{ $item->created_at }}</p>
</div>
</div>
<div class="row">
<div class="col-12">
<a class="btn btn-outline-secondary float-right" href="{{ route('index') }}">戻る</a>
</div>
</div>
</div>
@endsection
-
@if($item->sex === 1)
〜@endif
の部分で、sex
カラムの値に応じて'男性'などの文字列を表示するようにしています。今後、sex
カラムを扱う画面が増えた場合に、それらのビューに毎回これと同等のロジックを埋め込むのは保守性が悪いので改善の余地があるかと思いますが、いったんこの内容のままチュートリアルを進めます。
2.6.4. 一覧画面から詳細画面へのリンクを追加する 〜 パラメータありのroute()
〜
一覧画面の詳細
をクリックしたら、詳細画面へ遷移するようにします。
//略
<a class="btn btn-outline-secondary " href="{{ route('show', ['id' => $item->id]) }}">詳細</a>
//略
- 一覧画面のURLは
items/{id}
なので、{id}
部分を指定する必要があります。route()
の第二引数で、そうしたパラメータ部分を連想配列で指定します。
2.7. 編集画面と編集処理
ここからは、itemsテーブル
のデータを更新するための編集画面と編集処理を作成します。
2.7.1. ルート定義
まず、編集画面へのルートと、編集画面からのリクエストを処理する編集処理へのルートを定義します。
<?php
Route::get('/', 'ItemController@index')->name('index');
Route::get('/items/create', 'ItemController@create')->name('create');
Route::post('/items', 'ItemController@store')->name('store');
Route::get('/items/{id}', 'ItemController@show')->name('show')->where('id', '[0-9]+');
Route::get('/items/{id}/edit', 'ItemController@edit')->name('edit')->where('id', '[0-9]+'); //追加
Route::patch('/items/{id}', 'ItemController@update')->name('update')->where('id', '[0-9]+'); //追加
- 今回の編集処理では、編集画面で変更された項目についてのみ、
items
テーブルのレコードのカラムを更新するので、HTTPのメソッドとして'部分更新'を表すpatch
を使用するようにしています。
2.7.2. コントローラの編集とItem
モデルへの$fillable
の追加
次に、ItemController
に、編集画面を表示するためのedit
メソッドと、編集処理を行うためのupdate
メソッドを追加します。
//略
class ItemController extends Controller
{
//略
/**
* 編集画面の表示
*
* @param String $id
* @return Response
*
*/
public function edit(String $id){
return view('items.edit')->with('item', Item::findOrFail($id));
}
/**
* 編集処理
*
* @param ItemRequest $request
* @param string $id
* @return Response
*
*/
public function update(ItemRequest $request, string $id){
$item = Item::findOrFail($id);
$item->fill($request->all())->save();
return redirect()->route('index');
}
}
-
update
メソッド内-
fill($request->all())
とすることで、更新するカラム名をひとつひとつ指定しなくて済んでいます。 - ただし、
fill()
を使う場合は、更新しても良いカラム名をモデルの$fillable
で指定しておく必要があります。
-
Item
モデルに以下の通り$fillable
を追加します。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
protected $fillable = [
'name',
'age',
'sex',
'memo'
];
}
2.7.3. ビューの新規作成 〜 method_field('PATCH')
の利用 〜
続いて、編集画面のビューとして、edit.blade.php
を新規作成します。
@extends('base')
@section('content')
<div class="container">
<div class="row">
<div class="col-12">
<h2 class="text-center">データ入力</h2>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="float-right">
<a class="btn btn-outline-secondary" href="{{ route('index') }}">戻る</a>
<button class="btn btn-outline-secondary save" type="submit" form="myform">保存</button>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="POST" action="{{ route('update', ['id' => $item->id ]) }}" id="myform">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach( $errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{ csrf_field() }}
{{ method_field('PATCH') }}
<div class="form-group">
<label>名前</label>
<input type="text" name="name" value="{{ $item->name }}"placeholder="記入例:山田 太郎" class="form-control">
</div>
<div class="form-group">
<label>年齢</label>
<input type="number" name="age" value="{{ $item->age }}" class="form-control">
</div>
<div class="form-group">
<label>性別</label>
<div class="form-check">
<input type="radio" name="sex" id="sex-male" value="1" @if($item->sex === 1) checked @endif class="form-check-input">
<label class="form-check-label" for="sex-male">男性</label>
</div>
<div class="form-check">
<input type="radio" name="sex" id="sex-female" value="2" @if($item->sex === 2) checked @endif class="form-check-input">
<label class="form-check-label" for="sex-female">女性</label>
</div>
<div class="form-check">
<input type="radio" name="sex" id="sex-unspecified" value="3" @if($item->sex === 3) checked @endif class="form-check-input">
<label class="form-check-label" for="sex-unspecified">指定なし</label>
</div>
</div>
<div class="form-group">
<label>備考</label>
<textarea name="memo" class="form-control">{{ $item->memo }}</textarea>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="float-right">
<a class="btn btn-outline-secondary" href="{{ route('index') }}">戻る</a>
<button class="btn btn-outline-secondary save" type="submit" form="myform">保存</button>
</div>
</div>
</div>
</div>
@endsection
-
<form>
タグ内に{{ method_field('PATCH') }}
を指定することで、<input type="hidden" name="_method" value="PATCH">
が埋め込まれます。これにより、LaravelではPOST
であるリクエストをPATCH
として処理します。
HTMLフォームはPUT、PATCH、DELETEアクションをサポートしていません。ですから、HTMLフォームから呼ばれるPUT、PATCH、DELETEルートを定義する時、フォームに_method隠しフィールドを追加する必要があります。_methodフィールドとして送られた値は、HTTPリクエストメソッドとして使用されます。
- 画面の見た目としては、
create.blade.php
(登録画面)と全く一緒であるため、ビューファイルを共通化できそうですが、以下の違いをビューファイル側で吸収する必要があるため、今回は別ビューファイルとしました。-
保存
をクリックした後の遷移先の違い(/items/{id}
と/items
) - 擬似フォームメソッドの有無(
{{ method_field('PATCH') }}
の有無) - コントローラから渡されるデータの有無(
$items
の有無)
-
2.7.4. 一覧画面から編集画面へのリンクを追加する
一覧画面の編集
をクリックしたら、編集画面へ遷移するようにします。
//略
<a class="btn btn-outline-secondary " href="{{ route('edit', ['id' => $item->id]) }}">編集</a>
//略
2.8. 削除確認画面と削除処理
ここからは、items
テーブルのデータを削除する際の削除確認画面と削除処理を作成します。
2.8.1. ルート定義とコントローラ 〜 Route Model Bindingの利用 〜
まず、削除確認画面へのルートと、削除確認画面からのリクエストを処理する削除処理へのルートを定義します。
<?php
Route::get('/', 'ItemController@index')->name('index');
Route::get('/items/create', 'ItemController@create')->name('create');
Route::post('/items', 'ItemController@store')->name('store');
Route::get('/items/{id}', 'ItemController@show')->name('show')->where('id', '[0-9]+');
Route::get('/items/{id}/edit', 'ItemController@edit')->name('edit')->where('id', '[0-9]+');
Route::patch('/items/{id}', 'ItemController@update')->name('update')->where('id', '[0-9]+');
Route::get('/items/{item}/delete', 'ItemController@delete')->name('delete')->where('item', '[0-9]+'); //追加
Route::delete('/items/{item}', 'ItemController@destroy')->name('destroy')->where('item', '[0-9]+'); //追加
次に、ItemController
に、削除確認画面を表示するためのdelete
メソッドと、削除処理を行うためのdestroy
メソッドを追加します。
//略
class ItemController extends Controller
{
//略
/**
* 削除確認画面の表示
*
* @param Item $item
* @return Response
*
*/
public function delete(Item $item){
return view('items.delete')->with('item', $item);
}
/**
* 削除処理
*
* @param Item $item
* @return Response
* @throws \Exception
*/
public function destroy(Item $item){
$item->delete();
return redirect()->route('index');
}
}
今回のルート定義では、コントローラへ渡すパラメータとして{id}
ではなく{item}
といったようにモデルの名称(小文字、単数形)にしました。
加えて、コントローラ側の各メソッドでは、(Item $item)
といったようにそのモデルを型宣言しています。
すると、$item
には、パラメータに指定した値をid
カラムとするレコードが代入されます。
こうした処理の流れをRoute Model Bindingと呼びます。
これにより、$item = Item::findOrFail($id);
といった処理を記述する必要が無くなります。
2.8.2. ビューの新規作成
続いて、削除確認画面のビューとして、delete.blade.php
を新規作成します。
2.8.2.1. データ内容表示部分を詳細画面と共通化する
削除確認画面は以下のイメージとなります。
名前〜更新日までの表示内容は、詳細画面(show.blade.php
)と全く同じです。
そこで、その部分を別ファイルとして切り出し、削除確認画面から呼び出すようにします。同様に詳細画面からも呼び出すように変更します。
2.8.2.2. 共通部分の新規作成
まず、共通の部分を(card.blade.php
)として作成します。
<div class="row">
<div class="col-3">
<p>名前</p>
</div>
<div class="col-9">
<p>{{ $item->name }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>年齢</p>
</div>
<div class="col-9">
<p>{{ $item->age }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>性別</p>
</div>
<div class="col-9">
@if($item->sex === 1)
<p>男性</p>
@elseif($item->sex === 2)
<p>女性</p>
@elseif($item->sex === 3)
<p>指定なし</p>
@else
<p></p>
@endif
</div>
</div>
<div class="row">
<div class="col-3">
<p>備考</p>
</div>
<div class="col-9">
<p>{{ $item->memo }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>登録日</p>
</div>
<div class="col-9">
<p>{{ $item->created_at }}</p>
</div>
</div>
<div class="row">
<div class="col-3">
<p>更新日</p>
</div>
<div class="col-9">
<p>{{ $item->updated_at }}</p>
</div>
</div>
2.8.2.3. 削除確認画面本体の新規作成と共通部分の呼び出し 〜 @include
の利用 〜
次に、削除画面本体のビューファイルとして以下を作成します。
@extends('base')
@section('content')
<div class="container">
<h2 class="text-center">データ削除</h2>
<p>このデータを削除します。よろしいですか?</p>
<form action="{{ route('destroy', ['item' => $item]) }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<div class="row">
<div class="col-12">
<div class="float-right">
<a class="btn btn-outline-secondary" href="{{ route('index') }}">戻る</a>
<input type="submit" class="btn btn-outline-secondary" value="削除" />
</div>
</div>
</div>
@include('items.card')
<div class="row">
<div class="col-12">
<div class="float-right">
<a class="btn btn-outline-secondary" href="{{ route('index') }}">戻る</a>
<input type="submit" class="btn btn-outline-secondary" value="削除" />
</div>
</div>
</div>
</form>
</div>
@endsection
-
@include('items.card')
により、この部分に``card.blade.php`が展開されます -
<form>
タグ内に{{ method_field('DELETE') }}
を指定することで、<input type="hidden" name="_method" value="DELETE">
が埋め込まれます。これにより、LaravelではPOSTであるリクエストをDELETEとして処理します。
2.8.2.4. 詳細画面本体からの共通部分の呼び出し
詳細画面のビューファイルも以下の通り、@include('items.card')
を利用します。
@extends('base')
@section('content')
<div class="container">
<h2 class="text-center">詳細表示</h2>
<div class="row">
<div class="col-12">
<a class="btn btn-outline-secondary float-right" href="{{ route('index') }}">戻る</a>
</div>
</div>
@include('items.card')
<div class="row">
<div class="col-12">
<a class="btn btn-outline-secondary float-right" href="{{ route('index') }}">戻る</a>
</div>
</div>
</div>
@endsection
2.8.3. 一覧画面から削除確認画面へのリンクを追加する
一覧画面の削除
をクリックしたら、削除確認画面へ遷移するようにします。
//略
<a class="btn btn-outline-secondary " href="{{ route('delete', ['item' => $item]) }}">削除</a>
//略
最後に
以上でテーブルとCRUD画面の作成は完了です。
次の記事では、一覧画面に検索機能とページネーションを追加します。