PROGRAMMING

【シンプル解説】Laravelで掲示板を作る【設計,コーディング,完成まで】

こんにちは。もんしょー(@sima199407)です。

ある人
laravelでプロダクト作ってみたいなー

という疑問があるかと思います。

今回は「掲示板」を作ってみたいと思います。

ゼロの状態からでも作れるように解説していきます。

DEMOはこちら。

ID:test@test.net

PS:test1234

GitHubで確認もできます。

それではやっていきましょう。

動画でも解説してます

開発環境

PHP7系
Laravel7系
MySQL8.0系
nginx

開発で使う基本的な環境です。

laravelを導入した画面

まずは導入した画面からスタートです。

もし、「Laravelの導入してないよ!」という人はこちらも参考してみてください▼

マイグレーション(DB作成)

マイグレーションとは簡単に言うと、DBを管理することで、使う項目として、テーブル、カラムの作成、追加変更を行います。
データベースで直接カラム追加するとエラーになる可能性もあるので、ここでやっていきます。

テーブル、カラムの紹介

ここで紹介はしますが、後で見返してもOKです。

Users テーブル

カラム名 データ型 その他設定
id int auto_increment
name VARCHAR
email VARCHAR
password VARCHAR
created_at TIMESTAMP
updated_at TIMESTAMP

ユーザー情報を入れておきます。

Posts テーブル

カラム名 データ型 その他設定
id int auto_increment
user_id int
body VARCHAR
created_at TIMESTAMP
updated_at TIMESTAMP

こちらが投稿したデータを入れるテーブルですね。

非常にシンプルですがこちらでOKです。

 

マイグレーションファイル作成

コマンドラインを使います。

laravelが入っている一番上のディレクトリにいることを確認します。(app,artisan,publicがあるとこ)

posts tableを作成

php artisan make:migration create_posts_table --create=posts

create_posts_tableという部分は任意で変更ができます。どんなファイルかわかりやすくしておきましょう。

— create=postsをつけることで、新規のテーブル名を設定します。

ちなみにテーブル名は複数形にしておくのが基本です。

create_posts_tableの編集

作成したcreate_posts_tableファイルを編集しましょう。

ファイルは以下の場所にあります。

database/migrations/に以下のようなファイルがあります。

