Laravelで始めるTwitter風(Twitterクローン)のSNSツール開発チュートリアル
この記事はLaravel5.7で作成されているため、2024/07 現在 Laravel11で実装するとかなりのエラーが発生します。
プログラムの参考程度にしてください
概要
スクールとかの課題だったりLaravelを初めてみたいけど何を作ろうって迷ってる人向けによくあるTwitter風のWEBサイトを作ってみます。
- 第1回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第1回DB設計とMigration)
- 第2回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第2回Seeder->ログイン/新規登録)
- 第3回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第3回ユーザ関連とフォロー機能)
- 第4回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第4回ツイートのCRUD機能)
- 第5回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第5回ツイートのCRUD機能 編集と削除)
- 第6回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第6回コメントといいね機能)
第1回は環境構築からDB設計->マイグレーション実行までやってみたいと思います。
前提
- PHPをある程度理解している
- Laravelが使える環境がある
- MVC構造をある程度理解している
環境
- Mac
- Homestead
- Laravel 5.8
Homestead
今回はHomesteadを使用していますが、DockerでもMAMPでもローカルでも何でもOKです!
DB設計
DB設計といえど要件がなければ設計のしようが無いので簡潔に要件をまとめます。
- ユーザがログインできる(ログイン状態で無いと閲覧や投稿は出来ない)
- ユーザ毎にツイート(記事)を投稿できる
- ツイートに対してコメントといいねが出来る
- コメントにはいいねとコメントはできない
- ユーザ同士でフォローができ、自身のタイムラインにはフォローしているユーザのツイート以外見えない
とまぁこんな感じでしょうか。実際のTwitterはログインしなくても見えるのですが、
めんどくさいので今回はログイン状態時しか閲覧できないとします。
テーブルはざっとこんな感じでしょうか。
- usersテーブル
- ユーザを管理するテーブル
- tweetsテーブル
- ユーザ毎のツイートを管理するテーブル
- commentsテーブル
- ツイートに対してコメントする機能
- favoritesテーブル
- ツイートに対するいいね機能
- followersテーブル
- フォロー関係を管理するテーブル
画像にするとこんな感じです。
テーブルの説明はこの記事が分かりやすいと思います。(Rubyですが)
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸
Migration(マイグレーション)
Migrationとは言うならばテーブルの設計図のようなものです。
テーブルの構築やカラムの追加などの更新をチームで共有しやすいようにすることが出来ます。
これについては良記事が沢山あるので詳しくは書きません。
artisanコマンド
Laravelにはartisan
コマンドというターミナルで使える便利なコマンドが用意されています。
例えば以下のコマンドを入力するとapp/Http/Controllers
の中にSampleContorller
というファイルが生成されます。
php artisan make:controller SampleController
この要領でMigrationとModelを作っていきたいと思います。
Migrationファイルの作成
make:model 〇〇 -m とすることでModelとMigrationを同時に作成してくれます。
それと分かりやすいようにapp/Models
と言うディレクトリを作成してそこにまとめて管理します。
Tweets
テーブル
php artisan make:model Models/Tweet -m
Comments
テーブル
php artisan make:model Models/Comment -m
Favorites
テーブル
php artisan make:model Models/Favorite -m
Followers
テーブル
php artisan make:model Models/Follower -m
Migrationを実際に書いていく
ユーザに関しては最初から存在している2014_10_12_000000_create_users_table.php
と言うファイルが用意されているのでそちらにカラムを追加します。
Users
<?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()->null()->comment('アカウント名');
$table->string('name')->null()->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
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
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('id');
$table->index('user_id');
$table->index('text');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade')
->onUpdate('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tweets');
}
}
この部分でUsers
テーブルと外部キー接続を宣言しています。
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade')
->onUpdate('cascade');
Comments
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
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('id');
$table->index('user_id');
$table->index('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
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateFavoritesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('favorites', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id')->comment('ユーザID');
$table->unsignedInteger('tweet_id')->comment('ツイートID');
$table->index('id');
$table->index('user_id');
$table->index('tweet_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');
}
}
Followers
テーブル
ここ少しややこしいですね。簡単に言うと自分がフォローしているユーザのツイートをTLに表示するときは
自分がfollowing_id
で相手(自分がフォローしているユーザ)がfollowed_id
になります。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
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');
$table->index('followed_id');
$table->unique([
'following_id',
'followed_id'
]);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('followers');
}
}
uniqueを登録する事で以下のキーの組み合わせで同じIDの登録を防ぐことができる
$table->unique([
'following_id',
'followed_id'
]);
Model
Modelに設定を記述する
LaravelではEroquentを使用して登録/編集する時にデフォルトでTimestampが登録するようになっていたり、
登録/更新を許可するカラムを指定したりと様々な設定が可能です。
Users
screen_name
とprofile_image
を追加したので、登録/更新を許可するために
$fillableの配列にカラムを指定します。
※Userは元々app
直下にあるのでapp/Models
に移動してください。その際namespaceを変更するのを忘れずに!
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'screen_name',
'name',
'profile_image',
'email',
'password'
];
}
Tweets
TweetテーブルではSoftDeleteという論理削除(削除してもDBには残るがシステム上削除したとみなす機能)を使える様に設定します。
ついでに登録/更新を許可するために$fillable
はtext
カラムだけ許可しておきます。
$fillableはLaravelで用意されているメンバ変数です。
$fillableにカラム名を定義するとそれ以外のカラムを登録/更新でエラーを吐きます。
つまりホワイトリストですね。
逆に$guardedというのはブラックリストで登録/更新できないカラムを指定します。
基本的にはどちらでも可です。
<?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 = [
'text'
];
}
Comments
こちらもTweet
と同じです。
<?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 = [
'text'
];
}
Favorites
デフォルトでTimestampが設定されているのでfalseにします。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Favorite extends Model
{
public $timestamps = false;
}
Followers
Followerテーブルはincrementも使用しないという設定とprimary_keyを指定する設定を合わせて記述します。
<?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;
}
リレーションの親子関係
Users
// 省略
public function followers()
{
return $this->belongsToMany(self::class, 'followers', 'followed_id', 'following_id');
}
public function follows()
{
return $this->belongsToMany(self::class, 'followers', 'following_id', 'followed_id');
}
Tweets
<?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 = [
'text'
];
public function user()
{
return $this->belongsTo(User::class);
}
public function favorites()
{
return $this->hasMany(Favorite::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Comments
<?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 = [
'text'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Migration実行!
php artisan migrate
これでDBは用意できました!第1回はとりあえずここまで!
次回 -> 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第2回Seeder->ログイン/新規登録)