こんにちは。もんしょー(@sima199407)です。
という疑問があるかと思います。
今回は「掲示板」を作ってみたいと思います。
ゼロの状態からでも作れるように解説していきます。
GitHubで確認もできます。
それではやっていきましょう。
動画でも解説してます
開発環境
PHP7系
Laravel7系
MySQL8.0系
nginx
開発で使う基本的な環境です。
laravelを導入した画面
まずは導入した画面からスタートです。
もし、「Laravelの導入してないよ!」という人はこちらも参考してみてください▼
マイグレーション(DB作成)
マイグレーションとは簡単に言うと、DBを管理することで、使う項目として、テーブル、カラムの作成、追加変更を行います。
データベースで直接カラム追加するとエラーになる可能性もあるので、ここでやっていきます。
テーブル、カラムの紹介
ここで紹介はしますが、後で見返してもOKです。
Users テーブル
カラム名 | データ型 | その他設定 |
id | int | auto_increment |
name | VARCHAR | |
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を作る上で使います。
これを使うことで独自の関数の読み込みができたりするので、覚えておきましょう。
【ポイント】共通部分はテンプレに
そういった毎回書いていること、使い回すようなパーツはテンプレートにするのが有効です。
なので、こういう風に考えます。
一つ一つファイルを作る
テンプレートのベースを作って、そこにパーツを入れる
なので変更していきましょう。
テンプレの基礎となるファイルを作成
先ほど、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>
スクリプトもかけちゃいます。