変更前
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Schema::create('posts',~
と書いてある'posts'のところがテーブル名です。

$table->bigIncrements('id');
というのが、->[データ型]('カラム名');
という形になります。

変更後
class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
						$table->increments('id');
						$table->integer('user_id');
						$table->string('body');
						$table->timestamp('created_at')->default(\DB::raw('CURRENT_TIMESTAMP'))->nullable();
						$table->timestamp('updated_at')->default(\DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'))->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

変更したのは、$table->~という部分だけです。

$table->bigIncrements('id');を
$table->increments('id');にしました。int型のほうが扱いやすいという理由です。

$table->timestamps();を

$table->timestamp('created_at')->default(\DB::raw('CURRENT_TIMESTAMP'))->nullable(); 
$table->timestamp('updated_at')->default(\DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'))->nullable();

というちょっと長ったらしい感じ変えました。
なんでそんなことをしたかというと、デフォルトのtimestampsだとDB上でNullになってしまうという事象があるためにこのように書き換えてます。

環境変数を変える

.envファイルをDB接続用に変更します。

DB_CONNECTION=mysql
DB_HOST=[環境に合わせて変更。ローカルならlocalhost 。 dockerならコンテナ名に。]
DB_PORT=3306
DB_DATABASE=[Database Name]
DB_USERNAME=[YOUT NAME]
DB_PASSWORD=[YOUR PASS]

ポイントは、DB_HOSTでもし接続ができないときはここを疑ってみてください。

マイグレーションコマンドを打つ(DBへの反映)

以下のようにコマンドを入力

ターミナル

php artisan migrate

そして、

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.07 seconds)
Migrating: 2020_07_24_073106_create_posts_table
Migrated:  2020_07_24_073106_create_posts_table (0.04 seconds)

というふうにエラーなく出てくればOKです。

コントローラー、モデルを作成

コントローラーとモデルを作成します。

それが以下の通り。

php artisan make:controller UserController --model=User

これでUserコントローラーとUserモデルを同時に作成します。 Postも同じように作ります。

php artisan make:controller PostController --model=Post

もしコントローラーとモデルを別々に作りたい時は以下の通り。
【モデル】

php artisan make:model User

【コントローラー】

php artisan make:controller UserController

後で編集します。

認証(Auth)とフレームワーク

これを作ることで、ユーザーの「新規作成」「ログイン」などを作ってくれます。

以下のコマンドを使います。

composer require laravel/ui
php artisan ui vue --auth

5系とはちょっと違うので注意です。

これをすると、view/layoutsというディレクトリが作成されて、その中にapp.blade.phpというファイルが作成されます。

これはあとで使いますね。

Bootstrap導入

7系はBootstrapが外部ライブラリになったので、自分で入れます。

php artisan ui bootstrap

という感じでコマンドを打つと、npmでビルドするように指示がでますので、以下のコマンドを打ちます。

npm install && npm run dev

ルーティングの作成

ルーティングとは、簡単にいうとURLの

https://www.facebook.com/login/

loginの部分です。

これでどの内容を読み込むのかというのを判別しているわけなんですが、これを設定します。

使用するルーティング一覧

[GET](/posts) ->index
[GET](/posts/{id}) ->show($id)
[GET](/posts/create) ->create
[POST](/posts) ->store
[GET](/posts/{id}) ->edit($id)
[PUT/PATCH](/posts/{id})->update($id)
[DELETE](/posts/{id}) ->destroy($id)

いっぱいあるんです。笑

基本的な書き方

ファイルは、routes/web.phpでデフォルトはwelcomeしか設定されてないかなと思います。

以下のように変更します。

Route::get('/posts', 'PostController@index');
Route::get('/posts/{id}', 'PostController@show');
Route::get('/posts/create','PostController@create');
Route::post('/posts', 'PostController@store');
Route::get('/posts/{id}', 'PostController@edit');
Route::put('/posts/{id}', 'PostController@update');
Route::delete('/posts/{id}','PostController@destroy');

という感じです。

解説すると、('/posts', 'PostController@index');'/posts'URLを叩いた時の部分でして、

'PostController@index'PostController使用するコントローラー名、indexメソッド名です。

一つまとめるルーティングの書き方

これでもいいんですが、もっとまとめる方法もあります。

それが、Route::Resourceというものなんですが、これを使うと、今回使用する全てのルーティングを網羅できます。

Route::resource('posts', 'PostController');

という形でOKです。

Resourceは基本的なルーティング機能として用意されているんですが、全て使わない場合でも以下のように書くと使いたいものだけ使えます。

Route::resource('posts', 'PostController', ['only' => ['index', 'create', 'edit', 'store']]);

これだと、only内に入っているものだけを使うという形になります。

完成ルートファイル(route.php)

Route::get('/', function () {
    return view('welcome');
});
Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Route::resource('posts', 'PostController', ['only' => ['index','show', 'create', 'store']]);
Route::get('posts/edit/{id}', 'PostController@edit');
Route::post('posts/edit', 'PostController@update');
Route::post('posts/delete/{id}', 'PostController@destroy');

 

こんな感じです。

コントローラーの編集

Viewとモデルをつなぐ役目のコントローラーですが、今回はほぼメインパーツです。

コントローラーファイル

編集するのは以下の通り。

PostController.php

基本的な動きはここだけでできます。

PostController.php

<?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;


class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $posts = Post::all();
        return view('posts.index', ['posts' => $posts]);

    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        
        return view('posts.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $id = Auth::id();
        //インスタンス作成
        $post = new Post();
        
        $post->body = $request->body;
        $post->user_id = $id;

        $post->save();

       return redirect()->to('/posts');
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function show(Post $post)
    {
        $usr_id = $post->user_id;
        $user = DB::table('users')->where('id', $usr_id)->first();
        

        return view('posts.detail',['post' => $post,'user' => $user]);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        // $usr_id = $post->user_id;
        $post = \App\Post::findOrFail($id);

        return view('posts.edit',['post' => $post]);
        // return view('posts.edit');
    }


    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request)
    {
        $id = $request->post_id;
        
        //レコードを検索
        $post = Post::findOrFail($id);
        
        $post->body = $request->body;
        
        //保存(更新)
        $post->save();
        
        return redirect()->to('/posts');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $post = \App\Post::find($id);
        //削除
        $post->delete();

        return redirect()->to('/posts');
    }
}

 

モデルの編集

今回そこまで使いませんが、本来であればDBの接続、関数の指定などに使います。

モデルファイル

今回編集するのは以下の通り。

Post.php

User.phpはデフォルトのままにします。

Post.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';

    
}

protected $table = 'posts';だけ加えました。

これでテーブルを指定したので、コントローラーからインスタンスを呼び出すと、postsテーブルを選択できます。

Viewの編集

特にコマンドではなく、新規ファイルを作っていきましょう。

場所は、/resources/viewsになります。

welcome.blade.phpが入っているのが目印です。

作成ファイル一覧

・index.blade.php
・detail.blade.php
・create.blade.php
・edit.blade.php

ちなみにbladeと言うのがlaravelのviewを作る上で使います。

これを使うことで独自の関数の読み込みができたりするので、覚えておきましょう。

【ポイント】共通部分はテンプレに

ある人
headerやfooterなどは基本的に同じものをつかうはどうすればいいですか?

そういった毎回書いていること、使い回すようなパーツはテンプレートにするのが有効です。

なので、こういう風に考えます。

