データベース設計からフロントエンドまで含めた、記事(Article)機能の CRUD リソースを作成するプロセスを解説します。
- データベースマイグレーションの定義 まずはプロジェクトにデータベーススキーマを構築します。Artisan ツールを使用して新しいテーブルを作成します。
php artisan make:migration create_articles_schema
生成されたファイルは database/migrations ディレクトリに保存され、タイムスタンプを含む一意の名前が付けられています。以下の内容でスキーマを定義してください。
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateArticlesSchema extends Migration
{
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->increments('id');
$table->string('title', 100)->nullable(false);
$table->text('body')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('articles');
}
}
マイグレーションを実行して実際にテーブルを生成します。エラーが発生した場合(デフォルトの User テーブル関連など)、既存のインデックス設定を確認し調整が必要です。
php artisan migrate
- オブジェクト指向のリポジトリモデル(ORM)設定 Laravel の Eloquent ORM を利用してデータアクセス層を抽象化します。
php artisan make:model Article
生成されたクラスには明示的にテーブル名を設定します(デフォルトでは複数形を使いますが、ここでは単数形を使用するため)。
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $table = 'articles';
// フィールド名のマッピングや検証ルールを定義可能
protected $fillable = ['title', 'body'];
}
- ルートとコントローラーの実装 MVC パターンの核心となるコントローラーを作成します。
php artisan make:controller ArticleController --resource
routes/web.php にリソースルーティングを設定します。
<?php
Route::resource('articles', 'ArticleController');
メインロジックの記述 コントローラー内でメソッドを実装します。変数名を変更し、コード構造を見やすくしています。
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Article;
class ArticleController extends Controller
{
/**
* 一覧表示
*/
public function index(Request $request)
{
$list = Article::orderBy('created_at', 'desc')->get();
return view('articles.list', compact('list'));
}
/**
* 新規登録フォーム
*/
public function create()
{
return view('articles.form');
}
/**
* 登録処理
*/
public function store(Request $request)
{
$params = $request->all();
$record = new Article;
$record->fill($params);
if ($record->save()) {
return redirect()->route('articles.index');
} else {
return back()->withInput()->withErrors('処理中に問題が発生しました');
}
}
/**
* 詳細表示
*/
public function show($identifier)
{
$detail = Article::findOrFail($identifier);
return view('articles.detail', compact('detail'));
}
/**
* 編集画面表示
*/
public function edit($identifier)
{
$item = Article::findOrFail($identifier);
return view('articles.form', compact('item'));
}
/**
* 更新処理
*/
public function update(Request $request, $identifier)
{
$target = Article::findOrFail($identifier);
$data = $request->only(['title', 'body']);
if ($target->update($data)) {
return redirect()->route('articles.show', $identifier);
}
return back()->withInput()->withErrors('更新失敗');
}
/**
* 削除処理
*/
public function destroy($identifier)
{
$deleteTarget = Article::findOrFail($identifier);
$deleteTarget->delete();
return redirect()->route('articles.index');
}
}
- Blade テンプレートの構築 デザインレイアウトとして共通の親テンプレートを作成し、個別ページでこれを継承します。
共通レイアウト (resources/views/layouts/master.blade.php)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>記事管理システム</title>
<link href="/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container">
<a class="navbar-brand" href="/">Admin Panel</a>
</div>
</nav>
<div class="container">
@yield('content')
</div>
<script src="//code.jquery.com/jquery.min.js"></script>
</body>
</html>
新規作成フォーム (resources/views/articles/form.blade.php) POST と PUT メソッドを適切にハンドリングします。
<!-- master.blade.php から拡張 -->
@extends('layouts.master')
@section('content')
<div class="panel panel-default">
<div class="panel-heading">@if(isset($item))編集<else>新規作成</endif></div>
<div class="panel-body">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="@if(isset($item)){{ url('articles/'.$item->id) }}@else{{ url('articles') }}@endif" method="POST">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<!-- 編集時はメソッド偽装が必要 -->
@if(isset($item))
<input type="hidden" name="_method" value="PUT">
@endif
件名:<input type="text" name="title" class="form-control" value="{{ old('title', isset($item) ? $item->title : '') }}" required><br>
本文:<textarea name="body" rows="10" class="form-control">{{ old('body', isset($item) ? $item->body : '') }}</textarea><br>
<button type="submit" class="btn btn-success">保存</button>
<button type="reset" class="btn btn-default">クリア</button>
</form>
</div>
</div>
@endsection
リスト表示 (resources/views/articles/list.blade.php)
@extends('layouts.master')
@section('content')
<h3>記事一覧</h3>
<a href="{{ route('articles.create') }}" class="btn btn-primary mb-3">記事追加</a>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>タイトル</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@forelse ($list as $row)
<tr>
<td>{{ $row->id }}</td>
<td>{{ $row->title }}</td>
<td>
<a href="{{ route('articles.edit', $row->id) }}" class="btn btn-xs btn-warning">編集</a>
<form action="{{ route('articles.destroy', $row->id) }}" method="POST" style="display:inline;">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('削除してもよろしいですか?');">削除</button>
</form>
</td>
</tr>
@empty
<tr><td colspan="3">データはありません</td></tr>
@endforelse
</tbody>
</table>
@endsection
詳細表示 (resources/views/articles/detail.blade.php)
@extends('layouts.master')
@section('content')
<div class="well">
<h2>{{ $detail->title }}</h2>
<hr>
<div class="text">{{ nl2br(e($detail->body)) }}</div>
<div style="margin-top:20px;">
<a href="{{ route('articles.edit', $detail->id) }}">この項目を編集</a> |
<a href="{{ route('articles.index') }}">一覧に戻る</a>
</div>
</div>
@endsection