はじめに
私はプログラミング歴1年の初心者です。
実務でWebサイトのコーディングを1年間行ってきました。
そろそろシステム開発もできるようになりたいということで
LaravelやReactをこれから勉強していこうと思っております。
今回の目的は
ポートフォリオアプリを作成しようと思います。
Laravelの開発を1から行う
gitをCLIで使えるようにする
開発環境は
今回はLaravel×PostgreSQL
でいきます。
GUIツールはTablePlusを使ってみます。
ただし、postgresqlを使うことを途中で断念して
mysqlに切り替えました。
設計
機能一覧
画面設計
URL設計
テーブル定義
前回と同様でスプレッドシートで作成してみました。
Laravelアプリ作成
$ laravel new Hanseee
$ cd Hanseee
PostgreSQLデータベース作成・設定
# postgreSQLのインストール
$ brew search postgresql
$ brew install postgresql
$ postgres --version
# postgreSQLのサーバー起動
$ pg_ctl -D /usr/local/var/postgres start
# データベース一覧取得
$ psql -l
# 環境変数へPATHを通す
$ echo 'export PGDATA=/usr/local/var/postgres' >> ~/.zshrc
$ source ~/.zshrc
# ユーザーアカウントを追加
$ createuser -P [アカウント名]
# データベースを作成
$ createdb [データベース名] -O [アカウント名]なので
$ createdb Hanseee -O [アカウント名]
$ psql -l
○TablePlasを使ってデータベースとつなげる
「Create new connection」をクリック
DBを「PostgreSQL」をクリック
Nameに「local」を指定
Userに「アカウント名」を指定
Databaseに「データベース名」を指定
○.envの設定をする
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=Hanseee
DB_USERNAME=[アカウント名]
DB_PASSWORD=[パスワード]
○PostgreSQLのドライバがないと怒られた
$ php artisan migrate
とすると
could not find driver
とかえってくる
調べましたがよくわからないので、諦めます!
もし次回postgresqlを使うとなれば、Homesteadを使ってみようと思います。
mysqlの設定
// データベース作成
$ mysql -u root
mysql> CREATE DATABASE `todoapp2`;
mysql> SHOW databases;
.envの設定は特になし
database.php
// mysqlのutfmb4をutf8に変更!
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
$ php artisan migrate
GitHubに登録する
この記事のGitHub登録でOKです。
マイグレーション作成
# マイグレーションファイルを作成
$ php artisan make:migration create_categories_table --create=categories
$ problemのも
$ stepのも
xxxx_xx_xx_xxxxxx_create_categories_table.php
// テーブル作成のために編集
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name', 20);
$table->foreignId('user_id')->constrained();
$table->timestamps();
});
problemsのも
stepsのも
ユーザーログイン・新規登録機能
準備
# laravel/uiをインストール
$ composer require laravel/ui
$ php artisan ui vue --auth
web.php
// Routingを設定する
Auth::routes();
Route::get('/', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
resources/views/layout.blade.php
// 共通テンプレートを作成する
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Kataduke</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<header>
<nav class="my-navbar">
<a class="my-navbar-brand" href="/">Hanseee</a>
<div class="my-navbar-control">
@if(Auth::check())
<span class="my-navbar-item">こんにちは, {{ Auth::user()->name }}さん</span>
|
<a href="#" id="logout" class="my-navbar-item">ログアウト</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
@else
<a class="my-navbar-item" href="{{ route('login') }}">ログイン</a>
|
<a class="my-navbar-item" href="{{ route('register') }}">会員登録</a>
@endif
</div>
</nav>
</header>
<main>
@yield('content')
</main>
@if(Auth::check())
<script>
document.getElementById('logout').addEventListener('click', function(event) {
event.preventDefault();
document.getElementById('logout-form').submit();
});
</script>
@endif
</body>
</html>
public/css/styles.css
// cssファイルを作成する
@import url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css');
@import url('https://cdnjs.cloudflare.com/ajax/libs/bootflat/2.0.4/css/bootflat.min.css');
body {
background-color: #eee5d3;
}
.navbar {
margin: 2rem 0 2.5rem 0;
}
.my-navbar {
align-items: center;
background: #709e6b;
display: flex;
height: 6rem;
justify-content: space-between;
padding: 0 2%;
margin-bottom: 3rem;
}
.my-navbar-brand {
font-size: 18px;
}
.my-navbar-brand,
.my-navbar-item {
color: #afc29f;
}
.my-navbar-brand:hover,
a.my-navbar-item:hover {
color: #fff;
}
.table tr td:first-child a {
color: #333;
font-weight: bold;
}
.table td:not(:first-child) {
white-space: nowrap;
width: 60px;
}
.table-room:hover,
.table-room.act {
background-color: #ddae87;
}
.form-control[disabled],
.form-control[readonly] {
background-color: #fff;
}
home.blade.php
// テンプレートを設定する
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">
ようこそ!まずはカテゴリを作成しましょう!
</div>
<div class="panel-body">
<div class="text-center">
<a href="#" class="btn btn-primary">
カテゴリ作成ページへ
</a>
</div>
</div>
</nav>
</div>
</div>
</div>
@endsection
register.blade.php
// テンプレートを設定する
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">会員登録</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
@foreach($errors->all() as $message)
<p>{{ $message }}</p>
@endforeach
</div>
@endif
<form action="{{ route('register') }}" method="POST">
@csrf
<div class="form-group">
<label for="email">メールアドレス</label>
<input type="text" class="form-control" id="email" name="email" value="{{ old('email') }}" placeholder="(例) test@example.com" />
</div>
<div class="form-group">
<label for="name">ユーザー名</label>
<input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}" placeholder="(例) 山田 太郎" />
</div>
<div class="form-group">
<label for="password">パスワード</label>
<input type="password" class="form-control" id="password" name="password" placeholder="パスワードは8文字以上">
</div>
<div class="form-group">
<label for="password-confirm">パスワード(確認)</label>
<input type="password" class="form-control" id="password-confirm" name="password_confirmation" placeholder="パスワードは8文字以上">
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">送信</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
login.blade.php
// テンプレートを設定する
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">ログイン</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
@foreach($errors->all() as $message)
<p>{{ $message }}</p>
@endforeach
</div>
@endif
<form action="{{ route('login') }}" method="POST">
@csrf
<div class="form-group">
<label for="email">メールアドレス</label>
<input type="text" class="form-control" id="email" name="email" value="{{ old('email') }}" />
</div>
<div class="form-group">
<label for="password">パスワード</label>
<input type="password" class="form-control" id="password" name="password" />
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">送信</button>
</div>
</form>
</div>
</nav>
<div class="text-center">
<a href="{{ route('password.request') }}">パスワードの変更はこちらから</a>
</div>
</div>
</div>
</div>
@endsection
バリデーションの日本語化
RegisterController.php
// リダイレクト先を変更
protected $redirectTo = '/';
// Validator::makeメソッドのattributesを設定
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
], [], [
'name' => 'ユーザー名',
'email' => 'メールアドレス',
'password' => 'パスワード',
]);
}
ログイン状態とログアウト状態の出し分け
layout.blade.php
// すでに実装済だが、以下の記述を追加する
@if(Auth::check())
<span class="my-navbar-item">こんにちは, {{ Auth::user()->name }}さん</span>
|
<a href="#" id="logout" class="my-navbar-item">ログアウト</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
@else
<a class="my-navbar-item" href="{{ route('login') }}">ログイン</a>
|
<a class="my-navbar-item" href="{{ route('register') }}">会員登録</a>
@endif
web.php
// ログイン時のみのページにミドルウェアで認証
Route::group(['middleware' => 'auth'], function() {
Route::get('/', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
})
app/Http/Middleware/RedirectIfAuthenticated.php
// ログイン状態時に、他の処理をせずにリダイレクトする
// つまり、ログアウト時のみ他の処理をする設定
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect('/');
}
}
return $next($request);
}
パスワード再設定
// .envでMailtrapに接続
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=ユーザー名
MAIL_PASSWORD=パスワード
MAIL_ENCRYPTION=
auth/passwords/email.blade.php
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">パスワード再発行</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form action="{{ route('password.email') }}" method="POST">
@csrf
<div class="form-group">
<label for="email">メールアドレス</label>
<input type="text" class="form-control" id="email" name="email" />
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">再発行リンクを送る</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
auth/passwords/reset.blade.php
// input hiddenで$tokenをもたせる必要がある
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">パスワード再設定</div>
<div class="panel-body">
<form action="{{ route('password.update') }}" method="POST">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group">
<label for="email">メールアドレス</label>
<input type="text" class="form-control" id="email" name="email" />
</div>
<div class="form-group">
<label for="password">新しいパスワード</label>
<input type="password" class="form-control" id="password" name="password" />
</div>
<div class="form-group">
<label for="password-confirm">新しいパスワード(確認)</label>
<input type="password" class="form-control" id="password-confirm" name="password_confirmation" />
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">再設定</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
Auth/ResetPasswordController.php
// リダイレクト先を変更
protected $redirectTo = '/';
resources/views/mail/password-reset.blade.php
// メールの内容テンプレートを編集
<a href="{{ route('password.reset', ['token' => $token]) }}">
パスワード再設定用リンク
</a>
App/Mail/ResetPassword.php
// $ php artisan make:mail ResetPasswordをする
// tokenに関する記述をする
class ResetPassword extends Mailable
{
use Queueable, SerializesModels;
public $token;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->subject('パスワード再設定リンク')->view('mail.password-reset');
}
}
App/Models/User.php
// 設定したパスワード再設定メールを送信する記述をする
use App\Mail\ResetPassword;
use Illuminate\Support\Facades\Mail;
/**
* パスワード再設定メールを送信する
*/
public function sendPasswordResetNotification($token) {
Mail::to($this)->send(new ResetPassword($token));
}
Category一覧機能
カテゴリ一覧表示機能
web.php
// Routingの設定
Route::group(['middleware' => 'auth'], function() {
Route::get('/categories', [App\Http\Controllers\CategoriesController::class, 'index'])->name('category.index');
});
Models/Category.php
// Categoryモデル作成
$ php artisan make:model Category
Models/User.php
// CategoryとUserのリレーションをもたせる
/**
* ユーザーの設定したカテゴリを取得
*/
public function categories() {
return $this->hasMany(Category::class);
}
database/seeders/CategoriesTableSeeder.php
// Categoryシーダー作成
$ php artisan make:seeder CategoriesTableSeeder
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
public function run()
{
$names = ['仕事', '恋愛', '遊び', 'その他'];
foreach($names as $name) {
DB::table('categories')->insert([
'name' => $name,
'user_id' => '2',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
}
}
CategoriesController.php
// Categoryコントローラー作成
$ php artisan make:controller CategoriesController
use Illuminate\Support\Facades\Auth;
/**
* ユーザーの持つカテゴリの一覧ページを表示する
*/
public function index() {
$categories = Auth::user()->categories()->get();
return view('categories.index', [
'categories' => $categories,
]);
}
resources/views/categories/index.blade.php
// テンプレートを作成
@extends('layout')
@section('content')
<div class="container">
<ul class="nav justify-content-center">
@foreach($categories as $category)
<li class="nav-item">
<a class="nav-link" href="#">{{ $category->name }}</a>
</li>
@endforeach
<li class="nav-item">
<a class="nav-link btn btn-success" href="#">カテゴリ追加</a>
</li>
</ul>
</div>
@endsection
views/layout.blade.php
// bootstrap追加
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
ホーム画面からカテゴリ一覧ページへのリンク
home.blade.php
<a href="{{ route('categories.index') }}" class="btn btn-primary">
カテゴリ一覧ページへ
</a>
Category作成機能
カテゴリ作成ページ表示実装
web.php
Route::group(['middleware' => 'auth'], function() {
Route::get('/categories/create', [App\Http\Controllers\CategoriesController::class, 'create'])->name('categories.create');
Route::post('/categories', [App\Http\Controllers\CategoriesController::class, 'store']);
});
CategoriesController.php
// カテゴリ作成ページ表示
public function create() {
return view('categories.create');
}
views/categories/create.blade.php
// カテゴリ作成ページのテンプレート作成
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">ルームを登録する</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('categories.store') }}" method="post">
@csrf
<div class="form-group">
<label for="name">カテゴリ名</label>
<input type="text" class="form-control" name="name" id="name" value="{{ old('name') }}" />
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">追加</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
カテゴリ保存機能実装
CategoriesController.php
// カテゴリ保存機能
use App\Models\Category;
use App\Http\Requests\CreateCategory;
public function store(CreateCategory $request) {
$category = new Category();
$category->name = $request->name;
Auth::user()->categories()->save($category);
return redirect()->route('categories.index');
}
Requests/CreateCategory.php
// Requestsクラス作成
$ php artisan make:request CreateCategory
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'requiredmax:20',
];
}
エラーメッセージの日本語化
/resources/lang/ja/validation.php
// 日本語化用のファイルを作成
$ mkdir resources/lang/ja
$ cp ./resources/lang/en/validation.php ./resources/lang/ja
'required' => ':attribute は入力必須です。',
'max'の'string' => ':attribute は :max 文字以内で入力してください。',
Requests/CreateCategory.php
/**
* エラーメッセージを日本語化
*
* @return array
*/
public function attributes() {
return [
'name' => 'カテゴリ名',
];
}
config/app.php
'locale' => 'ja',
カテゴリ一覧画面からカテゴリ作成画面へのリンク
views/categories/index.blade.php
<a class="nav-link btn btn-success" href="{{ route('categories.create') }}">カテゴリ追加</a>
Problem一覧機能
課題一覧表示機能の実装
web.php
// Routingの設定
Route::get('/categories/{category}/problems', [App\Http\Controllers\ProblemsController::class, 'index'])->name('problems.index');
Models/Problem.php
// モデル作成
$ php artisan make:model Problem
Models/Category.php
// カテゴリと課題とのリレーション
/**
* カテゴリが持つ課題を取得
*/
public function problems() {
return $this->hasMany(Problem::class);
}
datebase/seeds/ProblemsTableSeeder.php
// シーダー作成
$ php artisan make:seeder ProblemsTableSeeder
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
public function run()
{
$titles = ['タイトル1', 'タイトル2', 'タイトル3'];
foreach($titles as $title) {
DB::table('problems')->insert([
'title' => $title,
'category_id' => 1,
'created_at' =>Carbon::now(),
'updated_at' =>Carbon::now(),
]);
}
}
// シード実装
$ php artisan db:seed --class=ProblemsTableSeeder
ProblemsController.php
// コントローラー作成
$ php artisan make:controller ProblemsController
use Illuminate\Support\Facades\Auth;
use App\Models\Category;
public function index(Category $category) {
$categories = Auth::user()->categories()->get();
$problems = $category->problems()->get();
return view('problems.index', [
'categories' => $categories,
'problems' => $problems,
]);
}
problems/index.blade.php
// テンプレート作成
$ mkdir resources/views/problems
$ touch resources/views/problems/index.blade.php
@extends('layout')
@section('content')
<div class="container">
<ul class="nav justify-content-center mb-5">
@foreach($categories as $category)
<li class="nav-item">
<a class="nav-link" href="#">{{ $category->name }}</a>
</li>
@endforeach
<li class="nav-item">
<a class="nav-link btn btn-success" href="{{ route('categories.create') }}">カテゴリ追加</a>
</li>
</ul>
<div class="row">
<div class="col col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">課題一覧</div>
<div class="panel-body">
<a href="#" class="btn btn-default btn-block">
課題を追加する
</a>
</div>
<table class="table">
<thead>
<tr>
<th>課題</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($problems as $problem)
<tr class="table-problem">
<td><a href="#">{{ $problem->title }}</a></td>
<td><a class='btn btn-primary btn-xs' href="#">編集</a></td>
<td>
<form action="#" method="POST">
@csrf
<input type='submit' value='削除' class='btn btn-danger btn-xs'>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</nav>
</div>
</div>
</div>
@endsection
現在表示されているカテゴリをact状態にする
ProblemsController.php
return view('problems.index', [
'categories' => $categories,
'current_category_id' => $category->id, <-これを追加
'problems' => $problems,
]);
index.blade.php
<li class="nav-item nav-category {{ $current_category_id === $category->id ? 'act' : '' }}">
<a class="nav-link" href="{{ route('problems.index', ['category' => $category->id]) }}">{{ $category->name }}</a>
</li>
styles.css
.nav-category a {
color: #333;
}
.nav-category {
border-radius: 4px;
}
.nav-category:hover,
.nav-category.act {
background-color: #709e6b;
}
.nav-category:hover a,
.nav-category.act a {
color: #fff;
}
.table-problem:hover,
.table-problem.act {
background-color: #709e6b;
}
Problem作成機能
web.php
Route::get('/categories/{category}/problems/create', [App\Http\Controllers\ProblemsController::class, 'create'])->name('problems.create');
Route::post('/categories/{category}/problems', [App\Http\Controllers\ProblemsController::class, 'store'])->name('problems.store');
ProblemsController.php
/**
* 課題の作成ページを表示する
*/
public function create(Category $category) {
return view('problems.create', [
'category' => $category,
]);
}
/**
* 課題を保存する
*/
public function store(Category $category, CreateProblem $request) {
$problem = new Problem();
$problem->title = $request->title;
$category->problems()->save($problem);
return redirect()->route('problems.index', [
'category' => $category,
]);
}
Requests/CreateProblem.php
// Requestsクラスを作成する
$ php artisan make:request CreateProblem
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|max:30'
];
}
/**
* エラーメッセージを日本語化
*
* @return array
*/
public function attributes() {
return [
'title' => '課題タイトル',
];
}
problems/create.blade.php
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">課題を登録する</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('problems.store', ['category' => $category->id]) }}" method="post">
@csrf
<div class="form-group">
<label for="title">課題タイトル</label>
<input type="text" class="form-control" name="title" id="title" value="{{ old('title') }}" />
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">登録</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
課題作成ページへのリンクを貼る
problems/index.blade.php
<a href="{{ route('problems.create', ['category' => $category->id]) }}" class="btn btn-default btn-block">
課題を追加する
</a>
Problem編集機能
web.php
Route::get('/categories/{category}/problems/{problem}/edit', [App\Http\Controllers\ProblemsController::class, 'edit'])->name('problems.edit');
Route::put('/categories/{category}/problems/{problem}/', [App\Http\Controllers\ProblemsController::class, 'update'])->name('problems.update');
ProblemsController.php
/**
* 課題の編集ページを表示する
*/
public function edit(Category $category, Problem $problem) {
return view('problems.edit', [
'category' => $category,
'problem' => $problem,
]);
}
/**
* 課題を更新する
*/
public function update(Category $category, Problem $problem, EditProblem $request) {
$categories = Auth::user()->categories()->get();
$problems = $category->problems()->get();
$problem->title = $request->title;
$problem->save();
return redirect()->route('problems.index', [
'categories' => $categories,
'category' => $category,
'current_category_id' => $category->id,
'problems' => $problems,
'problem' => $problem,
]);
}
problems/edit.blade.php
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">課題を登録する</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('problems.update', ['category' => $category->id, 'problem' => $problem->id]) }}" method="post">
@csrf
@method('PUT')
<div class="form-group">
<label for="title">課題タイトル</label>
<input type="text" class="form-control" name="title" id="title" value="{{ old('title') ?? $problem->title }}" />
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">登録</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
Requests/EditProblem.php
class EditProblem extends CreateProblem
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rule = parent::rules();
return $rule;
}
public function attributes() {
$attributes = parent::attributes();
return $attributes;
}
}
課題編集ページへのリンクを貼る
problems/index.blade.php
<td><a class='btn btn-primary btn-xs' href="{{ route('problems.edit', ['category' => $category->id, 'problem' => $problem->id]) }}">編集</a></td>
Problem削除機能
web.php
Route::delete('/categories/{category}/problems/{problem}', [App\Http\Controllers\ProblemsController::class, 'destroy'])->name('problems.destroy');
ProblemsController.php
/**
* 課題を削除する
*/
public function destroy(Category $category, Problem $problem) {
$problem->delete();
return redirect()->route('problems.index', [
'category' => $category,
'current_category_id' => $category->id,
'problem' => $problem,
]);
}
problems/index.blade.php
<form action="{{ route('problems.destroy', ['category' => $current_category_id, 'problem' => $problem->id]) }}" method="POST">
@csrf
@method('DELETE')
<input type='submit' value='削除' class='btn btn-danger btn-xs'>
</form>
Step一覧機能
web.php
Route::get('/categories/{category}/problems/{problem}/steps', [App\Http\Controllers\StepsController::class, 'index'])->name('steps.index');
Models/StepsController.php
$ php artisan make:model Step
Models/Problem.php
/**
* 課題に対する対策を取得
*/
public function step() {
return $this->hasOne(Step::class);
}
datebase/seeds/StepsTableSeeder.php
// シーダー作成
$ php artisan make:seeder StepsTableSeeder
public function run()
{
$texts = ['対策1です', '対策2です', '対策3です'];
for($i=0; $i<3; $i++) {
DB::table('steps')->insert([
'text' => $texts[$i],
'problem_id' => $i+1,
'created_at' =>Carbon::now(),
'updated_at' =>Carbon::now(),
]);
}
}
// シード実装
$ php artisan db:seed --class=StepsTableSeeder
StepsController.php
// コントローラー作成
$ php artisan make:controller StepsController
use App\Models\Category;
use App\Models\Problem;
/**
* カテゴリに対する対策一覧ページを表示する
*/
public function index(Category $category, Problem $problem) {
$categories = Auth::user()->categories()->get();
$problems = $category->problems()->get();
$step = $problem->step;
return view('steps.index', [
'categories' => $categories,
'current_category_id' => $category->id,
'problems' => $problems,
'step' => $step,
]);
}
steps/index.blade.php
// テンプレート作成
$ mkdir resources/views/steps
$ touch resources/views/steps/index.blade.php
@extends('layout')
@section('content')
<div class="container">
<ul class="nav justify-content-center mb-5">
@foreach($categories as $category)
<li class="nav-item nav-category {{ $current_category_id === $category->id ? 'act' : '' }}">
<a class="nav-link" href="{{ route('problems.index', ['category' => $category->id]) }}">{{ $category->name }}</a>
</li>
@endforeach
<li class="nav-item">
<a class="nav-link btn btn-success" href="{{ route('categories.create') }}">カテゴリ追加</a>
</li>
</ul>
<div class="row">
<div class="col col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">課題一覧</div>
<div class="panel-body">
<a href="{{ route('problems.create', ['category' => $current_category_id]) }}" class="btn btn-default btn-block">
課題を追加する
</a>
</div>
<table class="table">
<thead>
<tr>
<th>課題</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($problems as $problem)
<tr class="table-problem">
<td><a href="{{ route('steps.index', ['category' => $current_category_id, 'problem' => $problem->id]) }}">{{ $problem->title }}</a></td>
<td><a class='btn btn-primary btn-xs' href="{{ route('problems.edit', ['category' => $current_category_id, 'problem' => $problem->id]) }}">編集</a></td>
<td>
<form action="{{ route('problems.destroy', ['category' => $current_category_id, 'problem' => $problem->id]) }}" method="POST">
@csrf
@method('DELETE')
<input type='submit' value='削除' class='btn btn-danger btn-xs'>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</nav>
</div>
<div class="col col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">対策</div>
<div class="panel-body">
@if(empty($step->id))
<a href="#" class="btn btn-default btn-block">
対策を追加する
</a>
@else
<div class="row">
<div class="col offset-10">
<a href="#" class="btn btn-primary btn-xs">
編集
</a>
<form action="#" method="POST">
@csrf
@method('DELETE')
<input type='submit' value='削除' class='btn btn-danger btn-xs'>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
{{$step->text}}
</div>
</div>
@endif
</div>
</nav>
</div>
</div>
</div>
@endsection
Step作成機能
web.php
Route::get('/categories/{category}/problems/{problem}/steps/create', [App\Http\Controllers\StepsController::class, 'create'])->name('steps.create');
Route::post('/categories/{category}/problems/{problem}/steps', [App\Http\Controllers\StepsController::class, 'store'])->name('steps.store');
StepsController.php
/**
* 対策の作成ページを表示する
*/
public function create(Category $category, Problem $problem) {
return view('steps.create', [
'category' => $category,
'current_category_id' => $category->id,
'problem' => $problem,
]);
}
/**
* 対策を保存する
*/
public function store(Category $category, Problem $problem, CreateStep $request) {
$step = new Step();
$step->text = $request->text;
$problem->step()->save($step);
return redirect()->route('steps.index', [
'category' => $category,
'problem' => $problem,
]);
}
CreateStep.php
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'text' => 'required|max:400',
];
}
/**
* エラーメッセージを日本語化
*
* @return array
*/
public function attributes() {
return [
'text' => '対策テキスト',
];
}
steps/create.blade.php
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">対策を登録する</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
<label>課題</label>
<div class="card mb-4">
<div class="card-body">
{{$problem->title}}
</div>
</div>
<form action="{{ route('steps.store', ['category' => $category->id, 'problem' => $problem->id]) }}" method="post">
@csrf
<div class="form-group">
<label for="text">対策</label>
<textarea class="form-control" name="text" id="text">{{ old('text') }}</textarea>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">登録</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
Step編集機能
web.php
Route::get('/categories/{category}/problems/{problem}/steps/{step}/edit', [App\Http\Controllers\StepsController::class, 'edit'])->name('steps.edit');
Route::put('/categories/{category}/problems/{problem}/steps/{step}', [App\Http\Controllers\StepsController::class, 'update'])->name('steps.update');
StepsController.php
/**
* 対策の編集ページを表示する
*/
public function edit(Category $category, Problem $problem, Step $step) {
return view('steps.edit', [
'category' => $category,
'problem' => $problem,
'step' => $step,
]);
}
/**
* 対策を更新する
*/
public function update(Category $category, Problem $problem, Step $step, EditStep $request) {
$step->text = $request->text;
$step->save();
return redirect()->route('steps.index', [
'category' => $category,
'current_category_id' => $category->id,
'problem' => $problem,
'step' => $step,
]);
}
steps/edit.blade.php
@extends('layout')
@section('content')
<div class="container">
<div class="row">
<div class="col col-md-offset-3 col-md-6">
<nav class="panel panel-success">
<div class="panel-heading">対策を編集する</div>
<div class="panel-body">
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
<label>課題</label>
<div class="card mb-4">
<div class="card-body">
{{$problem->title}}
</div>
</div>
<form action="{{ route('steps.update', ['category' => $category->id, 'problem' => $problem->id, 'step' => $step->id]) }}" method="post">
@csrf
@method('PUT')
<div class="form-group">
<label for="text">対策テキスト</label>
<textarea class="form-control" name="text" id="text">{{ old('text') ?? $step->text }}</textarea>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">更新</button>
</div>
</form>
</div>
</nav>
</div>
</div>
</div>
@endsection
Requests/EditStep.php
class EditStep extends CreateStep
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rule = parent::rules();
return $rule;
}
public function attributes() {
$attributes = parent::attributes();
return $attributes;
}
対策削除機能
web.php
Route::delete('/categories/{category}/problems/{problem}/steps/{step}', [App\Http\Controllers\StepsController::class, 'destroy'])->name('steps.destroy');
StepsController.php
/**
* 対策を削除する
*/
public function destroy(Category $category, Problem $problem, Step $step) {
$step->delete();
return redirect()->route('steps.index', [
'category' => $category,
'current_category_id' => $category->id,
'problem' => $problem,
'step' => $step,
]);
}
EditStep.php
class EditStep extends CreateStep
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rule = parent::rules();
return $rule;
}
public function attributes() {
$attributes = parent::attributes();
return $attributes;
}
}
steps/index.blade.php
<form action="{{ route('steps.destroy', ['category' => $current_category_id, 'problem' => $problem->id, 'step' => $step->id]) }}" method="POST">
@csrf
@method('DELETE')
<input type='submit' value='削除' class='btn btn-danger btn-xs'>
</form>
一旦触ってみる
カテゴリ削除機能
web.php
Route::delete('/categoris/{category}', [App\Http\Controllers\CategoriesController::class, 'destroy'])->name('categories.destroy');
CategoriesController.php
/**
* カテゴリを削除する
*/
public function destroy(Category $category) {
$category->delete();
return redirect()->route('categories.index');
}
categories/index.blade.php
<li class="nav-item d-flex">
<a class="nav-link" href="{{ route('problems.index', ['category' => $category->id]) }}">
{{ $category->name }}
<form class="d-inline-block" action="{{ route('categories.destroy', ['category' => $category->id]) }}" method="POST">
@csrf
@method('DELETE')
<input type='submit' value='×' class='btn btn-danger btn-xs'>
</form>
</a>
</li>
problems/index.blade.php
上記と同様
steps/index.blade.php
上記と同様
Herokuでデプロイ
この記事を参考に
今回は
$ git push heroku main
さいごに
次はLaravelのマニュアルを読んでいこうと思います。