【全6回】Laravel6でTwitterっぽいサイトを作るチュートリアル(第1回DB設計とMigration)

Laravelで始めるTwitter風(Twitterクローン)のSNSツール開発チュートリアル

概要

以前5.8で作成した記事の6.x版です。
6.xの書き方に変えるだけじゃなくて、色々気になるところも修正していこうと思います。

Laravel5.8の記事
第1回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第1回DB設計とMigration)

スクールとかの課題だったりLaravelを初めてみたいけど何を作ろうって迷ってる人向けによくあるTwitter風のWEBサイトを作ってみます。

第1回は環境構築からDB設計->マイグレーション実行までやってみたいと思います。

前提

  • PHPをある程度理解している
  • Laravelが使える環境がある
  • MVC構造をある程度理解している

Laravel初心者を脱却したい!という方はこちらも合わせてどうぞ!
Laravelワカンネ(゚⊿゚)から「完全に理解した()」までステップアップ

環境

  • Mac
  • Docker
  • Laravel6.x

今回はDockerを使用していますが、VagrantでもMAMPでもローカルでも何でもOKです!

要件定義

  • ユーザがログインできる(ログイン状態で無いと閲覧や投稿は出来ない)
  • ユーザはツイート(記事)を投稿できる
  • ツイートに対してコメントといいねが出来る
  • コメントにはいいねとコメントはできない
  • ユーザ同士でフォローができ、自身のタイムラインにはフォローしているユーザのツイートのみ閲覧が可能

とまぁこんな感じでしょうか。実際のTwitterはログインしなくても見えるのですが、
めんどくさいので今回はログイン状態時しか閲覧できないとします。

DB設計

テーブルはざっとこんな感じでしょうか。

  • usersテーブル
    • ユーザを管理するテーブル
  • tweetsテーブル
    • ユーザ毎のツイートを管理するテーブル
  • commentsテーブル
    • ツイートに対してコメントする機能
  • favoritesテーブル
    • ツイートに対するいいね機能
  • followersテーブル
    • フォロー関係を管理するテーブル

画像にするとこんな感じです。

この記事ではfavoritesテーブルは中間テーブルとして扱います!

スクリーンショット 2019-08-13 13.19.00.png

テーブルの説明はこの記事が分かりやすいと思います。(Rubyですが)
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸

Migration(マイグレーション)

Migrationとは言うならばテーブルの設計図のようなものです。
テーブルの構築やカラムの追加などの更新をファイルとして残すことで、チームで共有しやすいようにすることが出来ます。
これについては良記事が沢山あるので詳しくは書きません。

artisanコマンド

Laravelには artisan コマンドというターミナルで使用できる便利なコマンドが用意されています。
例えば以下のコマンドを入力すると app/Http/Controllers の中に SampleContorller というファイルがクラスなどを定義した状態で生成されます。

php artisan make:controller SampleController

この要領でターミナルにteMigrationとModelを作っていきたいと思います。

Migrationファイルの作成

テーブルを作成するときは下記のようなコマンドを入力します。

php artisan make:migration create_tweets_table

Laravelではテーブル一つに付き、一つのモデルを用意することが多いので、
モデルも同時に作成したいときは make:model 〇〇 -m とすることでModelとMigrationを同時に作成してくれるので活用していきましょう!

デフォルトではモデルは app/ に設置されてしまうので、モデルのファイルと分かりやすいように app/Models というディレクトリを作成し、そこにまとめる管理方法が一般的です。

Tweetsテーブル

php artisan make:model Models/Tweet -m

Commentsテーブル

php artisan make:model Models/Comment -m

Followersテーブル

php artisan make:model Models/Follower -m

Favoritesテーブル

こちらは中間テーブルとして使用する為Modelは不要

php artisan make:migration create_fovorites_table

Migrationを実際に書いていく

ユーザに関しては最初から 2014_10_12_000000_create_users_table.php というファイルが用意されているのでそちらにカラムを追加します。

書き方など知りたければ日本語ドキュメントをみましょう

Laravel 6.x データベース:マイグレーション

Users

2014_10_12_000000_create_users_table.php
<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('screen_name')->unique()->comment('アカウント名');
            $table->string('name')->comment('ユーザ名');
            $table->string('profile_image')->nullable()->comment('プロフィール画像');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

Tweets

2020_06_09_060211_create_tweets_table.php
<?php

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

class CreateTweetsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tweets', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id')->comment('ユーザID');
            $table->string('text')->comment('本文');
            $table->softDeletes();
            $table->timestamps();

            $table->index('user_id');

            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade')
                ->onUpdate('cascade');
        });
    }

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

この部分でUsersテーブルと外部キー接続を宣言しています。

2020_06_09_060211_create_tweets_table.php
$table->foreign('user_id')
    ->references('id')
    ->on('users')
    ->onDelete('cascade')
    ->onUpdate('cascade');

Comments

2020_06_09_060252_create_comments_table.php
<?php

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

class CreateCommentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id')->comment('ユーザID');
            $table->unsignedInteger('tweet_id')->comment('ツイートID');
            $table->string('text')->comment('本文');
            $table->softDeletes();
            $table->timestamps();

            $table->index(['user_id', 'tweet_id']);

            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade')
                ->onUpdate('cascade');

            $table->foreign('tweet_id')
                ->references('id')
                ->on('tweets')
                ->onDelete('cascade')
                ->onUpdate('cascade');
        });
    }

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

