Ikeda's Blog

Laravelでブログサイトを作る 12. 管理機能・親カテゴリの新規登録と更新

はじめに

前回、親カテゴリの一覧表示の続きです。

Controller

app/Http/Controllers/ParentCategoryController.php
前回で一覧表示を実装したControllerに、更新処理を追加します。
FORMのaction属性に{{ route('admin_parent_update') }}としておいたので、submit後はルーティングで指定したArticleController@update、つまり、上記Controller内のupdateメソッド(今回追加するもの)が実行されます。

    /**
     * 親カテゴリ レコード総更新
     *
     * @param Request $request
     * @return mixed
     */
    public function update(Request $request) {
        $flg = true; // $flgがfalseになるまでループします
        for ($i = 1;$flg;$i++) {
            // 入力欄が無くなったらループ終了
            if (!isset($request['id_' . $i])) {
                $flg = false;
                break;
            }

            // 入力値を取得
            $id = $request['id_' . $i];
            $name = $request['name_' . $i];
            $number = $request['number_' . $i];
            $delete = $request['delete_' . $i];

            // カテゴリ名、表示順が空白になっているレコードは登録・変更しない
            if ($name === null || $number === null) {
                continue;
            }

            // idがあるなら上書き、なければ新規作成
            $parentCategoryData = $this->parentCategory->withTrashed()->where('id', $id)->first();
            if ($parentCategoryData === null) {
                $parentCategoryData = new ParentCategory();
            }
            $parentCategoryData->name = $name;
            $parentCategoryData->number = $number;
            $parentCategoryData->save();

            if ($delete === 'on') {
                // 削除チェックが入っていれば削除
                $parentCategoryData->delete();
            } else if ($parentCategoryData->trashed()) {
                // 削除済みレコードを復帰させる
                $parentCategoryData->restore();
            }
        }

        return redirect()->route('admin_parent');
    }

パラメータは「id_連番」といった規則で送られてくるので、これを1件ずつ確認します。
カテゴリの名前と表示順が空欄であれば、登録も更新もしません。空欄で登録されると他で使う時に面倒なのでこのようにしています。

削除処理をsave()の後に実行しているのは、例えば「とりあえずこのカテゴリ作るけど、記事のネタがある程度できてから有効化しよう」のようなケースだと、新規登録のクセして削除チェックしている、なんてことが起きるためです。

Laravelでブログサイトを作る 11. 管理機能・親カテゴリの一覧表示

はじめに

今回は、親カテゴリの管理機能のうち、一覧表示画面を作成します。

Controller

app/Http/Controllers/ParentCategoryController.php
Controllerでは、親カテゴリのレコードを全て取得し、テンプレートに渡します。

<?php

namespace App\Http\Controllers;

use App\Models\ParentCategory;
use Illuminate\Http\Request;

class ParentCategoryController extends Controller
{
    /**
     * @var ParentCategory
     */
    protected $parentCategory;

    /**
     * 新しいコントローラインスタンスの生成
     *
     * @param ParentCategory $parentCategory
     * @return void
     */
    function __construct(ParentCategory $parentCategory) {
        $this->parentCategory = $parentCategory;
    }

    /**
     * 一覧画面表示
     *
     * @return mixed
     */
    public function index() {
        // 親カテゴリデータの取得
        $parentCategoryData = $this->parentCategory
            ->withTrashed() // 管理画面では、削除済みデータも取得対象とする
            ->orderBy('id', 'asc') // 管理画面の場合、IDでソートする
            ->get()
        ;

        return view('admin.parentCategory', [
            'parentCategoryData' => $parentCategoryData,
        ]);
    }
}

テンプレート

resources/views/admin/parentCategory.blade.php

@extends('admin.app')

@section('adminTitle', '親カテゴリ管理')

