0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実務未経験エンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(バックエンド実装編⑫)~マイページ機能作成~

Last updated at Posted at 2025-09-11

実務未経験エンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その18)

0. 初めに

こんにちは!
このシリーズでは、実務未経験の僕が頑張ってWebアプリケーションを開発する方法を解説しております!

今回は、マイページ機能を作成していきたいと思います!
是非最後までお付き合いのほどよろしくお願いいたします。(>_<)

今回実装する内容

今回は以下の順で実装していきたいと思います。

  • ユーザー情報表示
  • ユーザー情報更新
  • 退会
  • ブックマーク済み研究室表示
  • ブックマーク解除

1. ブランチ運用

いつも通り、develop ブランチを最新化して、今回は feature/16-mypage というブランチを切って作業します。
作業が終わったら、コミット・プッシュ、プルリクエスト作成・マージを忘れずに行いましょう。

2. ユーザー情報表示

まずは、ログインしているユーザー情報を表示できるようにしましょう!

コントローラーを作成する

コントローラーを作成しましょう。

実行コマンド

/var/www
$ php artisan make:controller MyPageController
\project-root\src\app\Http\Controllers\MyPageController.php
<?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コンポーネントを作成する

\project-root\src\resources\js\Pages\Mypage\Index.jsx
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 ミドルウェアの中に追加!

\project-root\src\routes\web.php
    // マイページ関連
    Route::get('/mypage', [MyPageController::class, 'showUser'])->name('mypage.index');

動作確認をする

適当なユーザーでログインして、以下にアクセス。
http://localhost/mypage

ユーザー情報が表示できていれば成功!

image.png

これにて、ユーザー情報表示は完成とします!

3. ユーザー情報更新

次にユーザー情報更新機能を作りましょう。

コントローラー修正

\project-root\src\app\Http\Controllers\MyPageController.php
<?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コンポーネントを作成する

編集用ページを作ります。

\project-root\src\resources\js\Pages\Mypage\Edit.jsx
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>
    );
}
\project-root\src\resources\js\Pages\MyPage\Index.jsx
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>
    );
}

動作確認をする

image.png

image.png

image.png

「名前」と「メールアドレス」を無事編集することができました。

4. 退会

モデルを修正する

将来、アカウント復活機能みたいなのを実装できるように拡張性を持たせるために、論理削除ができるようにしてみます。

php\project-root\src\app\Models\User.php
<?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);
    }
}

コントローラーを修正する

\project-root\src\app\Http\Controllers\MyPageController.php
<?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コンポーネントを修正する

\project-root\src\resources\js\Pages\MyPage\Index.jsx
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'); // 追加

動作確認をする

image.png

image.png

削除が完了するとログアウトされ、トップページに移動します。
image.png

データベースを見ると、deleted_at カラムに日付が入っていることが確認できます。
image.png

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

5. ブックマーク済み研究室表示

モデルを修正する

将来、アカウント復活機能みたいなのを実装できるように拡張性を持たせるために、論理削除ができるようにしてみます。

php\project-root\src\app\Models\User.php
<?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);
    }
}

コントローラーを修正する

\project-root\src\app\Http\Controllers\MyPageController.php
<?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コンポーネントを修正・作成する

\project-root\src\resources\js\Pages\MyPage\Index.jsx
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>
    );
}
\project-root\src\resources\js\Pages\MyPage\Bookmarks.jsx
// 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>
    );
}

ルーティングを追加する

\project-root\src\routes\web.php
    // マイページ関連
    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'); // 追加

動作確認をする

先ほど退会したのとは別のユーザーでログインします。
image.png

好きな研究室をブックマークしておきます。
image.png

マイページに戻ると、「ブックマーク済み研究室」というボタンがあります。
image.png

ボタンをクリックすると表示されることを確認します。
image.png

6. ブックマーク解除

コントローラーを修正する

\project-root\src\app\Http\Controllers\MyPageController.php
<?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コンポーネントを修正する

\project-root\src\resources\js\Pages\MyPage\Bookmarks.jsx
// 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>
    );
}

ルーティングを追加する

\project-root\src\routes\web.php
    // マイページ関連
    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'); // 追加

動作確認をする

image.png

image.png

image.png
無事削除できたようです。

7. まとめ・次回予告

今回は、マイページ機能実装ということで、ユーザー情報の表示・更新ブックマークの表示・解除ができるようになりました。

次回は、管理者機能を作りたいと思います!
最後まで読んでくださり、ありがとうございました。

これまでの記事一覧

--- 要件定義・設計編 ---

--- 環境構築編 ---

--- バックエンド実装編 ---

参考

PHPDocコメント まとめ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?