Favorites

2020_06_09_060332_create_favorites_table.php
<?php

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

class CreateFavoritesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('favorites', function (Blueprint $table) {
            $table->unsignedInteger('user_id')->comment('ユーザID');
            $table->unsignedInteger('tweet_id')->comment('ツイートID');

            $table->unique(['user_id', 'tweet_id']);

            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade')
                ->onUpdate('cascade');

            $table->foreign('tweet_id')
                ->references('id')
                ->on('tweets')
                ->onDelete('cascade')
                ->onUpdate('cascade');
        });
    }

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

Favoriteテーブルでは中間テーブルとして使用する為、user_idとtweet_idのみとしてます。

下記の部分ではテーブルにユニーク制約を設定しています。
つまりこのテーブルではuser_idとtweet_idの組み合わせは一意でなければ格納出来ないという制約です

2020_06_09_060332_create_favorites_table.php
$table->unique(['user_id', 'tweet_id']);

Followersテーブル

ここ少しややこしいですね。簡単に言うと自分がフォローしているユーザのツイートをTLに表示するときは
自分がfollowing_idで相手(自分がフォローしているユーザ)がfollowed_idになります。

2020_06_09_060412_create_followers_table.php
<?php

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

class CreateFollowersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('followers', function (Blueprint $table) {
            $table->unsignedInteger('following_id')->comment('フォローしているユーザID');
            $table->unsignedInteger('followed_id')->comment('フォローされているユーザID');

            $table->index(['following_id', 'followed_id']);

            $table->unique(['following_id', 'followed_id']);
        });
    }

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

Model

Modelに設定を記述する

LaravelではEloquentを使用して登録/編集する時にデフォルトでTimestampが登録するようになっていたり、
登録/更新を許可するカラムを指定したりと様々な設定が可能です。

Users

screen_nameprofile_image を追加したので、登録/更新を許可するために
$fillable の配列にカラムを追加します。

※Userは元々app直下にあるのでapp/Modelsに移動してください。その際namespaceを変更するのを忘れずに!

app/Models/User
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'screen_name',
        'name',
        'profile_image',
        'email',
        'password'
    ];

// 省略

}

Tweets

TweetテーブルではSoftDeleteという論理削除(削除してもDBには残るがシステム上削除したデータとして扱う機能)を使える様に設定します。
この時Migrationで $table->softDeletes を設定しておかないと動作しないので注意!

ついでに登録/更新を許可するために $fillableuser_idtext カラムだけ許可しておきます。

$fillableはLaravelで用意されているメンバ変数です。
$fillableにカラム名を定義するとそれ以外のカラムを登録/更新でエラーを吐きます。
つまりホワイトリストですね。
逆に$guardedというのはブラックリストで登録/更新できないカラムを指定します。
基本的にはどちらでも可です!

app/Models/Tweet.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\softDeletes;

class Tweet extends Model
{
    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'text'
    ];
}

Comments

こちらも Tweet と同じ要領です。

app/Models/Comment.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\softDeletes;

class Comment extends Model
{
    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'tweet_id',
        'text'
    ];
}

Followers

Followerテーブルはincrementを使用しないという設定とprimary_keyを指定する設定を合わせて記述します。

app/Models/Follower.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Follower extends Model
{
    protected $primaryKey = [
        'following_id',
        'followed_id'
    ];

    protected $fillable = [
        'following_id',
        'followed_id'
    ];
    public $timestamps = false;
    public $incrementing = false;
}

リレーションの親子関係

Laravelでは関連するテーブルをSQLを意識せずに操作がしやすい様にEloquentという機能が用意されています。
それらを利用する為にModelに関連テーブルとの関係を定義します。

詳しく知りたければ日本語ドキュメントを見よう(投げやり)
Laravel 6.x Eloquent:リレーション

Users

app/Models/User.php
    // 省略

    public function tweets()
    {
        return $this->hasMany(Tweet::class);
    }

    public function followings()
    {
        return $this->belongsToMany(self::class, 'followers', 'followed_id', 'following_id');
    }

    public function followed()
    {
        return $this->belongsToMany(self::class, 'followers', 'following_id', 'followed_id');
    }

Tweets

app/Models/Tweet.php
    // 省略

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function favorites()
    {
        return $this->belongsToMany(User::class, 'favorites');
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

favorites()は中間テーブルを扱う時(いいね付けたり外したり)に使用します。

Comments

app/Models/Comment.php
    // 省略

    public function users()
    {
        return $this->hasMany(User::class);
    }

    public function tweets()
    {
        return $this->hasMany(Tweet::class);
    }

Migration実行

以下のコマンドで先ほど作成したMigrationファイルを実行してDBにテーブルを作成します!

php artisan migrate

これでDBは用意できました!第1回はとりあえずここまで!

namizatork
Laravelがすこ
bengo4
「専門家をもっと身近に」を理念として、人々と専門家をつなぐポータルサイト「弁護士ドットコム」「弁護士ドットコムニュース」「税理士ドットコム」を提供。
https://corporate.bengo4.com/
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした