実務未経験エンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その18)
0. 初めに
こんにちは!
このシリーズでは、実務未経験の僕が頑張ってWebアプリケーションを開発する方法を解説しております!
今回は、マイページ機能を作成していきたいと思います!
是非最後までお付き合いのほどよろしくお願いいたします。(>_<)
今回実装する内容
今回は以下の順で実装していきたいと思います。
- ユーザー情報表示
- ユーザー情報更新
- 退会
- ブックマーク済み研究室表示
- ブックマーク解除
1. ブランチ運用
いつも通り、develop ブランチを最新化して、今回は feature/16-mypage というブランチを切って作業します。
作業が終わったら、コミット・プッシュ、プルリクエスト作成・マージを忘れずに行いましょう。
2. ユーザー情報表示
まずは、ログインしているユーザー情報を表示できるようにしましょう!
コントローラーを作成する
コントローラーを作成しましょう。
実行コマンド
$ php artisan make:controller MyPageController
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Inertia\Inertia;
class MyPageController extends Controller
{
public function showUser()
{
$user = Auth::user();
return Inertia::render('MyPage/Index', [
'user' => $user,
]);
}
}
Reactコンポーネントを作成する
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
export default function Index({ user }) {
return (
<AuthenticatedLayout>
<Head title="マイページ" />
<div>
<h3>ユーザー情報</h3>
<p>名前: {user.name}</p>
<p>メールアドレス: {user.email}</p>
<p>登録日: {new Date(user.created_at).toLocaleDateString('ja-JP')}</p>
</div>
</AuthenticatedLayout>
);
}
ルーティングを追加する
auth ミドルウェアの中に追加!
// マイページ関連
Route::get('/mypage', [MyPageController::class, 'showUser'])->name('mypage.index');
動作確認をする
適当なユーザーでログインして、以下にアクセス。
http://localhost/mypage
ユーザー情報が表示できていれば成功!
これにて、ユーザー情報表示は完成とします!
3. ユーザー情報更新
次にユーザー情報更新機能を作りましょう。
コントローラー修正
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Inertia\Inertia;
class MyPageController extends Controller
{
public function showUser()
{
$user = Auth::user();
return Inertia::render('MyPage/Index', [
'user' => $user,
]);
}
// 追加
public function editUser(Request $request)
{
$user = Auth::user();
return Inertia::render('MyPage/Edit', [
'user' => $user,
'errors' => $request->session()->get('errors'),
]);
}
// 追加
public function updateUser(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . Auth::id(),
]);
/** @var User $user */
$user = Auth::user();
$user->name = $request->name;
$user->email = $request->email;
$user->save();
return redirect()->route('mypage.index')->with('success', 'ユーザー情報を更新しました');
}
}
※ /** @var User $user */ って何?(読み飛ばしてもらっても構いません)
見慣れない一行があると思います。
これは、PHPDocコメントと呼ばれるもので、要は特別なコメントアウトの仕方ですね。
コメントアウトの用途として、「開発者のメモ用」、「一時的に処理を止めたい場合」などがあると思います。
一方で、「関数の使い方」みたいなのを定義の上にコメントアウトとして書いておくことで、チームで開発するときに他の開発者が使いやすくなるというメリットもあります。
その際、書き方が統一されていると読みやすいと思います。
PHPDocコメントもそういう用途だと思います(多分w)。
今回の場合は、@var というのを付けることで、以前このシリーズで何度か登場していたタイプヒントを付けることができるらしいです。
使い方としては、以下のような感じです。
/** @var 型名 $変数名 */
なぜ今回これを急に付けたかと言いますと、phpには解析ツールが内蔵されており、型に対して適切な補完やエラーチェックを自動でしてくれるらしいのですが、それがなぜかはたらいて僕の環境だとVS Code上で警告を出していました。
結論として、構文のエラーではないし、特殊なコメントアウトとはいえただのコメントアウトなので、/** @var User $user */ はなくても動作に影響は出ません。
ただ警告がうっとうしかったので消すために付けただけなので、ぶっちゃけこの話は忘れてもらってもかまいません。
ちなみに、結構自信なさげに書いていますが、PHPDocコメントというものを全く知らなかったためです。
お恥ずかしい限り。(._.)
Javaでは、こういうコメントアウト見たことあったんですけどねぇ~。
PHPDocコメントについてまとめてくれている人がいるみたいなので、余裕がある方は読んでみてください。
PHPDocコメント まとめ
Reactコンポーネントを作成する
編集用ページを作ります。
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, useForm, Link } from '@inertiajs/react';
export default function Edit({ user }) {
const { data, setData, put, processing, errors } = useForm({
name: user.name,
email: user.email,
});
const handleSubmit = (e) => {
e.preventDefault();
put(route('mypage.update'));
};
return (
<AuthenticatedLayout>
<Head title="ユーザー情報編集" />
<div>
<h3>ユーザー情報編集</h3>
<form onSubmit={handleSubmit}>
<div>
<label>名前</label>
<input
type="text"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
/>
{errors.name && <p>{errors.name}</p>}
</div>
<div>
<label>メールアドレス</label>
<input
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
/>
{errors.email && <p>{errors.email}</p>}
</div>
<button type="submit" disabled={processing}>
{processing ? '更新中...' : '更新する'}
</button>
</form>
<Link href={route('mypage.index')}>戻る</Link>
</div>
</AuthenticatedLayout>
);
}
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link } from '@inertiajs/react';
export default function Index({ user }) {
return (
<AuthenticatedLayout>
<Head title="マイページ" />
<div>
<h3>ユーザー情報</h3>
<p>名前: {user.name}</p>
<p>メールアドレス: {user.email}</p>
<p>登録日: {new Date(user.created_at).toLocaleDateString('ja-JP')}</p>
<Link href={route('mypage.edit')}>
<button>編集する</button>
</Link>
</div>
</AuthenticatedLayout>
);
}
動作確認をする
「名前」と「メールアドレス」を無事編集することができました。
4. 退会
モデルを修正する
将来、アカウント復活機能みたいなのを実装できるように拡張性を持たせるために、論理削除ができるようにしてみます。
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable, SoftDeletes; // 追加
/**
* 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 universities()
{
return $this->belongsToMany(University::class, 'university_edit_histories')->withTimestamps();
}
// 学部とのリレーション(多対多)
// 中間テーブル名を明示的に指定
public function faculties()
{
return $this->belongsToMany(Faculty::class, 'faculty_edit_histories')->withTimestamps();
}
// 研究室とのリレーション(多対多)
// 中間テーブル名を明示的に指定
public function labs()
{
return $this->belongsToMany(Lab::class, 'lab_edit_histories')->withTimestamps();
}
// レビューとのリレーション(一対多)
public function reviews()
{
return $this->hasMany(Review::class);
}
// コメントとのリレーション(一対多)
public function comments()
{
return $this->hasMany(Comment::class);
}
}
コントローラーを修正する
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Inertia\Inertia;
class MyPageController extends Controller
{
public function showUser()
{
$user = Auth::user();
return Inertia::render('MyPage/Index', [
'user' => $user,
]);
}
public function editUser(Request $request)
{
$user = Auth::user();
return Inertia::render('MyPage/Edit', [
'user' => $user,
'errors' => $request->session()->get('errors'),
]);
}
public function updateUser(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . Auth::id(),
]);
/** @var User $user */
$user = Auth::user();
$user->name = $request->name;
$user->email = $request->email;
$user->save();
return redirect()->route('mypage.index')->with('success', 'ユーザー情報を更新しました');
}
// 追加
public function deleteUser()
{
/** @var User $user */
$user = Auth::user();
$user->delete();
return redirect()->route('labs.home')->with('success', 'アカウントを削除しました');
}
}
Reactコンポーネントを修正する
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, router } from '@inertiajs/react';
export default function Index({ user }) {
const handleDeleteAccount = () => {
if (confirm('本当に退会しますか?この操作は取り消せません。')) {
router.delete(route('mypage.delete'));
}
};
return (
<AuthenticatedLayout>
<Head title="マイページ" />
<div>
<h3>ユーザー情報</h3>
<p>名前: {user.name}</p>
<p>メールアドレス: {user.email}</p>
<p>登録日: {new Date(user.created_at).toLocaleDateString('ja-JP')}</p>
<Link href={route('mypage.edit')}>
<button>編集する</button>
</Link>
<button onClick={handleDeleteAccount}>
退会する
</button>
</div>
</AuthenticatedLayout>
);
}
ルーティングを追加する
// マイページ関連
Route::get('/mypage', [MyPageController::class, 'showUser'])->name('mypage.index');
Route::get('/mypage/edit', [MyPageController::class, 'editUser'])->name('mypage.edit');
Route::put('/mypage', [MyPageController::class, 'updateUser'])->name('mypage.update');
Route::delete('/mypage', [MyPageController::class, 'deleteUser'])->name('mypage.delete'); // 追加
動作確認をする
データベースを見ると、deleted_at カラムに日付が入っていることが確認できます。

