LoginSignup
0
1

More than 1 year has passed since last update.

Laravelでアプリを作ってみた!【Hanseee】

Posted at

はじめに

私はプログラミング歴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のマニュアルを読んでいこうと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1