Laravelでの単純なCRUD機能作成時の備忘録。
最終的なブラウザ表示
最終的なコード
■ ルーティング
実装機能 | HTTPメソッド | メソッド |
---|---|---|
一覧表示 | get | index |
新規作成 | get → post | create → store |
詳細表示 | get | show |
編集 | get → post | edit → update |
削除 | delete | destroy |
web.php
Route::get('/', function () {
return redirect('/articles');
});
Route::get('/articles', [App\Http\Controllers\ArticleController::class, 'index'])->name('article.list'); // 一覧表示ページ
Route::get('/article/new', [App\Http\Controllers\ArticleController::class, 'create'])->name('article.new'); // 新規投稿ページ
Route::post('/article', [App\Http\Controllers\ArticleController::class, 'store'])->name('article.store'); // 新規保存
Route::get('/article/{id}', [App\Http\Controllers\ArticleController::class, 'show'])->name('article.show'); // 詳細表示ページ
Route::delete('/article/{id}', [App\Http\Controllers\ArticleController::class, 'destroy'])->name('article.delete'); // 削除
Route::get('/article/edit/{id}', [App\Http\Controllers\ArticleController::class, 'edit'])->name('article.edit'); // 編集ページ表示
Route::post('/article/update/{id}', [App\Http\Controllers\ArticleController::class, 'update'])->name('article.update'); // 更新
■ コントローラー
Articleコントローラー
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller{
// 一覧表示
public function index(Request $request){
if ($request->filled('keyword')) {
$keyword = $request->input('keyword');
$message = '検索キーワード: '.$keyword;
$articles = Article::where('content', 'like', '%'.$keyword.'%')->get();
}else{
$message = "検索キーワードを入力してください。";
$articles = Article::all();
}
return view('index', ['message' => $message, 'articles' => $articles]);
}
// 新規投稿ページ表示
public function create(Request $request){
$message = '投稿フォーム: ';
return view('new', ['message'=>$message]);
}
// 新規保存
public function store(Request $request){
$article = new Article();
$article->content = $request->content;
$article->user_name = $request->user_name;
$article->save();
return redirect()->route('article.show', ['id' => $article->id]);
}
// 詳細表示
public function show(Request $request, $id, Article $article){
$message = '記事の内容: ';
$article = Article::find($id);
return view('show', ['message' => $message, 'article' => $article]);
}
// 編集ページ表示
public function edit(Request $request, $id, Article $article){
$message = '記事の編集: '.$id;
$article = Article::find($id);
return view('edit', ['message'=>$message, 'article'=>$article]);
}
// 更新
public function update(Request $request, $id, Article $article){
$article = Article::find($id);
$article->content = $request->content;
$article->user_name = $request->user_name;
$article->save();
return redirect()->route('article.show', ['id' => $article->id]);
}
// 削除
public function destroy(Request $request, $id, Article $article){
$article = Article::find($id);
$article -> delete();
return redirect("/articles");
}
}
■ ビュー
- Bootstrapを使用。
- 共通部分
layout.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset='utf-8'>
<title>Laravel_sample</title>
<style>body {padding: 10px;}</style>
@include('style-sheet') <!-- Bootstrapの読み込み -->
</head>
<body>
@include('nav') <!-- ナビゲーションバー -->
<div class='container'>
@yield('content')
</div>
</body>
</html>
- ナビゲーションバー
nav.blade.php
<nav class='navbar navbar-expand-md navbar-dark bg-dark fixed-top'>
<a class='navbar-brand' href='{{ route("article.list") }}'>My Articles📗</a>
</nav>
style-sheet.blade.php
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<style>body {padding-top: 80px;}</style> <!-- 設置位置の調節 -->
- 一覧表示ページ(root)
一覧表示ページ(index.blade.php)
@extends('layout')
@section('content')
<h1>一覧表示ページ</h1>
<p>{{ $message }}</p>
@include('search') <!-- 検索フォーム -->
<table class="table table-striped table-hover">
@foreach ($articles as $article)
<tr>
<td>
<a href='{{ route("article.show", ["id" => $article->id]) }}' >
{{ $article -> content }}
</a>
</td>
<td>{{ $article -> user_name }}</td>
<td>{{ $article -> created_at }}</td>
</tr>
@endforeach
</tabel>
<div>
<a href = '{{ route("article.new") }}' class="btn btn-primary">投稿</a>
</div>
@endsection
- 新規投稿ページ
新規投稿ページ(new.blade.php)
@extends('layout')
@section('content')
<h1>新規投稿ページ</h1>
<p>{{ $message }}</p>
{{ Form::open(['route'=>'article.store']) }}
<div class='form-group'>
{{ Form::label('content', '内容:') }}
{{ Form::text('content', null, ['placeholder'=>'内容を入力']) }}
</div>
<div class='form-group'>
{{ Form::label('user_name', '投稿者名: ') }}
{{ Form::text('user_name', null, ['placeholder'=>'投稿者名を入力']) }}
</div>
<div class='form-group'>
{{ Form::submit('投稿', ['class' => 'btn btn-primary']) }} <!-- 投稿ボタン -->
<a href='{{ route("article.list") }}'>もどる</a> <!-- indexページへのリンク -->
</div>
{{ Form::close() }}
@endsection
- 詳細表示ページ
詳細表示ページ(show.blade.php)
@extends('layout')
@section('content')
<h1>詳細表示ページ</h1>
<p>{{ $message }}</p>
<p>{{ $article -> content }}</p>
<p>{{ $article -> user_name }}</p>
<p>{{ $article -> created_at }}</p>
<p>
<a href='{{ route("article.edit", ["id"=> $article->id]) }}' class="btn btn-secondary">編集</a>
<a href = '{{ route("article.list") }}' class="btn btn-outline-secondary">戻る</a>
</p>
<div>
{{ Form::open(['method' => 'delete', 'route' => ['article.delete', $article -> id]]) }}
{{ form::submit("削除ボタン") }}
{{ Form::close() }}
</div>
@endsection
- 編集ページ
編集ページ(edit.blade.php)
@extends('layout')
@section('content')
<h1>編集ページ</h1>
<p>{{ $message }}</p>
{{ Form::model($article, ['route'=> ['article.update', $article->id] ]) }}
<div class='form-group'>
{{ Form::label('content', '内容:') }}
{{ Form::text('content', null) }}
</div>
<div class='form-group'>
{{ Form::label('user_name', '投稿者名: ') }}
{{ Form::text('user_name', null) }}
</div>
<div class='form-group'>
{{ Form::submit('保存', ['class' => 'btn btn-primary']) }}
<a href='{{ route("article.show", ["id"=> $article->id]) }}'>もどる</a>
</div>
{{ Form::close() }}
@endsection
- 検索フォーム
- 部分テンプレートで作成し、indexページに設置。
検索フォーム(search.blade.php)
{{ Form::open(['method' => 'get']) }}
{{ csrf_field() }}
<div class='form-group'>
{{ Form::label('keyword', 'キーワード:') }}
{{ Form::text('keyword', null, ['class' => 'form-control'], ['placeholder'=>'キーワードから記事を検索できます']) }}
</div>
<div class='form-group'>
{{ Form::submit('検索', ['class' => 'btn btn-outline-primary']) }}
<a href='{{ route("article.list") }}'>クリア</a>
</div>
{{ Form::close() }}
各論
前提
- MySQLに、Articlesテーブル(content、user_nameカラム)作成済みの状態とする。
- 動作確認のために、rootページにコントローラーからデータを送れるか?、表示できるか? 程度は確認しとく。
参)コントローラー作成コマンド
% php artisan make:controller articleController
動作確認(ルーティング→コントローラー→indexビュー)
// コントローラー
public function index(){
$message = '動作確認用';
return view('index', ['message' => $message]); // indexビューに、message を渡してみる
}
// ルーティング
Route::get('/', function () {
return redirect('/articles');
});
// ビュー(index.brade.php で、messageを取得してみる)
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Laravel_sample</title>
<style>body {padding: 10px;}</style>
</head>
<body>
<p>{{ $message }}</p>
</body>
</html>
CRUD機能 の実装
■ 一覧表示(index)
- コントローラー(モデルから全データを取得し、ビューに送る) → ビュー(一覧表示) の流れ。
/app/Http/Controllers/ArticleController.php
public function index(){
$message = 'Hello world';
$articles = Article::all(); // モデルから全データ取得
return view('index', ['message' => $message, 'articles' => $articles]); // ビューに渡す
}
/resources/views/index.blade.php
@extends('layout') // 部分テンプレートを呼び出す
@section('content')
<h1>一覧表示ページ</h1>
<p>{{ $message }}</p> // コントローラーから $message 取得
@foreach ($articles as $article) // コントローラーから $article をループで取得
<p>{{ $article->content }}</p>
@endforeach
@endsection
■ 詳細表示(show)
- コントローラー(モデルから1つの指定データ取得し、ビューに送る) → ビュー(詳細表示) の流れ。
/app/Http/Controllers/ArticleController.php
public function show(Request $request, $id, Article $article){ // id、記事情報が必要
$message = '詳細表示: '.$id;
$article = Article::find($id); // モデルから指定データ取得
return view('show', ['message' => $message, 'article' => $article]); // ビューに渡す
}
- 詳細表示ページを作成。
/resources/views/show.blade.php
<h1>詳細表示ページ</h1>
<p>{{ $message }}</p>
<p>{{ $article->content }}</p>
<p>
<a href='{{ route("article.list") }}'>戻る</a> // 一覧表示ページへのリンク (例:ルーティング名「article.list」と名付けてる)
</p>
- 一覧表示ページに、詳細表示ページへのリンクを貼る。
/resources/views/index.blade.php
@foreach ($articles as $article)
<p>
<a href='{{ route("article.show", ["id" => $article->id]) }}'>
{{ $article->content }}
{{ $article->user_name }}
{{ $article->created_at }}
</a>
</p>
@endforeach
■ 新規投稿(create、store)
- 投稿ページへのルーティングを設定。
-
記述順序に注意!! newよりshowを先に記述するとエラー!
/article/{id}
を探しに行っちゃうから。
-
記述順序に注意!! newよりshowを先に記述するとエラー!
/routes/web.php
Route::get('/articles', [App\Http\Controllers\ArticleController::class, 'index'])->name('article.list'); // 一覧表示ページ
Route::get('/article/new', [App\Http\Controllers\ArticleController::class, 'create'])->name('article.new'); // 新規投稿ページ
Route::get('/article/{id}', [App\Http\Controllers\ArticleController::class, 'show'])->name('article.show'); // 詳細表示ページ
- コントローラーに、投稿ページ表示のためのメソッド(create)を追加。
/app/Http/Controllers/ArticleController.php
public function create(Request $request){ // createでは、取得したい情報はない
// 確認用:フォームを使わずに、固定テキストや日時を格納する場合 (※ storeメソッドは不要)
$article = new Article(); // インスタンス作成
$article->content = '投稿内容';
$article->user_name = '投稿者';
$article->save();
return redirect('/articles'); // 保存したら、indexページにリダイレクト
// フォームから、投稿する場合
$message = '投稿フォーム: ';
return view('new', ['message'=>$message]); // newページへ送る
}
- 続いて、保存のためのメソッド(store)も追加。
app/Http/Controllers/ArticleController.php
public function store(Request $request){
$article = new Article(); // インスタンス作成
$article->content = $request->content; // 投稿内容
$article->user_name = $request->user_name; // 投稿者名
$article->save(); // 保存
// レコード保存後に、showページへデータを渡してリダイレクト
return redirect('article.show', ['id' => $article->id]);
}
- indexページに投稿ページへのリンクを設置。
- 上で、新規投稿ページへのルーティング名は、「article.new」と名付けたので、それを使用。
/resources/views/index.blade.php
<div>
<a href='{{ route("article.new") }}'>新規投稿</a> // 新規投稿ページへのリンク
</div>
- 最後に、投稿フォームを作成する。
- 投稿フォームを部分テンプレートとして作成。
- Laravelでのフォーム設置には、laravelcollective/html ライブラリ のインストールが必要。
注意)インストールを忘れると、Class 'Form' not found
エラー!!
ターミナルでフォーム使用時に必要なライブラリをインストール
% composer require laravelcollective/html
// メモリ制限エラー( Allowed memory size of 1610612736 bytes exhausted)の場合は、メモリを上げてインストールしてみる
% php -d memory_limit=-1 /usr/local/bin/composer require laravelcollective/html
% composer info // インストール済みか確認
resources/views/new.blade.php
@extends('layout')
@section('content')
<h1>投稿フォーム</h1>
<p>{{ $message }}</p>
{{ Form::open(['route' => 'article.store']) }} <!-- 投稿フォーム -->
<div class='form-group'> <!-- 投稿内容の記述エリア -->
{{ Form::label('content', '内容:') }}
{{ Form::text('content', null, ['placeholder'=>'内容を入力']) }}
</div>
<div class='form-group'> <!-- 投稿者名の記述エリア -->
{{ Form::label('user_name', '投稿者:') }}
{{ Form::text('user_name', null, ['placeholder'=>'投稿者を入力']) }}
</div>
<div class="form-group"> <!-- ボタン設置 -->
{{ Form::submit('投稿ボタン', ['class' => 'btn btn-primary']) }}
<a href='{{ route("article.list") }}'>一覧に戻る</a>
</div>
{{ Form::close() }}
@endsection
■ 編集 (edit、 update)
- 編集ページ表示機能を実装(editメソッド)。
app/Http/Controllers/ArticleController.php
public function edit(Request $request, $id, Article $article){ // 編集には、id情報 と 記事データが必要
$message = '記事の編集: '.$id; // 表示用
$article = Article::find($id); // 編集するレコードをid情報から取得
return view('edit', ['message'=>$message, 'article'=>$article]); // 編集ページに渡す
}
- 編集フォームを作成する。
resources/views/edit.blade.php
@extends('layout')
@section('content')
<h1>編集フォーム</h1>
<p>{{ $message }}</p>
<!-- Form::model : 編集内容を表示してくれる -->
{{ Form::model($article, ['route' => ['article.update', $article->id]]) }}
<div class='form-group'>
{{ Form::label('content', '内容:') }}
{{ Form::text('content', null) }}
</div>
<div class='form-group'>
{{ Form::label('user_name', '投稿者:') }}
{{ Form::text('user_name', null) }}
</div>
<div class="form-group">
{{ Form::submit('保存ボタン', ['class' => 'btn btn-primary']) }}
<a href='{{ route("article.show", ["id" => $article->id]) }}'>もどる</a>
</div>
{{ Form::close() }}
@endsection
- showページに、編集ボタンを設置。
resources/views/show.blade.php
<a href='{{ route("article.edit", ["id" => $article->id]) }}' class='btn btn-outline-primary'>編集ボタン</a>
- 編集内容を保存する更新機能を実装(updateメソッド)。
app/Http/Controllers/ArticleController.php
public function update(Request $request, $id, Article $article){
$article = Article::find($id);
$article->content = $request->content;
$article->user_name = $request->user_name;
$article->save();
return redirect()->route('article.show', ['id' => $article->id]);
}
■ 削除(delete)
Laravelでは、ブラウザとの通信にHTTPメソッドを指定するが、ブラウザが deleteメソッドをサポートしてないので、POSTで隠しメソッドを送って、コントローラーメソッドのdestroyを実行してもらうイメージ。
まず、削除用のルーティングを設定。
/routes/web.php
Route::delete('/article/{id}', [App\Http\Controllers\ArticleController::class, 'destroy'])->name('article.delete');
- コントローラーにアクション(destroy)を定義。
/app/Http/Controllers/ArticleController.php
public function destroy(Request $request, $id, Article $article){ // 削除するレコードのid、記事データ が必要
$article = Article::find($id); // id情報から、レコードを取得
$article->delete(); // 削除
return redirect('/articles'); // 削除後に、一覧表示ページへリダイレクト
}
- 詳細表示ページに、削除ボタンを設置。
/resources/views/show.blade.php
<div>
{{ Form::open(['method' => 'delete', 'route' => ['article.delete', $article->id]]) }}
{{ Form::submit('削除ボタン') }}
{{ Form::close() }}
</div>
■ 検索
- 削除と同じく、Laravelでフォームを使うには、「laravelcollective/html」ライブラリが必要。
参)laravelcollective/htmlのインストール
% composer require laravelcollective/html
% composer info // インストール済みかの確認
- Laravelでのフォームは、
{{ Form:: ... }}
に記述する(Formファサードという)。
(※ ファサード(facade)とは、建物の入り口という意味。) - 1. まず、検索フォームの部分テンプレートを作成する。
- GETメソッドによって、text型で、キー:「keyword」に値を格納してコントローラーにデータを送る。
resources/views/search.blade.php
{{ Form::open(['method' => 'get']) }}
{{ csrf_field() }} <!-- CSRFトークン -->
<div class='form-group'>
{{ Form::label('keyword', 'キーワード:') }}
{{ Form::text('keyword', null, ['class' => 'form-control'], ['placeholder'=>'キーワードから記事を検索できます']) }}
</div>
<div class='form-group'>
{{ Form::submit('検索', ['class' => 'btn btn-outline-primary']) }} <!-- 検索ボタン -->
<a href='{{ route("article.list") }}'>クリア</a> <!-- indexページへのリンク -->
</div>
{{ Form::close() }} <!-- フォーム終了タグ -->
- 一覧表示ページに、部分テンプレートを読み込む。
resources/views/index.blade.php
<p>{{ $message }}</p>
@section('content')
@include('search') <!-- ココで、検索フォームを読み込み -->
@endsection
- 2. 次に、検索機能の実装。
- コントローラーで、フォームから送られたデータを取得し、該当する投稿をビューに渡して表示させる。
app/Http/Controllers/ArticleController.php
public function index(Request $request){ // GETメソッドで受け取ったデータは、引数に指定した変数$requestで取得できる
if ($request->filled('keyword')) { // データの有無で条件分岐
$keyword = $request->input('keyword'); // $request(フォームで送られたデータ) の中から、「keyword」を取得して、変数に定義
$message = '検索結果: '.$keyword; // ビュー表示のため、変数に格納
$articles = モデル名::where('検索するカラム名', 'like', '%'.$keyword.'%')->get();
}else{ // 未入力の場合
$message = "検索キーワードを入力してください。";
$articles = モデル名::all(); // 未入力なら、全データ表示
}
return view('検索結果を送りたいビュー名', ['message' => $message, 'articles' => $articles]); // ビューに渡す
}
✔️ モデル名::where()
- 指定条件で、配列を絞り込む。
検索条件の指定(where)
use Illuminate\Support\Arr;
$array = [100, '200', 300, '400', 500];
$filtered = Arr::where($array, function($value, $key) {
return is_string($value); // string型のみ取得。 [1=>'200', 3=>'400']
});
✔️ filled( )
- 値が空でないか?の判定。
- blank メソッドの逆の動作。
空でないか?の判定(filled関数)
hoge1 = 'value';
$request->filled(hoge1); // true : キーが存在、値が空でない
$request->filled(hoge2); // false : キーが存在しない or 空
参) artisan tinker (対話型コンソール)
- artisan tinker:対話型コンソール
- 手っ取り早く、コンソールからDBにデータを登録したい時などに便利。
対話型コンソールの使い方
% php artisan tinker // 対話型コンソールの起動
>>> Article::all() // 全データ取得
>>> $article = Article::find(1) // 指定idのレコードを取得
>>> $article->content // カラムを出力
>>> $article // レコード出力
>>> $article->delete() // レコード削除
// レコード追加
>>> $article = new Article() // インスタンス作成
>>> $article->content = 'テスト' // 変数に格納
>>> $article->save() // データ保存
>>> exit // CTRL + C でもok
(おまけ) 関連づけたテーブルからのデータ取得
例)articlesテーブル、categoriesテーブルのデータを表示したい
// モデル にリレーションを定義 (例: Articleモデルは、Categoryモデルに対して、1対多の場合)
public function category() {
return $this->belongsTo('App\Models\Category');
}
// ビュー( ※変数はコントローラーで定義 )
{{ $article -> name }} // articlesテーブルのnameカラムを表示
{{ $article -> category -> name }} // articlesテーブルに関連付けたcategoriesテーブルのnameカラムを表示
(おまけ) 関連づけたテーブルへのデータ保存
例)articlesコントローラーから、categoriesテーブルにデータ保存したい
// コントローラー
use App\Models\Category; // Categoryモデルの使用を宣言
:
public function create(){
$categories = Category::all()->pluck('name', 'id'); // Categoryモデルから、nameとidカラムだけ取得
return view('new', ['article' => $article, 'categories'=> $categories] );
}
public function store(Request $request){
$article = new Article();
$article -> content = request('content');
$article -> user_name = request('user_name');
$article -> category_id = request('category_id');
$article -> save();
return redirect() -> route('article.detail', ['article' => $article->id] );
}
// ビュー
{{ Form::open(['route' => 'shop.store']) }}
{{ Form::text('content', null) }} // articlesテーブルへのデータ保存
{{ Form::select('category_id', $categories) }} // categoriesテーブルのデータ選択
{{ Form::submit('登録') }}
{{ Form::close() }}