環境
- Laravel 5.5
- PHP 7.2
この章でやること
14.1 Relationshipモデル
14.1.1 データモデルの問題(および解決策)
(/app/Relationship.php)
php
class Relationship extends Model
{
protected $guarded = ['id'];
}
(/database/migrations/[timestamp]_createrelationships.php)
```php
class CreateRelationships extends Migration
{
public function up()
{
Schema::create('relationships', function (Blueprint $table) {
$table->increments('id');
$table->integer('follower_id');
$table->integer('followed_id');
$table->timestamps();
$table->index('follower_id');
$table->index('followed_id');
$table->unique(['follower_id', 'followed_id']);
$table->foreign('follower_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('followed_id')->references('id')->on('users')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('relationships');
}
}
```
14.1.2 User/Relationshipの関連付け
(/app/User.php)
php
public function activeRelationships()
{
return $this->hasMany("App\Relationship", "follower_id");
}
(/app/Relationship.php)
```php
public function follower()
{
return $this->belongsTo("App\User", "id", "follower_id");
}
public function followed()
{
return $this->belongsTo("App\User", "id", "followed_id");
}
# 14.1.3 Relationshipのバリデーション
# 14.1.4 フォローしているユーザー
(/app/User.php)
```php
public function following()
{
return $this->hasManyThrough("App\User", "App\Relationship", "follower_id", "id", "id", "followed_id");
}
public function isFollowing($user_id)
{
return (bool) Relationship::where("follower_id", $this->id)->where("followed_id", $user_id)->count();
}
(/tests/Unit/UserTest.php)
php
public function testFollowAndUnfollow()
{
$user_1 = User::find(1);
$user_2 = User::find(2);
$this->assertFalse($user_1->isFollowing($user_2->id));
$user_1->follow($user_2->id);
$this->assertTrue($user_1->isFollowing($user_2->id));
$user_1->unfollow($user_2->id);
$this->assertFalse($user_1->isFollowing($user_2->id));
}
(/app/User.php)
```php
public function follow($user_id)
{
$this->activeRelationships()->create(["followed_id" => $user_id]);
}
public function unfollow($user_id)
{
$this->activeRelationships()->where("followed_id", $user_id)->delete();
}
public function isFollowed($user_id)
{
return (bool) Relationship::where("follower_id", $user_id)->where("followed_id", $this->id)->count();
}
# 14.1.5 フォロワー
(/app/User.php)
```php
public function passiveRelationships()
{
return $this->hasMany("App\Relationship", "followed_id");
}
public function followers()
{
return $this->hasManyThrough("App\User", "App\Relationship", "followed_id", "id", "id", "follower_id");
}
(/tests/Unit/UserTest.php)
php
$this->assertEquals(1, $user_1->following()->where("followed_id", $user_2->id)->count());
14.2 Follow のWebインターフェイス
14.2.1 フォローのサンプルデータ
(/database/seeds/UsersTableSeeder.php)
php
$user = User::first();
$following = User::where("id", ">=", 2)->where("id", "<=", 50);
$followers = User::where("id", ">=", 3)->where("id", "<=", 40);
$following->each(function ($following) use ($user) { $user->follow($following->id); });
$followers->each(function ($follower) use ($user) { $follower->follow($user->id); });
14.2.2 統計と Follow フォーム
(/routes/web.php)
php
Route::prefix('users')->group(function () {
Route::get('{user}/following', "UsersController@following")->name("following");
Route::get('{user}/followers', "UsersController@followers")->name("followers");
});
(/resources/views/shared/stats.blade.php)
html
@php empty($user) && $user = Auth::user() @endphp
<div class="stats">
<a href="{{ route("following", $user->id) }}">
<strong id="following" class="stat">
{{ $user->following()->count() }}
</strong>
following
</a>
<a href="{{ route("followers", $user->id) }}">
<strong id="followers" class="stat">
{{ $user->followers()->count() }}
</strong>
followers
</a>
</div>
(/resources/views/static_pages/home.blade.php)
html
<section class="stats">
@include ("shared.stats")
</section>
(/routes/web.php)
php
Route::resource('relationships', "RelationshipsController", ["only" => ['store', 'destroy']]);
(/resources/views/users/follow_form.blade.php)
html
@unless ($user == Auth::user())
<div id="follow_form">
@if (Auth::user()->isFollowing($user->id))
@include("users.unfollow")
@else
@include("users.follow")
@endif
</div>
@endunless
(/resources/views/users/follow.blade.php)
html
{{ Form::open(["route" => "relationships.store"]) }}
{{ Form::hidden('followed_id', $user->id) }}
{{ Form::submit("Follow", ["class" => "btn btn-primary"]) }}
{{ Form::close() }}
(/resources/views/users/unfollow.blade.php)
html
{{ Form::open(["route" => ["relationships.destroy", $user->id], "method" => "delete"]) }}
{{ Form::submit("Unfollow", ["class" => "btn"]) }}
{{ Form::close() }}
(/resources/views/users/show.blade.php)
```html
@extends('layouts.application')
@section('title', $user->name)
@section('content')
{!! gravatar_for($user) !!}
{{ $user->name }}
@include("shared.stats")
@includeWhen (Auth::check(), "users.follow_form")
@if ($user->microposts())
Microposts ({{ $user->microposts()->count() }})
@foreach ($microposts as $micropost)
@include ("microposts.micropost")
@endforeach
{{ $microposts->links() }}
@endif
@endsection
```
14.2.3 Following と Followers ページ
(/tests/Unit/UsersControllerTest.php)
```php
public function testRedirectFollowing()
{
$response = $this->get(route("following", 1));
$response->assertRedirect(route("login"));
}
public function testRedirectFollowers()
{
$response = $this->get(route("followers", 1));
$response->assertRedirect(route("login"));
}
(/app/Http/Controllers/UsersController.php)
```php
public function __construct()
{
$this->middleware('guest')->only(["index", "edit", "update", "destroy", "following", "followers"]);
}
public function following($user)
{
$title = "Following";
$user = User::find($user);
$users = $user->following()->paginate(30);
return view("users.show_follow")->with(["title" => $title, "user" => $user, "users" => $users]);
}
public function followers($user)
{
$title = "Following";
$user = User::find($user);
$users = $user->followers()->paginate(30);
return view("users.show_follow")->with(["title" => $title, "user" => $user, "users" => $users]);
}
(/resources/views/users/show_follow.blade.php)
```html
@extends('layouts.application')
@section('title', $title)
@section('content')
{!! gravatar_for($user) !!}
{{ $user->name }}
{{ Html::linkRoute("users.show", "view my profile", $user->id) }}
Microposts: {{ $user->microposts()->count() }}
@include("shared.stats")
@if ($users)
@endif
{{ $title }}
@if ($users)
@foreach ($users as $user)
@include("users.user", ["user" => $user])
@endforeach
{{ $users->links() }}
@endif
@endsection
(/database/seeds/test/TestSeeder.php)
phpRelationship::create(["follower_id" => 1, "followed_id" => 3]);
Relationship::create(["follower_id" => 1, "followed_id" => 4]);
Relationship::create(["follower_id" => 3, "followed_id" => 1]);
Relationship::create(["follower_id" => 2, "followed_id" => 1]);
(/tests/Feature/FollowingTest.php)
phpclass FollowingTest extends TestCase
{
private $user;
protected function setUp()
{
parent::setUp();
Artisan::call('migrate:refresh');
$this->seed('TestSeeder');
$this->user = User::find(1);
$this->be($this->user);
}
public function testFollowingPage()
{
$response = $this->get(route("following", $this->user->id));
$this->assertNotEmpty($this->user->following());
$response->assertSeeText((string) $this->user->following()->count());
$dom = $this->dom($response->content());
foreach ($this->user->following as $following) {
$this->assertSame(route("users.show", $following->id), $dom->filter("a:contains(\"{$following->name}\")")->attr("href"));
}
}
public function testFollowersPage()
{
$response = $this->get(route("followers", $this->user->id));
$this->assertNotEmpty($this->user->followers());
$response->assertSeeText((string) $this->user->followers()->count());
$dom = $this->dom($response->content());
foreach ($this->user->followers as $follower) {
$this->assertSame(route("users.show", $follower->id), $dom->filter("a:contains(\"{$follower->name}\")")->attr("href"));
}
}
}
```
14.2.4 Follow ボタン (基本編)
(/tests/Unit/RelationshipsControllerTest.php)
```php
class RelationshipsControllerTest extends TestCase
{
public function testRedirectCreatePage()
{
$count = Relationship::all()->count();
$response = $this->post(route("relationships.create"));
$this->assertEquals($count, Relationship::all()->count());
$response->assertRedirect(route("login"));
}
public function testRedirectDestroyPage()
{
$count = Relationship::all()->count();
$response = $this->delete(route("relationships.destory", 1));
$this->assertEquals($count, Relationship::all()->count());
$response->assertRedirect(route("login"));
}
}
php
(/app/Http/Controllers/RelationshipsController.php)
class RelationshipsController extends Controller
{
public function __construct()
{
$this->middleware('authenticate');
}
public function store(Request $request)
{
Auth::user()->follow($request->followed_id);
return redirect()->route("users.show", Auth::id());
}
public function destroy($id)
{
Auth::user()->unfollow($id);
return redirect()->route("users.show", Auth::id());
}
}
```
14.2.5 Follow ボタン (Ajax編)
14.2.6 フォローをテストする
(/tests/Feature/FollowingTest.php)
```php
class FollowingTest extends TestCase
{
private $user;
private $other_user;
protected function setUp()
{
parent::setUp();
Artisan::call('migrate:refresh');
$this->seed('TestSeeder');
$this->user = User::find(1);
$this->other_user = User::find(2);
$this->be($this->user);
}
public function testFollowUser()
{
$count = Relationship::all()->count();
$this->post(route("relationships.store"), ["followed_id" => $this->other_user->id]);
$this->assertEquals($count + 1, Relationship::all()->count());
}
public function testUnfollowUser()
{
$this->user->follow($this->other_user->id);
$count = Relationship::all()->count();
$this->delete(route("relationships.destroy", $this->other_user->id));
$this->assertEquals($count - 1, Relationship::all()->count());
}
}
```
14.3 ステータスフィード
14.3.1 動機と計画
(/tests/Unit/UserTest.php)
php
public function testStatusFeed()
{
$user_1 = User::find(1);
$user_2 = User::find(2);
$user_3 = User::find(3);
foreach ($user_3->microposts as $micropost) {
$this->assertTrue($user_1->feed()->get()->contains($micropost));
}
foreach ($user_1->microposts as $micropost) {
$this->assertTrue($user_1->feed()->get()->contains($micropost));
}
foreach ($user_2->microposts as $micropost) {
$this->assertFalse($user_1->feed()->get()->contains($micropost));
}
}
14.3.2 フィードを初めて実装する
(/app/User.php)
php
public function feed()
{
$relations = $this->activeRelationships()->get()->toArray();
$followed_ids = array_pluck($relations, "followed_id");
return Micropost::whereIn("user_id", $followed_ids)->orWhere("user_id", $this->id);
}
(/app/Http/Controllers/StaticPagesController.php)
php
public function home()
{
$feed_items = null;
if (Auth::check()) {
$feed_items = Auth::user()->feed()->paginate(30);
}
return view('static_pages/home')->with("feed_items", $feed_items);
}