@section('adminMain')
    <article>
        <section class="article">
            <h2>親カテゴリ管理</h2>

            <form action="{{ route('admin_parent_update') }}" method="post">
                <table>
                    <tr>
                        <th>カテゴリ名</th>
                        <th>表示順</th>
                        <th>削除</th>
                    </tr>

                    {{-- 入力枠追加ボタンを押した時、下記trを複製の上で調整して追加する --}}
                    <tr id="model">
                        <td>
                            <input type="hidden" id="id" value="" />
                            <input type="text" id="name" value="" />
                        </td>
                        <td><input type="number" id="number" value="" /></td>
                        <td>
                            <label>
                                <input type="checkbox" id="delete" value="on" />削除
                            </label>
                        </td>
                    </tr>

                    {{-- 既存の親カテゴリを表示 --}}
                    <?php $cnt = 1; ?>
                    @foreach ($parentCategoryData as $item)
                        <tr>
                            <td>
                                <input type="hidden" name="id_{{ $cnt }}" value="{{ $item->id }}" />
                                <input type="text" name="name_{{ $cnt }}" value="{{ $item->name }}" />
                            </td>
                            <td>
                                <input type="number" name="number_{{ $cnt }}"
                                    value="{{ $item->number }}" /></td>
                            <td>
                                <label>
                                    <input type="checkbox" name="delete_{{ $cnt }}" value="on"
                                        {{ ($item->deleted_at !== null) ? 'checked' : '' }} />削除
                                </label>
                            </td>
                        </tr>
                        <?php $cnt++; ?>
                    @endforeach

                    {{-- 新規枠をひとつ表示させておく --}}
                    <tr>
                        <td>
                            <input type="hidden" name="id_{{ $cnt }}" value="" />
                            <input type="text" name="name_{{ $cnt }}" value="" />
                        </td>
                        <td><input type="number" name="number_{{ $cnt }}" value="{{ $cnt }}" /></td>
                        <td>
                            <label>
                                <input type="checkbox" name="delete_{{ $cnt }}" value="on" />削除
                            </label>
                        </td>
                    </tr>
                </table>

                {{-- 入力枠追加ボタンを押した時に使用する、通し番号の現在最大値--}}
                <input type="hidden" id="max" value="{{ $cnt }}" />

                {{-- CSRFトークン --}}
                {{ csrf_field() }}

                <input type="button" value="+ 入力枠追加" id="addInput" />
                <input type="submit" value="更新" />
            </form>
        </section>
    </article>
@endsection

JavaScript

public/js/admin.js
新規枠追加ボタンを押した時の動作を追加します。
テンプレートで用意しておいたid="model"のtrを複製し、調整した上でtableの最下列に追加しています。

$(document)
    // 親カテゴリの入力枠追加
    .on('click', '#addInput', function(){
        // 次の番号を算出
        const max = $('#max');
        const nextNum = parseInt(max.val()) + 1;
        max.val(nextNum);

        // id=modelになっているtr要素を、idを削除した上でcloneする
        const clone = $('#model').clone(true).removeAttr('id');

        // cloneしたtr内の各name属性を変更し、識別用のidを削除しておく
        $('#name', clone).attr('name', 'name_' + nextNum).removeAttr('id');
        $('#number', clone).attr('name', 'number_' + nextNum).val(nextNum).removeAttr('id');
        $('#id', clone).attr('name', 'id_' + nextNum).removeAttr('id');
        $('#delete', clone).attr('name', 'delete_' + nextNum).removeAttr('id');

        // テーブルに追加
        clone.appendTo('table');
    })
;

Laravelでブログサイトを作る 10. テンプレートの作成

はじめに

今回は、サイトのデザインを作成します。
プログラム作ってからだと、「あれ足りない」「これ足りない」という事態が起きやすいので、先に、各URLへアクセスした際に、ただページを表示させるだけのものを作ります。

LaravelはBladeというテンプレートを採用しており、これを使用して構築していきます。
Bladeに限りませんが、継承機能がありますので、これを利用して構築していきます。

おおまかな構成は、以下のようにします。

+ resources/views
  + app.blade.php -> 全体の共通部分
  + front
  | + app.blade.php -> 公開ページの共通部分
  | + article.blade.php -> 記事1件のページ
  | + list.blade.php -> 記事一覧のページ(親子カテゴリ絞り込み等でも同じテンプレートを使う)
  + admin
    + app.blade.php -> 管理ページの共通部分
    + articleEdit.blade.php -> 記事の編集ページ
    + articleList.blade.php -> 記事の一覧ページ
    + parent.blade.php -> 親カテゴリの一覧・編集ページ
    + child.blade.php -> 子カテゴリの一覧・編集ページ