一つ一つファイルを作る

テンプレートのベースを作って、そこにパーツを入れる

なので変更していきましょう。

テンプレの基礎となるファイルを作成

先ほど、Authで作成した、

/resources/views/layoutsの中にapp.blade.phpというファイルがあります。

それを編集していきましょう。

app.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>App掲示板 - @yield('title')</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    <script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
        crossorigin="anonymous"></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>

<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/posts') }}">
                    App掲示板
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">
                        @auth
                        <a href="/posts/create" class="btn btn-outline-primary">新規投稿</a>
                        @endauth
                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('login') }}">{{ __('ログイン') }}</a>
                            </li>
                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('新規登録') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('home') }}">
                                        {{ __('Home') }}
                                    </a>
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
    @include('layouts.app_script')
</body>
</html>

という感じです。

@yield('content')とかいてある、yield(イェルド)というのがテンプレを表示するもので、カッコ内にどこのパーツを持っていくか書きます。

Viewファイル

テンプレができたので、中身の部分を作成します。

ディレクトリは、分かりやすくするために/resources/views/postsします。

index.blade.php

一番最初に表示する部分ですね。

@extends('layouts.app')
@section('title', 'TOP page')

@section('content')
<div class="container">
    <div class="row">
        <!-- メイン -->
        <div class="col-10 col-md-8 offset-1 offset-md-2">
            <table class="table">
                <tbody>
                    <tr>
                        <th>ID</th>
                        <th colspan="3">内容</th>
                    </tr>
                    
                    @foreach ($posts as $post)
                    <tr>
                        <td>{{ $post->id }}</td>
                        <td>{{ $post->body }}</td>
                        
                        <td>
                            <a href="{{ url('posts/'.$post->id) }}" class="btn btn-success">詳細</a>
                        @auth
                            <form action="/posts/delete/{{$post->id}}" method="POST">
                                {{ csrf_field() }}
                                <input type="submit" value="削除" class="btn btn-danger post_del_btn">
                            </form>
                        @endauth
                        </td>
                    </tr>
                    @endforeach
                </tbody>
            </table> 
        </div>
    </div>
</div>
@endsection

ポイントは@extends('layouts.app')をつけることで、テンプレにしたapp.blade.phpと紐づきます。継承するって言い方もしますね。

detail.blade.php

編集

@extends('layouts.app')
@section('title', 'detail page')

@section('content')
<div class="container">
    <div class="row">
        <!-- メイン -->
        <div class="col-10 col-md-6 offset-1 offset-md-3">
            <div class="card">
                <div class="card-header">
                   {{ $post->id }}
                </div>
                <div class="card-body">
                    <p class="card-text">{{ $post->body }}</p>
                    <div class="card-footer bg-transparent"><span class="font-weight-bold">by:</span> {{ $user->name }}</div>
                    @auth
                        <a href="{{ url('posts/edit/'.$post->id) }}" class="btn btn-dark">編集する</a>
                    @endauth
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

 

create.blade.php

新規作成

@extends('layouts.app')
@section('title', 'create page')

@section('content')
    <div class="row">
        <!-- メイン -->
        <div class="col-10 col-md-6 offset-1 offset-md-3">
            <form action="/posts" method="post">
                {{ csrf_field() }}
                <div class="form-group">
                    <label for="exampleFormControlTextarea1">新規投稿</label>
                    <textarea class="form-control" name="body" id="exampleFormControlTextarea1" rows="3"></textarea>
                    <div class="text-center mt-3">
                        <input class="btn btn-primary" type="submit" value="投稿する">
                        <input type="hidden" name="_token" value="{{csrf_token()}}">
                    </div>
                </div>
            </form>
        </div>
    </div>
@endsection

edit.blade.php

投稿編集

@extends('layouts.app')

@section('title', 'edit page')
@section('content')
    <div class="row">
        <!-- メイン -->
        <div class="col-10 col-md-6 offset-1 offset-md-3">
            <div class="card">
                <form action="/posts/edit" method="post">
                {{ csrf_field() }}
                <div class="card-body">
                    <p class="card-text">
                    <textarea class="form-control" name="body" id="exampleFormControlTextarea1" rows="3">{{$post->body}}</textarea>
                    </p>
                    <div class="text-center mt-3">
                        <input name="post_id" type="hidden" value="{{$id}}" >
                        <input class="btn btn-primary" type="submit" value="変更する">
                        <input type="hidden" name="_token" value="{{csrf_token()}}">
                    </div>
                </div>
                </form>
            </div>
        </div>
    </div>
@endsection

app_script.blade.php

削除ボタン用です。

<script>
  $(function(){
  $(".post_del_btn").click(function(){
  if(confirm("削除しますか?")){
  //そのままsubmit(削除)
  }else{
  //cancel
  return false;
  }
  });
  });
</script>

スクリプトもかけちゃいます。

-PROGRAMMING
-,