削除したアドレスでログインしようとすると、拒否られます(一応確認)。

5. ブックマーク済み研究室表示
モデルを修正する
将来、アカウント復活機能みたいなのを実装できるように拡張性を持たせるために、論理削除ができるようにしてみます。
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable, SoftDeletes; // 追加
/**
* 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 universities()
{
return $this->belongsToMany(University::class, 'university_edit_histories')->withTimestamps();
}
// 学部とのリレーション(多対多)
// 中間テーブル名を明示的に指定
public function faculties()
{
return $this->belongsToMany(Faculty::class, 'faculty_edit_histories')->withTimestamps();
}
// 研究室とのリレーション(多対多)
// 中間テーブル名を明示的に指定
public function labs()
{
return $this->belongsToMany(Lab::class, 'lab_edit_histories')->withTimestamps();
}
// レビューとのリレーション(一対多)
public function reviews()
{
return $this->hasMany(Review::class);
}
// コメントとのリレーション(一対多)
public function comments()
{
return $this->hasMany(Comment::class);
}
// 追加: ブックマークとのリレーション(一対多)
public function bookmarks()
{
return $this->hasMany(Bookmark::class);
}
}
コントローラーを修正する
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Inertia\Inertia;
class MyPageController extends Controller
{
public function showUser()
{
$user = Auth::user();
return Inertia::render('MyPage/Index', [
'user' => $user,
]);
}
public function editUser(Request $request)
{
$user = Auth::user();
return Inertia::render('MyPage/Edit', [
'user' => $user,
'errors' => $request->session()->get('errors'),
]);
}
public function updateUser(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . Auth::id(),
]);
/** @var User $user */
$user = Auth::user();
$user->name = $request->name;
$user->email = $request->email;
$user->save();
return redirect()->route('mypage.index')->with('success', 'ユーザー情報を更新しました');
}
public function deleteUser()
{
/** @var User $user */
$user = Auth::user();
$user->delete();
return redirect()->route('labs.home')->with('success', 'アカウントを削除しました');
}
// 追加
public function showBookmarks()
{
/** @var User $user */
$user = Auth::user();
$bookmarks = $user->bookmarks()->with('lab')->get();
return Inertia::render('MyPage/Bookmarks', [
'bookmarks' => $bookmarks,
]);
}
Reactコンポーネントを修正・作成する
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, router } from '@inertiajs/react';
export default function Index({ user }) {
const handleDeleteAccount = () => {
if (confirm('本当に退会しますか?この操作は取り消せません。')) {
router.delete(route('mypage.delete'));
}
};
return (
<AuthenticatedLayout>
<Head title="マイページ" />
<div>
<h3>ユーザー情報</h3>
<p>名前: {user.name}</p>
<p>メールアドレス: {user.email}</p>
<p>登録日: {new Date(user.created_at).toLocaleDateString('ja-JP')}</p>
<Link href={route('mypage.edit')}>
<button>編集する</button>
</Link>
<Link href={route('mypage.bookmarks')}>
<button>ブックマーク済み研究室</button>
</Link>
<button onClick={handleDeleteAccount}>
退会する
</button>
</div>
</AuthenticatedLayout>
);
}
// resources/js/Pages/MyPage/Bookmarks.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link } from '@inertiajs/react';
export default function Bookmarks({ bookmarks }) {
return (
<AuthenticatedLayout>
<Head title="ブックマーク済み研究室" />
<div>
<h3>ブックマーク済み研究室</h3>
{bookmarks.length === 0 ? (
<p>ブックマークした研究室はありません。</p>
) : (
<div>
{bookmarks.map((bookmark) => (
<div key={bookmark.id}>
<h4>{bookmark.lab.name}</h4>
<p>{bookmark.lab.description}</p>
<p>ブックマーク日: {new Date(bookmark.created_at).toLocaleDateString('ja-JP')}</p>
</div>
))}
</div>
)}
<Link href={route('mypage.index')}>
<button>マイページに戻る</button>
</Link>
</div>
</AuthenticatedLayout>
);
}
ルーティングを追加する
// マイページ関連
Route::get('/mypage', [MyPageController::class, 'showUser'])->name('mypage.index');
Route::get('/mypage/edit', [MyPageController::class, 'editUser'])->name('mypage.edit');
Route::put('/mypage', [MyPageController::class, 'updateUser'])->name('mypage.update');
Route::delete('/mypage', [MyPageController::class, 'deleteUser'])->name('mypage.delete');
Route::get('/mypage/bookmarks', [MyPageController::class, 'showBookmarks'])->name('mypage.bookmarks'); // 追加
動作確認をする
マイページに戻ると、「ブックマーク済み研究室」というボタンがあります。