Controller

TOPページのルーティングは、以下のようになっている。

Route::group(['prefix' => '/', 'namespace' => 'App\\Http\\Controllers\\'], function() {
    Route::get('/', 'ArticleController@list')->name('top');
});

なので、TOPページにアクセスした時には、app/Http/Controllers/ArticleController.phpのlistメソッドが実行されます。
ArticleController.phpの内容を、以下のようにします。テンプレートを読み込んで表示するだけです。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleController extends Controller
{
    /**
     * 一覧画面表示
     */
    public function list()
    {
        // view()で引数としている文字列は、resources/views以下のテンプレートファイルを表しています。
        // 「.」(ドット)でディレクトリが表現されるので、上記の場合は、
        // `resources/views/front/articleList.blade.php`
        // を表示します。
        return view('front.articleList');
    }
}

テンプレート・全体の共通部分

サイト全体の共通部分を作成します。
対象は、resources/views/app.blade.phpです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    {{--
    ***@yield*** は、子孫テンプレートで設定した値が入ります。
    例えば、タイトルタグ内に`@yield('title')`と記述することで、
    子孫テンプレートでそれぞれに設定して出し分けることが可能となります。
    --}}
    <title>サイトタイトル | @yield('title')</title>

    {{--
    ***asset()*** は、https(またはhttp)からはじまる、public以下のURLを生成してくれます。
    この方法で記述を統一することで、「画像やJS、CSSファイルをAWS S3に置く」などの
    変更が起きた際、`env`を書き換えるだけで済むので、大変便利です。
    --}}
    <link rel="stylesheet" href="{{ asset('/css/style.css') }}">

    <script type="text/javascript" src="{{ asset('/js/jquery.min.js') }}" charset="UTF-8"></script>
    <script type="text/javascript" src="{{ asset('/js/main.js') }}" charset="UTF-8"></script>
    <script src="https://kit.fontawesome.com/207c617789.js" crossorigin="anonymous"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
</head>

<body>

<header>
    <h1><a href="/"><img src="{{ asset('/img/header.png') }}" alt="サイトタイトル" /></a></h1>
</header>

<main>
    @yield('paging')

    @yield('main')

    <aside>
        @yield('aside')
    </aside>

    @yield('paging')
</main>

<footer>
    フッターテキスト
</footer>

</body>
</html>

テンプレート・公開ページの共通部分

先程のサイト全体(親)に対し、公開ページの共通部分(子)を作成します。
ファイルは、resources/views/front/app.blade.phpです。

{{--
***@extends*** は、継承元となるテンプレートを指定します。
ここでは、全体の共通部分`resources/views/app.blade.php`が対象です。
--}}
@extends('app')

{{--
***@section*** は、上位テンプレートで`@yield`を使用した箇所に挿入する内容を記述します。
@sectionから@endsectionまでの内容が対象となります。
以下の例では、`@yield('aside')`に出力されるサイドメニューの内容を記述しています。
--}}
@section('aside')
    <section class="side">
        <h3>検索</h3>
        <div id="searchWrapper">
            <form action="{{ route('list_search') }}" method="post" name="searchForm">
                {{ csrf_field() }}
                <input type="text" id="searchTxt" name="search" placeholder="サイト内検索" />
                <a href="#" id="searchBtn"><i class="fas fa-search"></i></a>
            </form>
        </div>
    </section>
@endsection

テンプレート・個別ページ

最後に、個別ページ(孫)を作成します。
ファイルは、resources/views/front/articleList.blade.phpです。

{{--
継承元のテンプレートファイルを指定します。
ルールは、Controller同様に「.」(ドット)でディレクトリの区切りです。
以下の場合は、`resources/views/front/app.blade.php`を表しています。
--}}
@extends('front.app')

{{--
@sectionは、一行で記述することも可能です。
以下のように、
--}}
@section('title', '記事一覧')

