人気のPHPフレームワーク「Laravel」によるWebサイトの作り方を解説します。

LaravelでWebサイトを作ってみよう!

基本

Twitterに似たサービスを作ってみよう

更新日:

新しいプロジェクトの作成

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>

-基本

Copyright© LaravelでWebサイトを作ってみよう! , 2025 All Rights Reserved Powered by STINGER.