3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ClosureTableを使って〇〇年のアニメを季節ごとに1テーブルで管理する

Posted at

やること

深い階層構造を持つデータを簡単に扱えちゃう `Closure Table`についての簡単な紹介と実装。

環境

- Laravel5.8 - Docker - MySQL

扱うデータたち

豊作で有名な2011年のアニメを季節ごとに分類するとこんな感じ。そしてこれを1テーブルで管理してみる。 (筆者が好きだったアニメを1つの季節に3つ選びました〜〜)
2011年アニメ
├ 冬
|   ├ 魔法少女まどかマギカ
|   ├ これはゾンビですか?
|   └ GOSICK   
├ 春
|   ├ STEINS;GATE
|   ├ あの日見た花の名前を僕達はまだ知らない
|   └ 花咲くいろは
├ 夏
|   ├ ゆるゆり
|   ├ 神様のメモ帳
|   └ ロウきゅーぶ!
└ 秋
    ├ Fate/Zero
    ├ 未来日記
    └ 僕は友達が少ない

ClosureTableの作成

公式のGithubに色々載ってます。 https://github.com/franzose/ClosureTable

まず下記コマンドでインストール。

$ composer require franzose/closure-table

インストールできたらテーブルを作ります。

$ php artisan closuretable:make --entity=animation

うまくいくと、migrationファイルと、Animation.php AnimationInterface.php AnimationClosure.php AnimationClosureInterface.php というモデル&インターフェースたちができています。

migrationファイルを見て見ます。

create_animation_table.php

<?php

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

class CreateAnimationsTableMigration extends Migration
{
    public function up()
    {
        Schema::create('animations', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('parent_id')->unsigned()->nullable();
            $table->integer('position', false, true);
            $table->integer('real_depth', false, true);
            $table->softDeletes();

            $table->foreign('parent_id')
                ->references('id')
                ->on('animations')
                ->onDelete('set null');
        });

        Schema::create('animation_closure', function (Blueprint $table) {
            $table->increments('closure_id');

            $table->integer('ancestor', false, true);
            $table->integer('descendant', false, true);
            $table->integer('depth', false, true);

            $table->foreign('ancestor')
                ->references('id')
                ->on('animations')
                ->onDelete('cascade');

            $table->foreign('descendant')
                ->references('id')
                ->on('animations')
                ->onDelete('cascade');
        });
    }

    public function down()
    {
        Schema::table('animation_closure', function (Blueprint $table) {
            Schema::dropIfExists('animation_closure');
        });

        Schema::table('animations', function (Blueprint $table) {
            Schema::dropIfExists('animations');
        });
    }
}

見ての通り、animationanimation_closureという2つのテーブルを作るファイルになっています。最初に1テーブルで管理と宣言したのですが実はこのanimation_closureというテーブルも自動で作られますし、このテーブルによって子孫をメソッドで取得できる仕組みになっています。

カラムを追加しておきます。

create_animation_table.php
<?php

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

class CreateAnimationsTableMigration extends Migration
{
    public function up()
    {
        Schema::create('animations', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');    //追加
            $table->integer('parent_id')->unsigned()->nullable();
            $table->integer('position', false, true);
            $table->integer('real_depth', false, true);
            $table->softDeletes();

            $table->foreign('parent_id')
                ->references('id')
                ->on('animations')
                ->onDelete('set null');
        });

        Schema::create('animation_closure', function (Blueprint $table) {
            $table->increments('closure_id');

            $table->integer('ancestor', false, true);
            $table->integer('descendant', false, true);
            $table->integer('depth', false, true);

            $table->foreign('ancestor')
                ->references('id')
                ->on('animations')
                ->onDelete('cascade');

            $table->foreign('descendant')
                ->references('id')
                ->on('animations')
                ->onDelete('cascade');
        });
    }

    public function down()
    {
        Schema::table('animation_closure', function (Blueprint $table) {
            Schema::dropIfExists('animation_closure');
        });

        Schema::table('animations', function (Blueprint $table) {
            Schema::dropIfExists('animations');
        });
    }
}