6. ブックマーク解除
コントローラーを修正する
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Inertia\Inertia;
class MyPageController extends Controller
{
public function showUser()
{
$user = Auth::user();
return Inertia::render('MyPage/Index', [
'user' => $user,
]);
}
public function editUser(Request $request)
{
$user = Auth::user();
return Inertia::render('MyPage/Edit', [
'user' => $user,
'errors' => $request->session()->get('errors'),
]);
}
public function updateUser(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . Auth::id(),
]);
/** @var User $user */
$user = Auth::user();
$user->name = $request->name;
$user->email = $request->email;
$user->save();
return redirect()->route('mypage.index')->with('success', 'ユーザー情報を更新しました');
}
public function deleteUser()
{
/** @var User $user */
$user = Auth::user();
$user->delete();
return redirect()->route('labs.home')->with('success', 'アカウントを削除しました');
}
public function showBookmarks()
{
/** @var User $user */
$user = Auth::user();
$bookmarks = $user->bookmarks()->with('lab')->get();
return Inertia::render('MyPage/Bookmarks', [
'bookmarks' => $bookmarks,
]);
}
// 追加
public function removeBookmark($bookmarksId)
{
/** @var User $user */
$user = Auth::user();
$bookmark = $user->bookmarks()->findOrFail($bookmarksId);
$bookmark->delete();
return redirect()->route('mypage.bookmarks')->with('success', 'ブックマークを削除しました');
}
}
Reactコンポーネントを修正する
// resources/js/Pages/MyPage/Bookmarks.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, router } from '@inertiajs/react';
export default function Bookmarks({ bookmarks }) {
const handleRemoveBookmark = (bookmarkId) => {
if (confirm('ブックマークを解除しますか?')) {
router.delete(route('mypage.bookmark.remove', bookmarkId));
}
};
return (
<AuthenticatedLayout>
<Head title="ブックマーク済み研究室" />
<div>
<h3>ブックマーク済み研究室</h3>
{bookmarks.length === 0 ? (
<p>ブックマークした研究室はありません。</p>
) : (
<div>
{bookmarks.map((bookmark) => (
<div key={bookmark.id}>
<h4>{bookmark.lab.name}</h4>
<p>{bookmark.lab.description}</p>
<p>ブックマーク日: {new Date(bookmark.created_at).toLocaleDateString('ja-JP')}</p>
<button onClick={() => handleRemoveBookmark(bookmark.id)}>
ブックマーク解除
</button>
</div>
))}
</div>
)}
<Link href={route('mypage.index')}>
<button>マイページに戻る</button>
</Link>
</div>
</AuthenticatedLayout>
);
}
ルーティングを追加する
// マイページ関連
Route::get('/mypage', [MyPageController::class, 'showUser'])->name('mypage.index');
Route::get('/mypage/edit', [MyPageController::class, 'editUser'])->name('mypage.edit');
Route::put('/mypage', [MyPageController::class, 'updateUser'])->name('mypage.update');
Route::delete('/mypage', [MyPageController::class, 'deleteUser'])->name('mypage.delete');
Route::get('/mypage/bookmarks', [MyPageController::class, 'showBookmarks'])->name('mypage.bookmarks');
Route::delete('/mypage/bookmarks/{bookmark}', [MyPageController::class, 'removeBookmark'])->name('mypage.bookmark.remove'); // 追加
動作確認をする
7. まとめ・次回予告
今回は、マイページ機能実装ということで、ユーザー情報の表示・更新、ブックマークの表示・解除ができるようになりました。
次回は、管理者機能を作りたいと思います!
最後まで読んでくださり、ありがとうございました。
これまでの記事一覧
--- 要件定義・設計編 ---
--- 環境構築編 ---
- その2: 環境構築編① ~WSL, Ubuntuインストール~
- その3: 環境構築編② ~Docker Desktopインストール~
- その4: 環境構築編③ ~Dockerコンテナ立ち上げ~
- その5: 環境構築編④ ~Laravelインストール~
- その6: 環境構築編⑤ ~Gitリポジトリ接続~
--- バックエンド実装編 ---
- その7: バックエンド実装編① ~認証機能作成~
- その8: バックエンド実装編②前編 ~レビュー投稿機能作成~
- その8.5: バックエンド実装編②後編 ~レビュー投稿機能作成~
- その9: バックエンド実装編③ ~レビューCRUD機能作成~
- その10: バックエンド実装編④ ~レビューCRUD機能作成その2~
- その11: バックエンド実装編⑤ ~新規大学・学部・研究室作成機能作成~
- その12: バックエンド実装編⑥ ~大学検索機能作成~
- その13: バックエンド実装編⑦ ~大学・学部・研究室編集機能作成~
- その14: バックエンド実装編⑧ ~コメント投稿機能~
- その15: バックエンド実装編⑨ ~コメント編集・削除機能~
- その16: バックエンド実装編⑩ ~ブックマーク機能~
- その17: バックエンド実装編⑪ ~排他制御・トランザクション処理~