@section('paging')
    <nav>
        <ul class="pagination">
            <li><a href="/" rel="prev" aria-label="« Previous">&lt;&lt;</a></li>
            <li><a href="/">1</a></li>
            <li><a href="/">2</a></li>
            <li><a href="/">3</a></li>
            <li class="active" aria-current="page"><span>4</span></li>
            <li class="disabled" aria-disabled="true" aria-label="Next »">
                <span aria-hidden="true">&gt;&gt;</span>
            </li>
        </ul>
    </nav>
@endsection

@section('main')
    <article>
        <section class="article">
            <h2><a href="#">記事タイトル</a></h2>

            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>

            <a href="#" class="more">続きを読む</a>
        </section>
        <section class="article">
            <h2><a href="#">記事タイトル</a></h2>

            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>

            <a href="#" class="more">続きを読む</a>
        </section>
        <section class="article">
            <h2><a href="#">記事タイトル</a></h2>

            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>

            <a href="#" class="more">続きを読む</a>
        </section>
        <section class="article">
            <h2><a href="#">記事タイトル</a></h2>

            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>

            <a href="#" class="more">続きを読む</a>
        </section>
        <section class="article">
            <h2><a href="#">記事タイトル</a></h2>

            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>
            <p>これはテストです。</p>

            <a href="#" class="more">続きを読む</a>
        </section>
    </article>
@endsection

以上で、テンプレートの表示は完了です。
あとは、public/css/style.cssを編集してデザインを整えておきましょう。
http://192.168.56.10/で確認できます。

テンプレート・管理画面の共通部分

なお、管理画面の共通テンプレートは以下のようにしました。
違いとしては、管理画面用のCSSとJavaScriptを読み込むようにしています。
あとは、サイドメニューに各管理機能のリンクを設置しております。

@extends('app')

@section('title')
    @yield('adminTitle')
@endsection

@section('main')
    {{-- 管理画面専用のCSSとJavaScript --}}
    <link rel="stylesheet" href="{{ asset('/css/admin.css?v=').'?'.time() }}">
    <script type="text/javascript" src="{{ asset('/js/admin.js?v=').'?'.time() }}" charset="UTF-8"></script>
    @yield('adminMain')
@endsection

@section('aside')
    <section class="aside">
        <h3>メニュー</h3>
        <ul>
            <li><a href="{{ route('admin_parent') }}">親カテゴリ管理</a></li>
            <li><a href="{{ route('admin_child') }}">子カテゴリ管理</a></li>
            <li><a href="{{ route('admin_article_list') }}">記事一覧</a></li>
            <li><a href="{{ route('admin_article_edit') }}">記事新規作成</a></li>
        </ul>
    </section>
@endsection

Laravelでブログサイトを作る 09. ルーティング

はじめに

ルーティングは、どのURLでアクセスされた時に、どのコントローラの、どのメソッドを呼ぶかを指定します。
URL設計で挙げたURLを、routes/web.phpに記述していくことで設定します。

不要なものを削除(コメントアウト)

routes/web.phpには、動作確認の際に見たwelcomページと、認証機能を導入した際に追加されたdashboardページについての記述が存在しているはずです。
これらは、今後使用する予定が無いので、コメントアウトしてしまいます。

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

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');
*/

管理ページ

管理側はログインしていることがアクセス条件となります。
※認証機能についてはコチラで解説しています。
'middleware' => 'auth'の記述を加えることで、簡単に実現できます。

また、Route::groupで、admin以下のアクセスについての設定をまとめることができます。
管理側のページ1件ずつに上記の記述を書くのは面倒なので、こうしてまとめれば簡単で、かつ「このグループは管理用」というのが分かりやすいです。