そしてmigrationします。

$ php artisan migrate

テーブルにデータを入れる

シードしてもいいのですがせっかくなのでメソッド紹介も含めて全部手動で入れる事にしました! Repositoryパターン使います〜という事でRepositoryとControllerとview作成。 (Interfaceの記述は省略してます)
EloquentAnimationRepository.php
<?php
namespace App\Repositories\Repository;

use App\Animation;
use App\Repositories\Contract\AnimationContract;

class EloquentAnimationRepository implements AnimationContract
{
    public function addName($name)
    {
        $animation = new Animation;
        $animation->name = $name;
        return $animation;
    }

    public function getSeason()
    {
        return Animation::select('name')->where('parent_id', null)->get();
    }

    public function addAnimation($season, $animation)
    {
        $parent = Animation::where('name', $season)->get();
        $animation = $this->addName($animation);
        $parent[0]->addChild($animation);
    }

}

AnimationController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\Contract\AnimationContract;
use App\Animation;

class AnimationController extends Controller
{
    /**
     * @var AnimationContract
     */
    private $animation;

    public function __construct(AnimationContract $animation)
    {
        $this->animation = $animation;
    }

    public function season()
    {
        return view('animation.season');

    }

    public function addSeason(Request $request)
    {
        $this->animation->addName($request->season)->save();

        return redirect()->back();
    }

    public function animation()
    {
        $seasons = [];
        foreach($this->animation->getSeason() as $season){
           
            $seasons[$season->name] = $season->name;
        }

        return view('animation.animation')->with('seasons', $seasons);
    }

//アニメ追加
    public function addAnimation(Request $request)
    {
        $this->animation->addAnimation($request->season, $request->animation);

        return redirect()->back();
    }
}

add.blade.php
<html>
    <head>
        <title>季節追加</title>
    </head>
    <body>
        季節を追加
        {{ Form::open() }}
        {{ Form::input('text','season') }}
        {{ Form::submit('追加') }}
        {{ Form::close() }}
    </body>
</html>

ルート定義して、view開いて、冬・春・夏・秋を入力して送信するとデータベースはこんな感じになってるはず。
image.png

それぞれに子要素を追加していく

季節を選択してアニメを追加するviewを作成

animation.blade.php
<html>
    <head>
        <title>アニメ追加</title>
    </head>
    <body>
        {{ Form::open() }}
        季節を選択
        {{ Form::select('season',$seasons) }}
        {{ Form::input('text','animation') }}
        {{ Form::submit('追加') }}
        {{ Form::close() }}
    </body>
</html>

こんな画面ができるので、季節を選択してアニメを入力していきます!

image.png

入力し終えるとテーブルはこんな感じに。
image.png

ちなみにanimation_closureテーブルには数字がたくさん入ってます。これは、それぞれの要素が自分の位置親に対してどの位置にいるかを記述したものになっています。

例えば「魔法少女まどかマギカ」,idは5です。
これは冬アニメなので親のidは1になります。つまりancestor = 1, 'descendant = 5'最初に追加されているから'depth = 1 となります。さらに自分の位置としてancestor = 5, descendant = 5, depth = 0も記述されています。確認して見てください!

アニメの時期を取得したり

またRepositoryとControllerに追記。
EloquentAnimationRepository.php

public function getAnimationSeason($animation)
    {
        $child = Animation::where('name', $animation)->get();

        return $child[0]->getParent();
    }

AnimationController.php

public function search()
    {
        return view('animation.search_season');
    }

    public function searchSeason(Request $request)
    {
        $season = $this->animation->getAnimationSeason($request->animation);

        return view('animation.search_season')->with('season', $season);
    }

viewを書いてうまくやるとこんな感じで季節取得できます

image.png

最後に

今回は親子関係しか見ませんでしたが、孫まで全て取ってきたりツリーそのまま持ってきたり、位置を変更したりもできます!!が、今回はもう体力の限界になったのでまた今度

参考ページ

https://www.wakuwakubank.com/posts/599-laravel-closure-table/
3
6
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
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?