新しいプロジェクトの作成
Twitterに似たサービスを作ってみましょう。
新しいプロジェクトを作成します。
プロジェクト名は、XとTwitterの両方をリスペクトして、xtterという名前で作成します。
> composer create-project laravel/laravel xtter
データベースの用意
xtter用のデータベースを用意します。
まず、MySQLクライアントを起動します。
> mysql -u root -p
MySQLクライアントで以下のコマンドを入力して、新しいデータベースとユーザーを作成し、データベースにユーザーを割り当てます。
mysql> create database xtter; Query OK, 1 row affected (0.00 sec) mysql> create user xtter@localhost identified by 'xtterdb'; Query OK, 0 rows affected (0.01 sec) mysql> grant all privileges on xtter.* to xtter@localhost; Query OK, 0 rows affected (0.00 sec)
VSCodeで .env ファイルを開き、データベースの設定部分を以下のように修正します。
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=xtter DB_USERNAME=xtter DB_PASSWORD=xtterdb
テーブルの作成
以下のコマンドを入力して、Laravelのデフォルトのテーブルを作成します。
> php artisan migrate
mysqlクライアントでテーブルができているのを確認します。
MySQL [(none)]> use xtter; Database changed MySQL [xtter]> show tables; +-----------------------+ | Tables_in_xtter | +-----------------------+ | cache | | cache_locks | | failed_jobs | | job_batches | | jobs | | migrations | | password_reset_tokens | | sessions | | users | +-----------------------+ 9 rows in set (0.001 sec)
ユーザー認証機能を有効にする
ログインが必要なサービスなので、ユーザー認証機能を有効にします。
作成した xtter プロジェクトに関しての操作を行うために、xtter フォルダに移動します。
> cd xtter
以下のコマンドを実行してください。
> composer require laravel/breeze > php artisan breeze:install
breeze:install では、最初に選択肢を聞かれるので、bladeと入力してください。
そのあとの質問には、ENTERだけ入力してください。
Laravelの起動
以下のコマンドを実行して、Laravelを起動します。
> php artisan serv forking is not supported on this platform INFO Server running on [http://127.0.0.1:8000]. Press Ctrl+C to stop the server
Chromeを起動して、http://127.0.0.1:8000 にアクセスすると、Laravelのページが表示できます。
Breezeをインストールしていると、画面右上に「Log in」と「Register」のリンクが表示されていますので、「Register」をクリックしてユーザー登録してみてください。
データベースの確認
mysqlクライアントで以下のコマンドを入力して、データベースにユーザーが登録されていることを確認してください。
> mysql -u root -p Enter password: ********* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 18 Server version: 5.7.44-log MySQL Community Server (GPL) Copyright (c) 2000, 2023, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use xtter; Database changed mysql> select * from users; +----+----------+--------------------+-------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+ | id | name | email | email_verified_at | password | remember_token | created_at | updated_at | +----+----------+--------------------+-------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+ | 1 | satoshis | satoshis@example.com | NULL | $2y$12$R2OOAoUymqnEfyX0v6pikOJS92RGFOIJ.uTBFmGnbh7.WJXgx1yqu | NULL | 2024-11-01 05:11:19 | 2024-11-01 05:11:19 | +----+----------+--------------------+-------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+ 1 row in set (0.00 sec) mysql> quit Bye
データベースの内容は、tinkerを使うことでも確認できます。
> php artisan tinker
Psy Shell v0.12.4 (PHP 8.2.12 — cli) by Justin Hileman
> App\Models\User::all()
= Illuminate\Database\Eloquent\Collection {#5996
all: [
App\Models\User {#5587
id: 1,
name: "satoshis",
email: "satoshis@example.com",
email_verified_at: null,
#password: "$2y$12$R2OOAoUymqnEfyX0v6pikOJS92RGFOIJ.uTBFmGnbh7.WJXgx1yqu",
#remember_token: null,
created_at: "2024-11-01 05:11:19",
updated_at: "2024-11-01 05:11:19",
},
],
}
>
ダッシュボードにツイートするフォームを追加する
resources/views/dashboard.blade.php を開き、以下のコードを追加します。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
{{ __("You're logged in!") }}
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('tweet') }}" method="post">
@csrf
<textarea name="tweet" required></textarea>
<br />
<button>ツイートする</button>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
コントローラーを追加する
以下のコマンドを入力して、コントローラーを作成します。
> php artisan make:controller TweetController
app/Http/Controllers フォルダに、TweetController.php が作られています。
POSTメソッドでのアクセスを受け取るため、以下のコードを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TweetController extends Controller
{
public function tweet(Request $request)
{
return redirect()->route('dashboard');
}
}
URLとコントローラーを関連付ける
routes/web.php を開き、以下のコードを追加します。
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TweetController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->name('tweet');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';
ツイートに対応するモデルを作成する
以下のコマンドを入力して、Tweetクラスを作成します。
「-m」オプションをつけておくと、マイグレーションファイルも同時に作成してくれます。
> php artisan make:model Tweet -m
Tweet.php には以下のコードを追加します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tweet extends Model
{
protected $fillable = ['user_id', 'text'];
}
マイグレーションファイルに以下のコードを追加します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tweets', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->string('text');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tweets');
}
};
マイグレーションを実行します。
> php artisan migrate
データベースに新しく tweets テーブルが作られているのを確認してみましょう。
MySQL [(none)]> use xtter; Database changed MySQL [xtter]> show tables; +-----------------------+ | Tables_in_xtter | +-----------------------+ | cache | | cache_locks | | failed_jobs | | job_batches | | jobs | | migrations | | password_reset_tokens | | sessions | | tweets | | users | +-----------------------+ 10 rows in set (0.000 sec) MySQL [xtter]> desc tweets; +------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | user_id | bigint(20) | NO | | NULL | | | text | varchar(255) | NO | | NULL | | | created_at | timestamp | YES | | NULL | | | updated_at | timestamp | YES | | NULL | | +------------+---------------------+------+-----+---------+----------------+ 5 rows in set (0.001 sec)
ツイートをデータベースに保存する
TweetController の tweet() メソッドでツイートをデータベースに保存します。
以下のコードを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Tweet;
class TweetController extends Controller
{
public function tweet(Request $request)
{
$user = \Auth::user();
Tweet::create([
'user_id' => $user->id,
'text' => $request->input('tweet'),
]);
return redirect()->route('dashboard');
}
}
過去のツイートを表示する
ダッシュボード画面に過去のツイートを表示してみましょう。
ツイートデータを渡すために、web.php を変更します。
もともとあった /dashboard に関する設定は削除して11行目を追加します。
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TweetController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', [TweetController::class, 'index'])->middleware('auth')->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->middleware('auth')->name('tweet');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';
コントローラーに index() メソッドを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Tweet;
class TweetController extends Controller
{
public function index()
{
$tweets = Tweet::orderBy('created_at', 'desc')->get();
return view('dashboard', compact('tweets'));
}
public function tweet(Request $request)
{
$user = \Auth::user();
Tweet::create([
'user_id' => $user->id,
'text' => $request->input('tweet'),
]);
return redirect()->route('dashboard');
}
}
dashboard.blade.php では、渡された tweets を表示します。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
{{ __("You're logged in!") }}
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('tweet') }}" method="post">
@csrf
<textarea name="tweet" required></textarea>
<br />
<button>ツイートする</button>
</form>
</div>
<div class="p-6 text-gray-900">
@foreach($tweets as $tweet)
<div>
<div>{!! nl2br($tweet->text) !!}</div>
<div>{{ $tweet->created_at }}</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-app-layout>
ツイートに投稿者の名前を表示する
いまのままでは、誰がツイートしたのかがわかりませんので、ツイートの上に投稿した人の名前を表示するように変更します。
Tweetクラスには、user_id が含まれていますが、名前はわかりません。
Tweetクラスから user_id を元に User を取得するように変更します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
class Tweet extends Model
{
protected $fillable = ['user_id', 'text'];
public function user()
{
return $this->belongsTo(User::class);
}
}
tinkerを使って確認してみます。
# php artisan tinker
Psy Shell v0.12.4 (PHP 8.2.12 — cli) by Justin Hileman
> $t = App\Models\Tweet::find(1)
= App\Models\Tweet {#6004
id: 1,
user_id: 1,
text: "はじめまして!",
created_at: "2024-11-01 06:51:47",
updated_at: "2024-11-01 06:51:47",
}
> $t->user
= App\Models\User {#5954
id: 1,
name: "satoshis",
email: "satoshis@example.com",
email_verified_at: null,
#password: "$2y$12$R2OOAoUymqnEfyX0v6pikOJS92RGFOIJ.uTBFmGnbh7.WJXgx1yqu",
#remember_token: null,
created_at: "2024-11-01 05:11:19",
updated_at: "2024-11-01 05:11:19",
}
>
TweetからUserが取得できるようになりましたので、これをbashboardに追加します。
<div class="p-6 text-gray-900">
@foreach($tweets as $tweet)
<div>
<div>{{ $tweet->user->name }}
<div>{!! nl2br($tweet->text) !!}</div>
<div>{{ $tweet->created_at }}</div>
</div>
@endforeach
</div>
これで投稿者の名前も表示できるようになりました。
しかし、どこからどこまでがひとつのツイートなのかがわかりにくいので、CSSで少しだけデザインを調整してみましょう。
ツイート単位の div に class=”tweet” を追加します。
<div class="p-6 text-gray-900">
@foreach($tweets as $tweet)
<div class="tweet">
<div>{{ $tweet->user->name }}</div>
<div>{!! nl2br($tweet->text) !!}</div>
<div>{{ $tweet->created_at }}</div>
</div>
@endforeach
</div>
app.blade.php にCSSを追加します。
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<style>
.tweet {
border: 2px solid gray;
border-radius: 5px;
margin: 2px;
padding-left: 5px;
}
</style>
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
プロフィールページを表示できるようにする
ツイートの上にあるユーザー名をクリックすると、プロフィールページに遷移するようにします。
まずは web.php にプロフィールページ用のURLを用意します。
Route::get('/dashboard', [TweetController::class, 'index'])->middleware('auth')->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->middleware('auth')->name('tweet');
Route::get('/prof/{name}', [TweetController::class, 'profile'])->middleware('auth')->name('profile');
URLを用意したので、blade を変更します。
ユーザー名の部分を a タグにして href 属性の値を上記 web.php で追加したルーティングを記述します。
<div class="p-6 text-gray-900">
@foreach($tweets as $tweet)
<div class="tweet">
<div><a href="{{ route('profile', $tweet->user->name) }}">{{ $tweet->user->name }}</a></div>
<div>{!! nl2br($tweet->text) !!}</div>
<div>{{ $tweet->created_at }}</div>
</div>
@endforeach
</div>
ユーザー名がリンクになりました。
クリックしてみるとエラーになります。
エラーメッセージを見ると、「Call to undefined method App\Http\Controllers\TweetController::profile()」となっています。
TweetControllerにprofile()メソッドを追加しましょう。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Tweet;
use App\Models\User;
class TweetController extends Controller
{
public function index()
{
$tweets = Tweet::orderBy('created_at', 'desc')->get();
return view('dashboard', compact('tweets'));
}
public function tweet(Request $request)
{
$user = \Auth::user();
Tweet::create([
'user_id' => $user->id,
'text' => $request->input('tweet'),
]);
return redirect()->route('dashboard');
}
public function profile($name)
{
$user = User::where('name', $name)->first();
return view('profile', compact('user'));
}
}
profile() メソッドを用意したので、もう一度ユーザー名をクリックしてみます。
すると「View [profile] not found.」のエラーになります。
ビュー profile を作ります。
> php artisan make:view profile INFO View [C:\Users\teacher\Documents\php\xtter\resources\views\profile.blade.php] created successfully.
ページを再読み込みするとエラーは出なくなり、真っ白なページが表示されるようになりました。
profile.blade.php の内容を以下のようにすると、ユーザー名だけを表示するページができます。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Profile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h2>{{ $user->name }}さん</h2>
</div>
</div>
</div>
</div>
</x-app-layout>
プロフィールページに表示名と自己紹介を追加する
プロフィールページに表示名(ツイッターでのログインIDとは別に、ツイッター上で表示する名前を設定できる)と自己紹介を追加してみましょう。
まず表示名と自己紹介を保存するための Profile モデルを作成します。
> php artisan make:model Profile -m
Profileクラスでは、データベースのテーブルに保存するカラムに対するアクセス許可を記入します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model
{
protected $fillable = ['user_id', 'name', 'description'];
}
マイグレーションファイルには、それらのカラムの追加を記述します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->string('name');
$table->string('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('profiles');
}
};
マイグレーションを実行します。
> php artisan migrate INFO Running migrations. 2024_11_08_151854_create_profiles_table .......... 24.84ms DONE
TweetControllerからビューに対して Profile も渡すようにします。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Profile;
use App\Models\Tweet;
use App\Models\User;
class TweetController extends Controller
{
public function index()
{
$tweets = Tweet::orderBy('created_at', 'desc')->get();
return view('dashboard', compact('tweets'));
}
public function tweet(Request $request)
{
$user = \Auth::user();
Tweet::create([
'user_id' => $user->id,
'text' => $request->input('tweet'),
]);
return redirect()->route('dashboard');
}
public function profile($name)
{
$user = User::where('name', $name)->first();
$profile = Profile::where('user_id', $user->id)->first();
return view('profile', compact('user', 'profile'));
}
}
プロフィールページを修正します。
Profileを登録していないユーザーもいるので、チェックを入れておきます。
<div class="p-6 text-gray-900">
@if(empty($profile))
<h2>{{ $user->name }}さん</h2>
<div>表示名と自己紹介は未設定です。</div>
@else
<h2>{{ $profile->name }}さん ({{ $user->name }})</h2>
<div>{!! nl2br($profile->description) !!}</div>
@endif
</div>
ログインしているユーザーと、プロフィールページのユーザーが一致している時だけ編集できるようにします。
フォームは最初は隠しておき「編集する」ボタンがクリックされたら表示するようにします。
<div class="p-6 text-gray-900">
@if(empty($profile))
<h2>{{ $user->name }}さん</h2>
<div>表示名と自己紹介は未設定です。</div>
@else
<h2>{{ $profile->name }}さん ({{ $user->name }})</h2>
<div>{!! nl2br($profile->description) !!}</div>
@endif
@if(\Auth::user()->id == $user->id)
<button class="form-edit btn">編集する</button>
<form id="form-profile" action="" method="post">
@csrf
表示名: <input type="text" name="name" value="{{ old('name') }}" required />
<br />
自己紹介:
<br />
<textarea name="description" required>{{ old('profile') }}</textarea>
<br />
<button class="btn">保存する</button>
</form>
@endif
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#form-profile').hide();
$('.form-edit').click(function() {
$('#form-profile').show();
})
})
</script>
app.blade.php に jQuery の読み込みを追加します。
<!-- Scripts -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
@vite(['resources/css/app.css', 'resources/js/app.js'])
フォームが用意できたので、入力内容を保存する部分を作ります。
まずはpostメソッドを受け取るためのURLを用意します。
Route::get('/dashboard', [TweetController::class, 'index'])->middleware('auth')->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->middleware('auth')->name('tweet');
Route::get('/prof/{name}', [TweetController::class, 'profile'])->middleware('auth')->name('profile');
Route::post('/updateProfile', [TweetController::class, 'updateProfile'])->middleware('auth')->name('updateProfile');
TweetControllerに updateProfile() メソッドを作ります。
public function updateProfile(Request $request)
{
$user = \Auth::user();
$profile = Profile::where('user_id', $user->id)->first();
if (empty($profile)) {
$profile = new Profile;
}
$profile->user_id = $user->id;
$profile->name = $request->input('name');
$profile->description = $request->input('description');
$profile->save();
return redirect()->route('profile', $user->name);
}
profile.blade.php の form の action 属性の値をさきほど web.php に指定したものを設定します。
@if(\Auth::user()->id == $user->id)
<button class="form-edit btn">編集する</button>
<form id="form-profile" action="{{ route('updateProfile') }}" method="post">
@csrf
表示名: <input type="text" name="name" value="{{ old('name') }}" required />
<br />
自己紹介:
<br />
<textarea name="description" required>{{ old('profile') }}</textarea>
<br />
<button class="btn">保存する</button>
</form>
@endif
ほかの人をフォローできるようにする
他の人をフォローできるようにするために、まずはフォローボタンをプロフィールページに追加します。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Profile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@if(empty($profile))
<h2>{{ $user->name }}さん</h2>
<div>表示名と自己紹介は未設定です。</div>
@else
<h2>{{ $profile->name }}さん ({{ $user->name }})</h2>
<div>{!! nl2br($profile->description) !!}</div>
@endif
@if(\Auth::user()->id == $user->id)
<button class="form-edit btn">編集する</button>
<form id="form-profile" action="{{ route('updateProfile') }}" method="post">
@csrf
表示名: <input type="text" name="name" value="{{ old('name') }}" required />
<br />
自己紹介:
<br />
<textarea name="description" required>{{ old('profile') }}</textarea>
<br />
<button class="btn">保存する</button>
</form>
@else
<form id="form-follow" action="{{ route('follow') }}" method="post">
@csrf
<input type="hidden" name="user_id" value="{{ \Auth::user()->id }}" />
<input type="hidden" name="follow_id" value="{{ $user->id }}" />
<button id="follow-button" class="btn">フォローする</button>
</form>
@endif
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#form-profile').hide();
$('.form-edit').click(function() {
$('#form-profile').show();
})
})
</script>
</x-app-layout>
フォローボタンが押された時の受け取る場所をサーバー側に用意します。
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TweetController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', [TweetController::class, 'index'])->middleware('auth')->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->middleware('auth')->name('tweet');
Route::get('/prof/{name}', [TweetController::class, 'profile'])->middleware('auth')->name('profile');
Route::post('/updateProfile', [TweetController::class, 'updateProfile'])->middleware('auth')->name('updateProfile');
Route::post('/follow', [TweetController::class, 'follow'])->middleware('auth')->name('follow');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';
この状態で「フォローする」ボタンを押すと、TweetController::follow()メソッドが定義されていないというエラーがブラウザ上に表示されます。
TweetControllerにfollow()メソッドを追加します。
とりあえず、データベースには何も記録することはしないで、同じページにリダイレクトするようにします。
public function follow(Request $request)
{
$follow_id = $request->input('follow_id');
$user = User::find($follow_id);
return redirect()->route('profile', $user->name);
}
誰かが誰かをフォローする操作をしたときに、データベースにその内容を記録するようにします。
Followクラスを作成して、内容をデータベースに保存できるようにします。
php artisan make:model Follow -m INFO Model [C:\Users\teacher\Documents\php\xtter\app\Models\Follow.php] created successfully. INFO Migration [C:\Users\teacher\Documents\php\xtter\database\migrations/2024_11_15_141610_create_follows_table.php] created successfully.
Followクラスでは、$fillable を定義して、データベースに保存できるカラムを指定します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Follow extends Model
{
protected $fillable = ['user_id', 'follow_id'];
}
マイグレーションファイルでは、user_id と follow_id を追加するように指定します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('follows', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->bigInteger('follow_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('follows');
}
};
マイグレーションを実行します。
php artisan migrate INFO Running migrations. 2024_11_15_141610_create_follows_table ........... 40.00ms DONE
TweetControllerに、フォローをデータベースに保存するコードを追加します。
public function follow(Request $request)
{
$user_id = $request->input('user_id');
$follow_id = $request->input('follow_id');
$user = User::find($follow_id);
Follow::create([
'user_id' => $user_id,
'follow_id' => $follow_id,
]);
return redirect()->route('profile', $user->name);
}
テーブルが作られたので「フォローする」ボタンを押すと、テーブルにその内容が記録されていることがわかります。
php artisan tinker
Psy Shell v0.12.4 (PHP 8.2.12 — cli) by Justin Hileman
> App\Models\Follow::all()
= Illuminate\Database\Eloquent\Collection {#6002
all: [
App\Models\Follow {#5593
id: 1,
user_id: 2,
follow_id: 1,
created_at: "2024-11-15 14:27:48",
updated_at: "2024-11-15 14:27:48",
},
],
}
>
ログインしているユーザーが、プロフィールページのユーザーをフォローしているかどうかを、profile.blade.php に渡すために、
新しく following という変数を用意します。
public function profile($name)
{
$user = User::where('name', $name)->first();
$profile = Profile::where('user_id', $user->id)->first();
$user_id = \Auth::user()->id;
$following = Follow::where('user_id', $user_id)->where('follow_id', $user->id)->first();
return view('profile', compact('user', 'profile', 'following'));
}
profile.blade.php では、following 変数を参照して「フォローする」または「フォロー解除する」のボタンを表示するようにします。
フォロー解除する側のフォームは id と action の値を変更しておきます。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Profile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@if(empty($profile))
<h2>{{ $user->name }}さん</h2>
<div>表示名と自己紹介は未設定です。</div>
@else
<h2>{{ $profile->name }}さん ({{ $user->name }})</h2>
<div>{!! nl2br($profile->description) !!}</div>
@endif
@if(\Auth::user()->id == $user->id)
<button class="form-edit btn">編集する</button>
<form id="form-profile" action="{{ route('updateProfile') }}" method="post">
@csrf
表示名: <input type="text" name="name" value="{{ old('name') }}" required />
<br />
自己紹介:
<br />
<textarea name="description" required>{{ old('profile') }}</textarea>
<br />
<button class="btn">保存する</button>
</form>
@else
@if(empty($following))
<form id="form-follow" action="{{ route('follow') }}" method="post">
@csrf
<input type="hidden" name="user_id" value="{{ \Auth::user()->id }}" />
<input type="hidden" name="follow_id" value="{{ $user->id }}" />
<button id="follow-button" class="btn">フォローする</button>
</form>
@else
<div>フォローしています。</div>
@endif
@endif
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#form-profile').hide();
$('.form-edit').click(function() {
$('#form-profile').show();
})
})
</script>
</x-app-layout>
web.php に unfollow を追加します。
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TweetController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', [TweetController::class, 'index'])->middleware('auth')->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->middleware('auth')->name('tweet');
Route::get('/prof/{name}', [TweetController::class, 'profile'])->middleware('auth')->name('profile');
Route::post('/updateProfile', [TweetController::class, 'updateProfile'])->middleware('auth')->name('updateProfile');
Route::post('/follow', [TweetController::class, 'follow'])->middleware('auth')->name('follow');
Route::post('/unfollow', [TweetController::class, 'unfollow'])->middleware('auth')->name('unfollow');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';
TweetControllerにunfollow()メソッドを追加します。
unfollow() 内では、Followを削除します。
public function unfollow(Request $request)
{
$user_id = $request->input('user_id');
$follow_id = $request->input('follow_id');
$user = User::find($follow_id);
$f = Follow::where('user_id', $user_id)->where('follow_id', $follow_id)->first();
$f->delete();
return redirect()->route('profile', $user->name);
}
プロフィールページにその人のツイートを表示する
プロフィールページでは、その人のツイートの一覧を表示してみましょう。
プロフィールページを表示しているメソッドはTweetControllerのprofile()メソッドです。
ここでその人のツイートを検索して、profile.blade.php に渡すようにします。
public function profile($name)
{
$user = User::where('name', $name)->first();
$profile = Profile::where('user_id', $user->id)->first();
$user_id = \Auth::user()->id;
$following = Follow::where('user_id', $user_id)->where('follow_id', $user->id)->first();
$tweets = Tweet::where('user_id', $user->id)->orderBy('created_at', 'desc')->get();
return view('profile', compact('user', 'profile', 'following', 'tweets'));
}
profile.blade.php では、渡された $tweets 変数に格納されたツイートをループで表示してやります。
<div class="p-6 text-gray-900">
@if(empty($profile))
<h2>{{ $user->name }}さん</h2>
<div>表示名と自己紹介は未設定です。</div>
@else
<h2>{{ $profile->name }}さん ({{ $user->name }})</h2>
<div>{!! nl2br($profile->description) !!}</div>
@endif
@if(\Auth::user()->id == $user->id)
<button class="form-edit btn">編集する</button>
<form id="form-profile" action="{{ route('updateProfile') }}" method="post">
@csrf
表示名: <input type="text" name="name" value="{{ old('name') }}" required />
<br />
自己紹介:
<br />
<textarea name="description" required>{{ old('profile') }}</textarea>
<br />
<button class="btn">保存する</button>
</form>
@else
@if(empty($following))
<form id="form-follow" action="{{ route('follow') }}" method="post">
@csrf
<input type="hidden" name="user_id" value="{{ \Auth::user()->id }}" />
<input type="hidden" name="follow_id" value="{{ $user->id }}" />
<button id="follow-button" class="btn">フォローする</button>
</form>
@else
<div>フォローしています。</div>
<form id="form-unfollow" action="{{ route('unfollow') }}" method="post">
@csrf
<input type="hidden" name="user_id" value="{{ \Auth::user()->id }}" />
<input type="hidden" name="follow_id" value="{{ $user->id }}" />
<button id="unfollow-button" class="btn">フォロー解除する</button>
</form>
@endif
@endif
</div>
<div class="p-6 text-gray-900">
@foreach($tweets as $tweet)
<div class="tweet">
<div><a href="{{ route('profile', $tweet->user->name) }}">{{ $tweet->user->name }}</a></div>
<div>{!! nl2br($tweet->text) !!}</div>
<div>{{ $tweet->created_at }}</div>
</div>
@endforeach
</div>
フォロー中とフォロワーの一覧を表示する
プロフィールページにフォロー中とフォロワーのボタンを追加して、それぞれの一覧を表示できるようにします。
<div class="p-6 text-gray-900">
@if(empty($profile))
<h2>{{ $user->name }}さん</h2>
<div>表示名と自己紹介は未設定です。</div>
@else
<h2>{{ $profile->name }}さん ({{ $user->name }})</h2>
<div>{!! nl2br($profile->description) !!}</div>
@endif
<div>
<a href="{{ route('follows') }}" id="btn-follows">フォロー中</a>
<a href="{{ route('followers') }}" id="btn-followers">フォロワー</a>
</div>
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TweetController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', [TweetController::class, 'index'])->middleware('auth')->name('dashboard');
Route::post('/tweet', [TweetController::class, 'tweet'])->middleware('auth')->name('tweet');
Route::get('/prof/{name}', [TweetController::class, 'profile'])->middleware('auth')->name('profile');
Route::post('/updateProfile', [TweetController::class, 'updateProfile'])->middleware('auth')->name('updateProfile');
Route::post('/follow', [TweetController::class, 'follow'])->middleware('auth')->name('follow');
Route::post('/unfollow', [TweetController::class, 'unfollow'])->middleware('auth')->name('unfollow');
Route::get('/follows', [TweetController::class, 'follows'])->middleware('auth')->name('follows');
Route::get('/followers', [TweetController::class, 'followers'])->middleware('auth')->name('followers');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';
TweetControllerにfollows()を追加します。
public function follows()
{
$user = \Auth::user();
$follows = Follow::where('user_id', $user->id)->orderBy('created_at', 'desc')->get();
return view('follows', compact('follows'));
}
follows.blade.php を作成します。
> php artisan make:view follows
follows.blade.php では、とりあえずフォローしている人のIDだけを表示してみます。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('フォロー中') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@foreach($follows as $follow)
<div class="">
<div>{{ $follow->follow_id }}</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-app-layout>
Followクラスからフォロー中のUserを取得できるようにします。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
class Follow extends Model
{
protected $fillable = ['user_id', 'follow_id'];
public function user()
{
return $this->belongsTo(User::class);
}
public function follow()
{
return $this->hasOne(User::class, 'id', 'follow_id');
}
}
follows.blade.php で、ユーザー名を表示するように変更します。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('フォロー中') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@foreach($follows as $follow)
<div class="">
<div>{{ $follow->follow->name }}</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-app-layout>
同様にして、フォロワーの一覧を表示します。
TweetControllerにfollowers()メソッドを追加します。
public function followers()
{
$user = \Auth::user();
$followers = Follow::where('follow_id', $user->id)->orderBy('created_at', 'desc')->get();
return view('followers', compact('followers'));
}
followers.blade.php を作成します。
> php artisan make:view followers
followers.blade.php の内容は follows.blade.php をコピーして、必要な部分だけ変更すると楽です。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('フォロワー') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@foreach($followers as $follower)
<div class="">
<div>{{ $follower->user->name }}</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-app-layout>