// 管理画面(認証後に触れるページ)
Route::group(
    [
        'prefix' => 'admin',
        'middleware' => 'auth',
        'namespace' => 'App\\Http\\Controllers\\'
    ],
    function() {
        // 親カテゴリ管理:一覧(入力欄込)表示
        Route::get('parent', 'ParentCategoryController@list')->name('admin_parent');
        // 親カテゴリ管理:更新処理
        Route::post('parentUpdate', 'ParentCategoryController@update')->name('admin_parent_update');

        // 子カテゴリ管理:一覧(入力欄込)表示
        Route::get('child', 'ChildCategoryController@list')->name('admin_child');
        // 子カテゴリ管理:更新処理
        Route::post('childUpdate', 'ChildCategoryController@update')->name('admin_child_update');

        // 記事:入力フォーム
        Route::get('articleEdit/{id?}', 'ArticleController@edit')->name('admin_article_edit');
        // 記事:更新処理
        Route::post('articleUpdate', 'ArticleController@update')->name('admin_article_update');
        // 画像アップロード
        Route::post('articleImageUpload', 'ArticleController@image')->name('admin_article_image_upload');
        // 記事一覧
        Route::get('articleList', 'ArticleController@adminList')->name('admin_article_list');
        // 記事一覧(親カテゴリ絞り)
        Route::get('articleList/parent/{parent}', 'ArticleController@adminParentList')->name('admin_article_list_parent');
        // 記事一覧(子カテゴリ絞り)
        Route::get('articleList/child/{child}', 'ArticleController@adminChildList')->name('admin_article_list_child');
        // 記事一覧(検索)
        Route::post('articleList/search/', 'ArticleController@adminSearchList')->name('admin_article_list_search');
        // 記事プレビュー
        Route::post('articlePreview', 'ArticleController@preview')->name('admin_article_preview');

        // 「/admin/」にアクセスされたら、記事一覧へ
        Route::get('/', 'ArticleController@adminList')->name('admin_top');
});

小ネタ

prefixの値を変更することで、管理画面のURLを外部から推測されづらいようにします。

たとえは、'prefix' => 'qawsedrftgyhujikolp',としておくと、管理画面のURLは「https://ドメイン/qawsedrftgyhujikolp/」となるので、URLを直接指定されるようなアクセスを防ぐことができます。

フロントページ

それぞれに->name('XXXXX')を付けることで、後から利用しやすくします。
詳しくはbladeテンプレートを作成する際に記載しますが、href="{{ route('XXXXX') }"のように記述することで、URLを生成してくれるのです。
例えば、「親カテゴリ一覧のURLを/p/{id}にしよう」となった時、routes/web.phpを修正し、nameの方はそのままにしておけば、出現箇所すべてを修正する必要はなくなるのです。

// フロントページ
Route::group(['prefix' => '/', 'namespace' => 'App\\Http\\Controllers\\'], function() {
    // 一覧(http://ドメイン/)
    Route::get('/', 'ArticleController@list')->name('top');

    // 記事1件表示(http://ドメイン/content/XX)
    Route::get('content/{id}', 'ArticleController@content')->name('content');

    // 親カテゴリ別記事一覧(http://ドメイン/parent/XX/)
    Route::get('parent/{id}', 'ArticleController@parentL')->name('list_parent');

    // 子カテゴリ別記事一覧(http://ドメイン/child/XX/)
    Route::get('child/{id}', 'ArticleController@childList')->name('list_child');

    // 検索結果一覧(http://ドメイン/search/)
    Route::post('search/', 'ArticleController@searchList')->name('list_search');
});

Laravelでブログサイトを作る 08. テーブル生成

設計について

設計はデータベース定義にて行っております。
これを、migrationに落とし込み、テーブルを作成します。
Laravelのmigrationについてはコチラを参考に。

親カテゴリmigrationファイル

createするテーブルのカラムについては、upメソッド内で設定します。
主キーを、デフォルトの$table->id();ではなく、increments('id')としています。
これは、$table->id()を使ってしまうと、BIGINT型になってしまうからです。
BIGINTの方が大量の上限値が上ですが、符号ありで考えても
INT型:約21億
BIGINT型:約922京(1000兆の922倍)
なので、個人運営サイトでわざわざBIGINTにするほどではないかなぁ、という考えです。
(とはいえ、ギリギリを詰めなければならない状況でもないので、MEDIUMINT型にもしていません)

対象ファイル:database/migrations/XXXX_XX_XX_XXXXXX_create_parent_categories_table.php

