ソースコードのバージョン管理にgitを使用する
Laravelのbreezeまでインストールしたら、ソースコードをgitで管理するようにしましょう。
まず、以下のコマンドでユーザー名とメールアドレスをgitに登録します。
> git config --global user.name "SAWADA Satoshi" > git config --global user.email satoshis@example.com
VSCodeの画面の左側の「Source Control」のアイコンをクリックします。
100個以上のファイルが並んでいますが、これらはLaravelとBreezeのインストールにより作られたファイルです。
Messageのところに「first commit」と入力して青いCommitボタンを押します。
これでソースコードがgitに登録されます。
次にBacklogとの連携を作ります。
Backlogにアカウントを作成してください。
以下のリンクからBacklogのサイトを表示し、フリープランから申し込みます。

スペース名は「aiwh#00##」とします。最初の # にはクラスの番号を、あとの ## には学籍番号の下2桁を指定してください。
Backlogにログインしたら、右上のアイコンをクリックしてメニューを表示し「個人設定」を選択します。
左のメニューから「メール設定」を選択し「通知とレポートを受信する」のチェックを外して「保存」します。
右上のアイコンから「スペース設定」を選択し「ユーザー」を選択します。
ユーザーの一覧画面で「ユーザー追加」をクリックすると「メンバー」ウィンドウが開くので、「管理者」をクリックして「次へ」をクリックします。
招待するメンバーのメールアドレスを入力するところで「satoshis@gmail.com」を入力して「管理者を1人招待する」ボタンをクリックします。
すると「1人のユーザーを招待しました。」と表示されるので、「参加させるプロジェクトを選択してください。」のところで「messe」にチェックを入れて「登録」をクリックします。
BacklogとVSCodeの連携
次は、BacklogのgitリポジトリとVSCodeを連携させます。
Backlogの画面でプロジェクト「messe」を選択します。
左のメニューで「プロジェクト設定」を選択し、右側の画面を少し下にスクロールして「Gitを使用する」にチェックをして「保存」をクリックします。
「保存」クリック後の画面でグレーのメニューの「Git」を選択します。
「リポジトリを追加する」をクリックし「リポジトリ名」に「messe」と入力し「リポジトリを作成する」をクリックします。
HTTPSの右にURLが表示していて、さらにその右のボタンをクリックするとURLがコピーされます。
VSCodeで「Source Control」を選んで右上の「…」をクリックするとメニューが表示されるので、[Remote]-[Add Remote…]を選択します。そこにコピーしたURLを貼り付けてEnterを押します。
さらにリモート名の入力を求められるので「origin」と入力してEnterします。
すると次のような画面が表示されるので、Backlogに登録したユーザー名とパスワードを入力します。
Backlogで課題を作成する
Backlogの画面で新しく課題を作成します。
まずは自分宛のメッセージを送れるようにする機能を追加していきます。
課題の件名に「メッセージを送信できるようにする」を入力し、担当者に自分を割り当てて「追加」をクリックします。
次にVSCodeで resources/views/dashboard.blade.php を編集します。
<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">
<form action="{{ route('post') }}" method="post">
@csrf
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
</div>
</div>
</div>
この変更内容をgitに登録します。
Backlogの課題のページで緑のアイコンをクリックします。
VSCodeのSOURCE CONTROLで、青いCommitの上の入力フィールドにコピーした内容を貼り付けます。
貼り付けたら青いCommitをクリックします。
青いボタンの表示がSync Changesに変化するので、これをクリックします。
すると、変更内容がBacklogのgitに送信されます。
Backlogの課題を画面を再読み込みすると、コミットが追加されているのがわかります。
コミットの右側の緑色のリンクをクリックすると、そのコミットによる変更内容が表示されます。
ルーティングを定義する
dashboard.blade.php 内に以下のコードを追加したので、このままではエラーが発生します。
<form action="{{ route('post') }}" method="post">
routes/web.php にルーティングを定義して、エラーを解消します。
<?php
use App\Http\Controllers\MessageController;
use App\Http\Controllers\ProfileController;
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('/post', [MessageController::class, 'post'])->name('post');
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';
コントローラーを作成する
メッセージを送信する画面はできたので、次はサーバー側で入力を受け取るためのコントローラーを作成します。
ターミナルで以下のコマンドを実行します。
> php artisan make:controller MessageController
app\Http\Controllers\MessageController.php が作られるので、これを修正します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MessageController extends Controller
{
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
return redirect()->route('dashboard');
}
}
\Log::debug($msg); でログに出力した内容は、storage\logs\laravel.log ファイルを参照することで確認できます。
モデルを作成してメッセージをデータベースに保存する
以下のコマンドでメッセージを保存するためのモデルを作成します。
> php artisan make:model Message -m
app\Models\Message.php を変更します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
protected $fillable = [
'sender',
'recipient',
'text',
];
}
マイグレーションファイルも変更します。
database\migrations\2024_12_15_hhmmss_create_messages_table.php
<?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('messages', function (Blueprint $table) {
$table->id();
$table->bigInteger('sender');
$table->bigInteger('recipient');
$table->text('text');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('messages');
}
};
マイグレーションを実行します。
> php artisan migrate
resources/views/dashboard.blade.php から宛先を受け取る必要があるので、追加します。
送信者はログインしているユーザーのIDでいいのですが、宛先は明示的に指定する必要があります。
ただし、ここでは自分宛なのでログインしているユーザーのIDを宛先にしていします。
<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">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ \Auth::user()->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
</div>
</div>
</div>
コントローラーでは、受け取ったメッセージを保存する処理を追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Message;
class MessageController extends Controller
{
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
return redirect()->route('dashboard');
}
}
ユーザー一覧のページを作成する
メッセージを送信しあうには友達登録のような仕組みが必要です。
Backlogで課題「ユーザー一覧のページを作成する」を作成します。
LINEの場合はセキュリティの関係で簡単には探せないようになっていますが、ここではユーザー一覧のページを用意して、システムに登録しているユーザー全員を表示するページから探せるようにします。
以下のコマンドでviewを作成します。
> php artisan make:view userlist
ユーザー一覧のページの内容は以下のようにします。
<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($users as $u)
<div>{{ $u->name }}</div>
@endforeach
</div>
</div>
</div>
</div>
</x-app-layout>
ルーティングを定義します。
routes/web.php
<?php
use App\Http\Controllers\MessageController;
use App\Http\Controllers\ProfileController;
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('/post', [MessageController::class, 'post'])->name('post');
Route::get('/users', [MessageController::class, 'users'])->name('users');
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';
コントローラーに users() メソッドを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Message;
use App\Models\User;
class MessageController extends Controller
{
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
return redirect()->route('dashboard');
}
public function users()
{
$users = User::orderBy('created_at', 'desc')->get();
return view('userlist', compact('users'));
}
}
dashboard.blade.php に、ユーザー一覧のページへのリンクを追加します。
<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">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ \Auth::user()->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
動作確認ができたら、コミットして同期します。
Backlogの課題のページにコミットが追加されます。
ユーザー一覧で各ユーザーの状態を表示する
現在のユーザー一覧ページでは、表示されているのが自分なのか、友達なのか、友達になっていないのかがわかりません。
自分だったら「あなたです」と表示するようにします。
友達申請の仕組みがないので、友達はひとりもいない状態なので、自分以外には「ともだち申請」のボタンを表示するようにします。
まずはBacklogで課題「ユーザー一覧で各ユーザーの状態を表示する」を作成します。
userlist.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($users as $u)
<div>
{{ $u->name }}
@if(\Auth::user()->id == $u->id)
あなたです!
@else
<button>ともだち申請</button>
@endif
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-app-layout>
ともだち申請のボタンがボタンっぽくないのでCSSで少しデザインを調整します。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
{{ $u->name }}
@if(\Auth::user()->id == $u->id)
あなたです!
@else
<button class="friend-button">ともだち申請</button>
@endif
</div>
@endforeach
</div>
CSSは layouts/app.blade.php に記述します。
<!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>
.username-div {
margin: 2px;
}
.friend-button {
font-size: small;
background-color: #b9f9f3;
border-radius: 5px;
padding: 3px 5px;
}
</style>
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
ボタンを form タグ内に配置して、誰に対してともだち申請するのかをわかるようにします。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
@if(\Auth::user()->id == $u->id)
{{ $u->name }}
あなたです!
@else
<form action="{{ route('friend-request') }}" method="post">
@csrf
{{ $u->name }}
<input type="hidden" name="id" value="{{ $u->id }}" />
<button class="friend-button">ともだち申請</button>
</form>
@endif
</div>
@endforeach
</div>
action属性に対応するルーティングを定義します。
routes/web.php
<?php
use App\Http\Controllers\MessageController;
use App\Http\Controllers\ProfileController;
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('/post', [MessageController::class, 'post'])->name('post');
Route::get('/users', [MessageController::class, 'users'])->name('users');
Route::post('/friend-request', [MessageController::class, 'friendRequest'])->name('friend-request');
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';
ともだち申請のボタンが押されたら、何もせずに元のページにリダイレクトするように、MessageController.php を修正しておきます。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Message;
use App\Models\User;
class MessageController extends Controller
{
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
return redirect()->route('dashboard');
}
public function users()
{
$users = User::orderBy('created_at', 'desc')->get();
return view('userlist', compact('users'));
}
public function friendRequest(Request $request)
{
return redirect()->route('users');
}
}
ともだち申請のリクエストをデータベースに保存するようにします。
FriendRequestモデルを作成します。
> php artisan make:model FriendRequest -m
FriendRequestモデルは、申請を送信した人と誰に申請したのかという情報があればよさそうなので、以下のように修正します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class FriendRequest extends Model
{
protected $fillable = [
'sender',
'recipient',
];
}
2024_12_13_hhmmss_create_friend_requests_table.php
<?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('friend_requests', function (Blueprint $table) {
$table->id();
$table->bigInteger('sender');
$table->bigInteger('recipient');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('friend_requests');
}
};
> php artisan migrate
コントローラーでともだち申請をデータベースに保存するように修正します。
MessageController
public function friendRequest(Request $request)
{
$sender = \Auth::user()->id;
$recipient = $request->input('id');
FriendRequest::create([
'sender' => $sender,
'recipient' => $recipient,
]);
return redirect()->route('users');
}
UserクラスでFriendRequestを取得できるようにします。
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function friendRequest($recipient)
{
return FriendRequest::where('sender', $this->id)->where('recipient', $recipient)->first();
}
}
userlist.blade.php では、ともだち申請済の人は「ともだち申請中」と表示するようにします。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
@if(\Auth::user()->id == $u->id)
{{ $u->name }}
あなたです!
@else
@if(empty(\Auth::user()->friendRequest($u->id)))
<form action="{{ route('friend-request') }}" method="post">
@csrf
{{ $u->name }}
<input type="hidden" name="id" value="{{ $u->id }}" />
<button class="friend-button">ともだち申請</button>
</form>
@else
{{ $u->name }}
<span class="wait-accept">ともだち申請中</span>
@endif
@endif
</div>
@endforeach
</div>
ともだち申請を承認するボタンを追加します。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
@if(\Auth::user()->id == $u->id)
{{ $u->name }}
あなたです!
@else
@if(empty(\Auth::user()->friendRequest($u->id)))
@if(empty(\Auth::user()->requestReceived($u->id)))
<form action="{{ route('friend-request') }}" method="post">
@csrf
{{ $u->name }}
<input type="hidden" name="id" value="{{ $u->id }}" />
<button class="friend-button">ともだち申請</button>
</form>
@else
<form action="" method="post">
@csrf
{{ $u->name}}
<span class="request-received">ともだち申請を受け取りました</span>
<button class="friend-button">承認する</button>
</form>
@endif
@else
{{ $u->name }}
<span class="wait-accept">ともだち申請中</span>
@endif
@endif
</div>
@endforeach
</div>
ともだちを承認するためのルーティングを用意する。
<?php
use App\Http\Controllers\MessageController;
use App\Http\Controllers\ProfileController;
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('/post', [MessageController::class, 'post'])->name('post');
Route::get('/users', [MessageController::class, 'users'])->name('users');
Route::post('/friend-request', [MessageController::class, 'friendRequest'])->name('friend-request');
Route::post('/request-accept', [MessageController::class, 'acceptRequest'])->name('request-accept');
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';
userlist.blade.php にルーティングを記入する。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
@if(\Auth::user()->id == $u->id)
{{ $u->name }}
あなたです!
@else
@if(empty(\Auth::user()->friendRequest($u->id)))
@if(empty(\Auth::user()->requestReceived($u->id)))
<form action="{{ route('friend-request') }}" method="post">
@csrf
{{ $u->name }}
<input type="hidden" name="id" value="{{ $u->id }}" />
<button class="friend-button">ともだち申請</button>
</form>
@else
<form action="{{ route('request-accept') }}" method="post">
@csrf
{{ $u->name}}
<span class="request-received">ともだち申請を受け取りました</span>
<button class="friend-button">承認する</button>
</form>
@endif
@else
{{ $u->name }}
<span class="wait-accept">ともだち申請中</span>
@endif
@endif
</div>
@endforeach
</div>
ともだち申請が承認されたかどうかのフラグを friend_requests テーブルに追加します。
> php artisan make:migration add_accept_to_friend_requests --table friend_requests
マイグレーションファイルが作成されるので編集します。
<?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::table('friend_requests', function (Blueprint $table) {
$table->boolean('accepted')->after('recipient');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('friend_requests', function (Blueprint $table) {
$table->dropColumn('accepted');
});
}
};
マイグレーションファイルを保存して、マイグレーションを実行します。
> php artisan migrate
MessageController.php で、ともだち承認のリクエストを受け取って、承認状態に変更する。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\FriendRequest;
use App\Models\Message;
use App\Models\User;
class MessageController extends Controller
{
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
return redirect()->route('dashboard');
}
public function users()
{
$users = User::orderBy('created_at', 'desc')->get();
return view('userlist', compact('users'));
}
public function friendRequest(Request $request)
{
$sender = \Auth::user()->id;
$recipient = $request->input('id');
FriendRequest::create([
'sender' => $sender,
'recipient' => $recipient,
'accepted' => false,
]);
return redirect()->route('users');
}
public function acceptRequest(Request $request)
{
$sender = $request->input('id');
$recipient = \Auth::user()->id;
$fr = FriendRequest::where('sender', $sender)->where('recipient', $recipient)->first();
$fr->accepted = true;
$fr->save();
return redirect()->route('users');
}
}
User.php に、isFriend()を追加して、承認済みのともだちかどうかを判定できるようにする。
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Models\FriendRequest;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function friendRequest($recipient)
{
return FriendRequest::where('sender', $this->id)->where('recipient', $recipient)->first();
}
public function requestReceived($sender)
{
return FriendRequest::where('sender', $sender)->where('recipient', $this->id)->first();
}
public function isFriend($id)
{
$fr = FriendRequest::where('sender', $this->id)->where('recipient', $id)->first();
if (!empty($fr) && $fr->accepted == true) return true;
$fr = FriendRequest::where('sender', $id)->where('recipient', $this->id)->first();
if (!empty($fr) && $fr->accepted == true) return true;
return false;
}
}
userlist.blade.php で、承認済みのともだちは「ともだち」と表示する。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
@if(\Auth::user()->id == $u->id)
{{ $u->name }}
あなたです!
@else
@if(\Auth::user()->isFriend($u->id))
{{ $u->name }} ともだち
@else
@if(empty(\Auth::user()->friendRequest($u->id)))
@if(empty(\Auth::user()->requestReceived($u->id)))
<form action="{{ route('friend-request') }}" method="post">
@csrf
{{ $u->name }}
<input type="hidden" name="id" value="{{ $u->id }}" />
<button class="friend-button">ともだち申請</button>
</form>
@else
<form action="{{ route('request-accept') }}" method="post">
@csrf
{{ $u->name}}
<input type="hidden" name="id" value="{{ $u->id }}" />
<span class="request-received">ともだち申請を受け取りました</span>
<button class="friend-button">承認する</button>
</form>
@endif
@else
{{ $u->name }}
<span class="wait-accept">ともだち申請中</span>
@endif
@endif
@endif
</div>
@endforeach
</div>
メッセージを表示できるようにする
まずはダッシュボードに自分あてのメッセージを表示できるようにします。
web.php のダッシュボードを表示する部分を、MessageController経由で動作するように修正します。
もとから定義していた以下の部分は削除します。
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
かわりに以下の記述を追加します。
Route::get('/dashboard', [MessageController::class, 'dashboard'])->name('dashboard');
MessageController.phpに dashboard()メソッドを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\FriendRequest;
use App\Models\Message;
use App\Models\User;
class MessageController extends Controller
{
public function dashboard()
{
$id = \Auth::user()->id;
$messages = Message::where('sender', $id)
->where('recipient', $id)
->orderBy('created_at', 'desc')
->get();
return view('dashboard', compact('messages'));
}
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
return redirect()->route('dashboard');
}
(以下略)
dashboard.blade.php に、メッセージを表示する処理を追加します。
<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($messages as $m)
<div>
{!! nl2br($m->text) !!}
</div>
<div>
{{ $m->created_at }}
</div>
@endforeach
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ \Auth::user()->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
ともだちとのトーク画面を作る
ダッシュボードでは自分あてのメッセージを表示できるようになりました。
次はともだちとのトーク画面を作って、ともだちとメッセージをやりとりできるようにしましょう。
ルーティングから作成します。
<?php
use App\Http\Controllers\MessageController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', [MessageController::class, 'dashboard'])->name('dashboard');
Route::post('/post', [MessageController::class, 'post'])->name('post');
Route::get('/users', [MessageController::class, 'users'])->name('users');
Route::post('/friend-request', [MessageController::class, 'friendRequest'])->name('friend-request');
Route::post('/request-accept', [MessageController::class, 'acceptRequest'])->name('request-accept');
Route::get('/talk/{id}', [MessageController::class, 'talk'])->name('talk');
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';
次に userlist.blade.php の名前の部分をリンクに変更します。
<div class="p-6 text-gray-900">
@foreach($users as $u)
<div class="username-div">
@if(\Auth::user()->id == $u->id)
{{ $u->name }}
あなたです!
@else
@if(\Auth::user()->isFriend($u->id))
<a href="{{ route('talk', $u->id) }}">{{ $u->name }}</a> ともだち
@else
@if(empty(\Auth::user()->friendRequest($u->id)))
@if(empty(\Auth::user()->requestReceived($u->id)))
<form action="{{ route('friend-request') }}" method="post">
@csrf
{{ $u->name }}
<input type="hidden" name="id" value="{{ $u->id }}" />
<button class="friend-button">ともだち申請</button>
</form>
@else
MessageController.phpにtalk()メソッドを追加します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\FriendRequest;
use App\Models\Message;
use App\Models\User;
class MessageController extends Controller
{
public function dashboard()
{
$id = \Auth::user()->id;
$messages = Message::where('sender', $id)
->where('recipient', $id)
->orderBy('created_at', 'desc')
->get();
return view('dashboard', compact('messages'));
}
public function post(Request $request)
{
$msg = $request->input('message');
\Log::debug($msg);
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
return redirect()->route('dashboard');
}
public function users()
{
$users = User::orderBy('created_at', 'desc')->get();
return view('userlist', compact('users'));
}
public function friendRequest(Request $request)
{
$sender = \Auth::user()->id;
$recipient = $request->input('id');
FriendRequest::create([
'sender' => $sender,
'recipient' => $recipient,
'accepted' => false,
]);
return redirect()->route('users');
}
public function acceptRequest(Request $request)
{
$sender = $request->input('id');
$recipient = \Auth::user()->id;
$fr = FriendRequest::where('sender', $sender)->where('recipient', $recipient)->first();
$fr->accepted = true;
$fr->save();
return redirect()->route('users');
}
public function talk($id)
{
return view('talk');
}
}
talk.blade.php を作成します。
> php artisan make:view talk
talk.blade.php を作成すると、エラーを表示しないでトーク画面を表示できるようになりました。
トーク画面を、layout/app.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>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
</x-app-layout>
トーク相手の名前をトーク画面に表示します。
そのために、MessageController.php の talk() メソッドで、トークの相手の情報を取得して、talk.blade.php に渡すようにします。
{
$me = \Auth::user();
$partner = User::find($id);
return view('talk', compact('me', 'partner'));
}
変数 partner にトーク相手の User が渡されるので、これをもとにトーク相手の名前を表示します。
<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>
{{ $partner->name }}さんとのトーク
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
トーク画面にメッセージを送信するためのフォームを追加します。
dashboard.blade.php にあるものをコピーして使います。
<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>
{{ $partner->name }}さんとのトーク
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ $partner->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
メッセージを送信すると、ダッシュボード画面に遷移してしまいました。
これは MessageController.php の post() メソッドが固定的にダッシュボードに遷移させているためです。
メッセージの送信者と受信者が同一の場合はダッシュボードに遷移し、それ以外はトーク画面に遷移するように修正します。
public function post(Request $request)
{
$msg = $request->input('message');
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
if ($sender == $recipient) {
return redirect()->route('dashboard');
}
return redirect()->route('talk', $recipient);
}
トーク画面にメッセージを表示します。
これもダッシュボードにあったものをコピーしましょう。
<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>
{{ $partner->name }}さんとのトーク
</div>
<div class="p-6 text-gray-900">
@foreach($messages as $m)
<div class="border">
<div>
{!! nl2br($m->text) !!}
</div>
<div>
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ $partner->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
MessageController.php から送受信したメッセージの一覧を talk.blade.php に渡します。
自分がトーク相手に送信したメッセージと、トーク相手が自分宛に送信したメッセージを検索する必要があるので、ちょっと複雑なクエリを実行することになります。
public function talk($id)
{
$me = \Auth::user();
$partner = User::find($id);
$messages = Message::where(function($query) use($me, $partner) {
$query->where('sender', $partner->id)
->where('recipient', $me->id);
})->orWhere(function($query) use($me, $partner) {
$query->where('sender', $me->id)
->where('recipient', $partner->id);
})->get();
return view('talk', compact('me', 'partner', 'messages'));
}
これでトーク画面にメッセージが表示されました。
しかし、自分が送信したメッセージなのか相手から送られたメッセージなのか区別がつきません。
自分が送信したメッセージをCSSで右寄せにしてみましょう。
@if~@endif を使用して、自分が送信したメッセージに右寄せのクラス text-right を追加します。
<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>
{{ $partner->name }}さんとのトーク
</div>
<div class="p-6 text-gray-900">
@foreach($messages as $m)
<div class="border @if($m->sender == \Auth::user()->id)text-right @endif">
<div>
{!! nl2br($m->text) !!}
</div>
<div>
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ $partner->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
LINEの見た目に少し近づけてみましょう。
以下の内容をCSSで調整します。
- メッセージや日時をボーダーから少し離す
- 日時のフォントサイズを小さくする
- 自分が送信したメッセージの背景色を緑にする
layout/app.blade.php にクラスを定義します。
<!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>
.username-div {
margin: 2px;
}
.friend-button {
font-size: small;
background-color: #b9f9f3;
border-radius: 5px;
padding: 3px 5px;
}
.border {
border: 1px solid gray;
border-radius: 5px;
margin-bottom: 2px;
}
.text-right {
text-align: right;
}
.pd5 {
padding: 5px;
}
.text-xs {
font-size: x-small;
}
.bg-green {
background-color: #80ff80;
}
</style>
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100">
@include('layouts.navigation')
<!-- Page Heading -->
@isset($header)
<header class="bg-white shadow">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{{ $header }}
</div>
</header>
@endisset
<!-- Page Content -->
<main>
{{ $slot }}
</main>
</div>
</body>
</html>
talk.blade.php に、app.blade.php で追加したクラスを反映させます。
<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>
{{ $partner->name }}さんとのトーク
</div>
<div class="p-6 text-gray-900">
@foreach($messages as $m)
<div class="border pd5 @if($m->sender == \Auth::user()->id)text-right bg-green @endif">
<div>
{!! nl2br($m->text) !!}
</div>
<div class="text-xs">
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post">
@csrf
<input type="hidden" name="recipient" value="{{ $partner->id }}" />
<textarea name="message" required></textarea>
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
画像を送信できるようにする
トーク画面では文字だけでなく画像を送信できるようにした方がコミュニケーションを楽しめるでしょう。
トーク画面で画像を送信できるようにしてみましょう。
まずはトーク画面のビューである talk.blade.php で画像を送信できるようにします。
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post" enctype="multipart/form-data">
@csrf
<input type="hidden" name="recipient" value="{{ $partner->id }}" />
<textarea name="message" required></textarea>
<br />
<input type="file" name="image">
<br />
<button>送信する</button>
</form>
</div>
MessageController.php で送信されてきた画像ファイルを受け取ってサーバー上に保存する。
念のため、24行目にLog::debug()を置いて、受信したデータをログに記録してみます。
出力内容は、storage/logs/laravel.log ファイルに記録されます。
public function post(Request $request)
{
\Log::debug($request);
$msg = $request->input('message');
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
$dir = 'images';
if (!empty($request->file('image'))) {
$request->file('image')->store('public/'.$dir);
}
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
]);
if ($sender == $recipient) {
return redirect()->route('dashboard');
}
return redirect()->route('talk', $recipient);
}
30行目で画像ファイルを保存しています。
保存したファイルは、storage/app/public/image に保存されます。
ファイルのパスを Message に保存する
現状では画像ファイルを保存はできていますが、それを参照する方法がありません。
データベースに画像ファイルのパスを保存できるように、Messageモデルにカラムを追加します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
protected $fillable = [
'sender',
'recipient',
'text',
'image',
];
}
データベースの messages テーブルに image カラムを追加するためのマイグレーションファイルを作成します。
> php artisan make:migration add_image_to_messages --table=messages
マイグレーションファイルの内容は以下の通りです。
<?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::table('messages', function (Blueprint $table) {
$table->string('image')->after('text')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('messages', function (Blueprint $table) {
$table->dropColumn('image');
});
}
};
マイグレーションを実行します。
> php artisan migrate
これで messages テーブルに image カラムが追加されました。
次は MessageController.php に、保存した画像のパスを Message に保存する処理を追加します。
public function post(Request $request)
{
\Log::debug($request);
$msg = $request->input('message');
$sender = \Auth::user()->id;
$recipient = $request->input('recipient');
$dir = 'images';
if (!empty($request->file('image'))) {
$path = $request->file('image')->store('public/'.$dir);
}
Message::create([
'sender' => $sender,
'recipient' => $recipient,
'text' => $msg,
'image' => $path,
]);
if ($sender == $recipient) {
return redirect()->route('dashboard');
}
return redirect()->route('talk', $recipient);
}
画像ファイルをアップロードすると、なぜか storage/app/private フォルダに保存されれてしまいます。
これは Laravel11 からデフォルトの保存先が変更されているためでした。
config/filesystems.php ファイルを変更します。
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'serve' => true,
'throw' => false,
],
設定ファイルを変更したので、Laravelを再起動し、画像をアップロードしなおしてみましょう。
画像ファイルは storage/app/public/images フォルダにアップロードされるようになりました。
このままではブラウザ上に画像を表示できないので、以下のコマンドを実行します。
> php artisan storage:link
このコマンドにより、public フォルダ内に storage/app/public フォルダへのショートカットが作られました。
Laravelでは public フォルダはブラウザからアクセス可能なフォルダなので、ブラウザ上でも表示できるようになりました。
talk.blade.php に、画像を表示するための要素を追加します。
<div class="p-6 text-gray-900">
@foreach($messages as $m)
<div class="border pd5 @if($m->sender == \Auth::user()->id)text-right bg-green @endif">
<div>
{!! nl2br($m->text) !!}
</div>
@if(!empty($m->image))
<div>
<img src="/storage/{{ $m->image }}" />
</div>
@endif
<div class="text-xs">
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>
この変更により、送信した画像がブラウザ上に表示できるようになりました。
自分が送信した画像を右寄せで表示する
すべての画像が左寄せで表示されているので、自分が送信した画像も、相手から送られたように見えてしまいます。
これを解消するために、自分が送信した画像を右寄せで表示するように変更します。
テンプレートファイル layout/app.blade.php に、画像を右寄せするためのCSSクラスを追加します。
<style>
.username-div {
margin: 2px;
}
.friend-button {
font-size: small;
background-color: #b9f9f3;
border-radius: 5px;
padding: 3px 5px;
}
.border {
border: 1px solid gray;
border-radius: 5px;
margin-bottom: 2px;
}
.text-right {
text-align: right;
}
.pd5 {
padding: 5px;
}
.text-xs {
font-size: x-small;
}
.bg-green {
background-color: #80ff80;
}
.img-right {
margin: 0 0 0 auto;
}
</style>
talk.blade.php では、自分が送信した画像にだけ、img-right クラスを適用します。
<div class="p-6 text-gray-900">
@foreach($messages as $m)
<div class="border pd5 @if($m->sender == \Auth::user()->id)text-right bg-green @endif">
<div>
{!! nl2br($m->text) !!}
</div>
@if(!empty($m->image))
<div>
<img src="/storage/{{ $m->image }}" @if($m->sender == \Auth::user()->id) class="img-right" @endif />
</div>
@endif
<div class="text-xs">
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>
ダッシュボードでも画像を送信できるようにする
現状ではトーク画面では画像を送信できますが、自分あてメッセージを送信可能なダッシュボードでは画像を送信できません。
ダッシュボードでも画像を送信できるように修正してみましょう。
トーク画面を見ながら画像送信と表示に関する部分を、ダッシュボードにも適用するだけです。
<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($messages as $m)
<div class="border pd5 text-right bg-green">
<div>
{!! nl2br($m->text) !!}
</div>
@if(!empty($m->image))
<div>
<img src="/storage/{{ $m->image }}" @if($m->sender == \Auth::user()->id) class="img-right" @endif />
</div>
@endif
<div>
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>
<div class="p-6 text-gray-900">
<form action="{{ route('post') }}" method="post" enctype="multipart/form-data">
@csrf
<input type="hidden" name="recipient" value="{{ \Auth::user()->id }}" />
<textarea name="message" required></textarea>
<br />
<input type="file" name="image">
<br />
<button>送信する</button>
</form>
</div>
<div>
<a href="{{ route('users') }}">ユーザー一覧</a>
</div>
</div>
</div>
</div>
「送信する」ボタンのCSSを調整する
現状の表示では「送信する」がボタンに見えないため、デザイン的によくないですのでボタンに見えるようにCSSを調整しましょう。
メッセージ内のURLをリンクに変換する
メッセージ内にURLがあった場合は自動的にリンクに変換する機能を追加しましょう。
Messageクラスにメソッドを追加します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
protected $fillable = [
'sender',
'recipient',
'text',
'image',
];
public function getAnchoredText()
{
$s = $this->text;
$pattern = '/^https?:\/\/[^\s \\\|`^"\'(){}<>\[\]]*$/';
$linked = preg_replace($pattern, '<a href="$0" target="_blank">$0</a>', $s);
return $linked;
}
}
talk.blade.php 内でメッセージのテキストを出力している部分を、新しいメソッドの出力に置き換えます。
<div class="p-6 text-gray-900">
@foreach($messages as $m)
<div class="border pd5 @if($m->sender == \Auth::user()->id)text-right bg-green @endif">
<div>
{!! nl2br($m->getAnchoredText()) !!}
</div>
@if(!empty($m->image))
<div>
<img src="/storage/{{ $m->image }}" @if($m->sender == \Auth::user()->id) class="img-right" @endif />
</div>
@endif
<div class="text-xs">
{{ $m->created_at }}
</div>
</div>
@endforeach
</div>