// ...省略...
    public function up()
    {
        Schema::create('parents', function (Blueprint $table) {
            $table->increments('id'); // 主キー(自動採番のINT型)
            $table->text('name'); // カテゴリ名
            $table->integer('number')->index(); // 表示順
            $table->softDeletes(); // ソフトデリートのためにNULL値可能なdeleted_at TIMESTAMPカラム追加
            $table->timestamps(); // NULL値可能なcreated_atとupdated_atカラム追加
        });
    }
// ...省略...

子カテゴリmigrationファイル

対象ファイル:database/migrations/XXXX_XX_XX_XXXXXX_create_child_categories_table.php

// ...省略...
    public function up()
    {
        Schema::create('parents', function (Blueprint $table) {
            $table->increments('id'); // 主キー(自動採番のINT型)
            $table->text('name'); // 子カテゴリ名
            $table->integer('number')->index(); // 表示順
            $table->integer('parent'); // 親カテゴリID
            $table->softDeletes(); // ソフトデリートのためにNULL値可能なdeleted_at TIMESTAMPカラム追加
            $table->timestamps(); // NULL値可能なcreated_atとupdated_atカラム追加
        });
    }
// ...省略...

記事migrationファイル

対象ファイル:database/migrations/XXXX_XX_XX_XXXXXX_create_articles_table.php

// ...省略...

public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->increments('id'); // 主キー(自動採番のINT型)
            $table->text('title'); // 記事タイトル
            $table->text('body'); // 本文
            $table->integer('parent')->index(); // 親カテゴリID
            $table->softDeletes(); // ソフトデリートのためにNULL値可能なdeleted_at TIMESTAMPカラム追加
            $table->timestamps(); // NULL値可能なcreated_atとupdated_atカラム追加

        });
    }

// ...省略...

記事-子カテゴリmigrationファイル

対象ファイル:database/migrations/XXXX_XX_XX_XXXXXX_create_article_children_table.php

// ...省略...
    public function up()
    {
        Schema::create('parents', function (Blueprint $table) {
            $table->increments('id'); // 主キー(自動採番のINT型)
            $table->integer('article')->index(); // 符記事ID
            $table->integer('children')->index(); // 子カテゴリID
            $table->softDeletes(); // ソフトデリートのためにNULL値可能なdeleted_at TIMESTAMPカラム追加
            $table->timestamps(); // NULL値可能なcreated_atとupdated_atカラム追加
        });
    }
// ...省略...

テーブル作成

サーバに接続し、以下のコマンドを実行します。

$ php artisan migrate

先述の4ファイルそれぞれで、MigratingMigratedが表示され、テーブルが作成されます。
MySQLでテーブル一覧を表示すると、作成されていることが確認できます。

$ mysql -u root -p
Enter password: [コメント]設定したパスワードを入力(過去記事では`NEW_pass_123`を設定)

mysql> use blog
mysql> show tables;
+------------------------+
| Tables_in_blog         |
+------------------------+
| article_children       |
| articles               |
| children               |
| failed_jobs            |
| migrations             |
| parents                |
| password_resets        |
| personal_access_tokens |
| sessions               |
| team_invitations       |
| team_user              |
| teams                  |
| users                  |
+------------------------+
13 rows in set (0.00 sec)

Modelの編集

app/Models以下にあるファイルを、テーブルの構成に合わせて編集します。
記事(Article.php)をサンプルにします。

  1. 論理削除のため、SoftDeletesを追加
  2. 主キーのカラム名を追加
  3. INSERTやUPDATE時に、入力値を保存するカラムを指定
  4. 日時関係のカラムを指定
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; // 【追加】1.論理削除用

class Article extends Model
{
    use HasFactory;
    use SoftDeletes; // 【追加】1.論理削除用

    // 【追加】2.主キー
    protected $primaryKey = 'id';

    // 【追加】3.入力値用カラム指定
    protected $fillable = ['title', 'body', 'parent'];

    // 【追加】2.日時系カラム
    protected $dates = ['created_at', 'updated_at', 'deleted_at'];
}