■P109

────────────────
Route::middleware('guest')->group(function () {
    Route::get('/login', \App\Http\Controllers\Auth\Login\IndexController::class)
        ->name('login');
});
────────────────


■P110

────────────────
sail artisan make:middleware SampleMiddleware
────────────────

●app/Http/Middleware/SampleMiddleware.php（例）
────────────────
public function handle(Request $request, Closure $next): Response
{
    return $next($request);
}
────────────────

────────────────
public function handle(Request $request, Closure $next): Response
{
    // コントローラーが実行される前の処理

    $response = $next($request); // コントローラーの実行

    // コントローラーが実行された後の処理

    return $response;
}
────────────────


■P111

●routes/web.php（例）
────────────────
Route::middleware('guest')->group(function () {
    Route::get('/login', \App\Http\Controllers\Auth\Login\IndexController::class)->name('login');
    Route::post('/login', \App\Http\Controllers\Auth\Login\PostController::class);
});
────────────────


■P112

●routes/web.php（例）
────────────────
Route::middleware(\App\Http\Middleware\SampleMiddleware::class)->group(function () {
    // このグループ内のルートにSampleMiddlewareが適用される
});
────────────────

●bootstrap/app.php（例）
────────────────
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        // エイリアスを登録
        $middleware->alias([
            'sample' => \App\Http\Middleware\SampleMiddleware::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
────────────────


●routes/web.php（例）
────────────────
Route::middleware('sample')->group(function () {
    // ...
});
────────────────


■P114

●config/auth.php
────────────────
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => env('AUTH_MODEL', App\Models\User::class),
    ],
],
────────────────


■P115

●bootstrap/app.php（例）
────────────────
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        // 404エラーのカスタマイズ例
        $exceptions->render(function (NotFoundHttpException $e) {
            return response()->view('errors.404', [], 404);
        });
    })->create();
────────────────

●Illuminate/Foundation/Exceptions/Handler.php（prepareExceptionメソッド）
────────────────
protected function prepareException(Throwable $e)
    {
        return match (true) {
            $e instanceof BackedEnumCaseNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
            $e instanceof ModelNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
            $e instanceof AuthorizationException && $e->hasStatus() => new HttpException(
                $e->status(), $e->response()?->message() ?: (Response::$statusTexts[$e->status()] ?? 'Whoops, looks like something went wrong.'), $e
            ),
            $e instanceof AuthorizationException && ! $e->hasStatus() => new AccessDeniedHttpException($e->getMessage(), $e),
            $e instanceof OriginMismatchException => new HttpException(403, $e->getMessage(), $e),
            $e instanceof TokenMismatchException => new HttpException(419, $e->getMessage(), $e),
            $e instanceof RequestExceptionInterface => new BadRequestHttpException('Bad request.', $e),
            $e instanceof RecordNotFoundException => new NotFoundHttpException('Not found.', $e),
            $e instanceof RecordsNotFoundException => new NotFoundHttpException('Not found.', $e),
            default => $e,
        };
    }
────────────────


■P117

────────────────
sail artisan make:controller Auth/Register/IndexController --invokable
────────────────

●app/Http/Controllers/Auth/Register/IndexController.php
────────────────
<?php

namespace App\Http\Controllers\Auth\Register;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class IndexController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(Request $request)
    {
        return view('auth.register');
    }
}
────────────────


■P118

●resources/views/auth/register.blade.php
────────────────
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ユーザー登録 - つぶやきアプリ</title>
</head>
<body>
    <h1>ユーザー登録</h1>
    <p>ここに登録フォームを作成します</p>
</body>
</html>
────────────────

●routes/web.php
────────────────
<?php

use Illuminate\Support\Facades\Route;

// ...既存のルーティング...

Route::get('/register', \App\Http\Controllers\Auth\Register\IndexController::class)->name('register');
────────────────


■P119

────────────────
sail artisan make:request Auth/RegisterRequest
────────────────



●app/Http/Requests/Auth/RegisterRequest.php
────────────────
<?php

namespace App\Http\Requests\Auth;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class RegisterRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => ['required', 'confirmed', Password::defaults()],
        ];
    }

    public function name(): string
    {
        return $this->input('name');
    }

    public function email(): string
    {
        return $this->input('email');
    }

    public function password(): string
    {
        return $this->input('password');
    }
}
────────────────


■P121

●app/Providers/AppServiceProvider.php（例）
────────────────
use Illuminate\Validation\Rules\Password;

	public function boot(): void
    {
        Password::defaults(function () {
            return Password::min(8)         // 最低8文字
                ->letters()                 // 英字を含む
                ->mixedCase()               // 大文字と小文字を含む
                ->numbers()                 // 数字を含む
                ->symbols();                // 記号を含む
        });
    }
────────────────

●resources/views/auth/register.blade.php
────────────────
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ユーザー登録 - つぶやきアプリ</title>
</head>
<body>
    <nav>
        <a href="{{ url('/tweet') }}">つぶやきアプリ</a>
    </nav>
    <main>
        <h1>ユーザー登録</h1>
        <form method="POST" action="{{ route('register') }}">
            @csrf
            <div>
                <label for="name">お名前</label>
                <input type="text"
                       id="name"
                       name="name"
                       value="{{ old('name') }}"
                       required
                       autofocus>
                @error('name')
                    <p style="color: red;">{{ $message }}</p>
                @enderror
            </div>
            <div>
                <label for="email">メールアドレス</label>
                <input type="email"
                       id="email"
                       name="email"
                       value="{{ old('email') }}"
                       required>
                @error('email')
                    <p style="color: red;">{{ $message }}</p>
                @enderror
            </div>
            <div>
                <label for="password">パスワード</label>
                <input type="password"
                       id="password"
                       name="password"
                       required>
                @error('password')
                    <p style="color: red;">{{ $message }}</p>
                @enderror
            </div>
            <div>
                <label for="password_confirmation">パスワード確認</label>
                <input type="password"
                       id="password_confirmation"
                       name="password_confirmation"
                       required>
            </div>
            <div>
                <button type="submit">登録</button>
            </div>
        </form>
    </main>
</body>
</html>
────────────────



■P123

────────────────
sail artisan make:controller Auth/Register/PostController --invokable
────────────────

●app/Http/Controllers/Auth/Register/PostController.php
────────────────
<?php

namespace App\Http\Controllers\Auth\Register;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\RegisterRequest;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class PostController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(RegisterRequest $request)
    {
        $user = User::create([
            'name' => $request->name(),
            'email' => $request->email(),
            'password' => Hash::make($request->password()),
        ]);

        Auth::login($user);

        return redirect('/tweet');
    }
}
────────────────


■P124

●routes/web.php
────────────────
// Step 3で追加したルートの下に追加
Route::post('/register', \App\Http\Controllers\Auth\Register\PostController::class);
────────────────


■P125

────────────────
sail artisan make:controller Auth/Login/IndexController --invokable
────────────────

●app/Http/Controllers/Auth/Login/IndexController.php
────────────────
<?php

namespace App\Http\Controllers\Auth\Login;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class IndexController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(Request $request)
    {
        return view('auth.login');
    }
}
────────────────

●resources/views/auth/login.blade.php
────────────────
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ログイン - つぶやきアプリ</title>
</head>
<body>
    <h1>ログイン</h1>
    <p>ここにログインフォームを作成します</p>
</body>
</html>
────────────────


■P126

●routes/web.php
────────────────
Route::get('/login', \App\Http\Controllers\Auth\Login\IndexController::class)->name('login');
────────────────

────────────────
sail artisan make:request Auth/LoginRequest
────────────────

●app/Http/Requests/Auth/LoginRequest.php
────────────────
<?php

namespace App\Http\Requests\Auth;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'email' => 'required|email',
            'password' => 'required',
        ];
    }

    public function email(): string
    {
        return $this->input('email');
    }

    public function password(): string
    {
        return $this->input('password');
    }

    public function credentials(): array
    {
        return [
            'email' => $this->email(),
            'password' => $this->password(),
        ];
    }
}
────────────────


■P128

●resources/views/auth/login.blade.php
────────────────
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ログイン - つぶやきアプリ</title>
</head>
<body>
    <nav>
        <a href="{{ url('/') }}">つぶやきアプリ</a>
    </nav>
    <main>
        <h1>ログイン</h1>
        <form method="POST" action="{{ route('login') }}">
            @csrf
            <div>
                <label for="email">メールアドレス</label>
                <input type="email"
                       id="email"
                       name="email"
                       value="{{ old('email') }}"
                       required
                       autofocus>
                @error('email')
                    <p style="color: red;">{{ $message }}</p>
                @enderror
            </div>
            <div>
                <label for="password">パスワード</label>
                <input type="password"
                       id="password"
                       name="password"
                       required>
                @error('password')
                    <p style="color: red;">{{ $message }}</p>
                @enderror
            </div>
            <div>
                <button type="submit">ログイン</button>
            </div>
        </form>
        <p>
            <a href="{{ route('register') }}">アカウントをお持ちでない方はこちら</a>
        </p>
    </main>
</body>
</html>
────────────────


■P129

●resources/views/auth/register.blade.php
────────────────
        </form>
        <p>
            <a href="{{ route('login') }}">すでにアカウントをお持ちの方はこちら</a>
        </p>
    </main>
</body>
</html>
────────────────


■P130

────────────────
sail artisan make:controller Auth/Login/PostController --invokable
────────────────

●app/Http/Controllers/Auth/Login/PostController.php
────────────────
<?php

namespace App\Http\Controllers\Auth\Login;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class PostController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(LoginRequest $request)
    {
        if (Auth::attempt($request->credentials())) {
            $request->session()->regenerate();
            return redirect()->intended('/tweet');
        }

        throw ValidationException::withMessages([
            'email' => 'メールアドレスまたはパスワードが正しくありません。',
        ]);
    }
}
────────────────


■P131

────────────────
Route::post('/login', \App\Http\Controllers\Auth\Login\PostController::class);
────────────────

────────────────
sail artisan make:controller Auth/Logout/PostController --invokable
────────────────

●app/Http/Controllers/Auth/Logout/PostController.php
────────────────
<?php

namespace App\Http\Controllers\Auth\Logout;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class PostController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(Request $request)
    {
        // ログイン情報を破棄する
        Auth::logout();

        // セッションを無効化する（セッションに保存されたデータをすべて削除）
        $request->session()->invalidate();

        // CSRFトークンを再生成する（セキュリティ対策）
        $request->session()->regenerateToken();

        return redirect('/tweet');
    }
}
────────────────


■P132

●routes/web.php
────────────────
Route::post('/logout', \App\Http\Controllers\Auth\Logout\PostController::class)->name('logout');
────────────────


■P133

●resources/views/tweet/index.blade.php
────────────────
    <header>
        <h1>つぶやきアプリ</h1>
        @auth
            <form action="{{ route('logout') }}" method="post" style="display: inline;">
                @csrf
                <button type="submit">ログアウト</button>
            </form>
        @endauth
    </header>
────────────────


■P134

●routes/web.php
────────────────
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return redirect()->route('tweet.index');
});

// 未ログインユーザー向けのルート
Route::middleware('guest')->group(function () {
    Route::get('/login', \App\Http\Controllers\Auth\Login\IndexController::class)
        ->name('login');
    Route::post('/login', \App\Http\Controllers\Auth\Login\PostController::class);

    Route::get('/register', \App\Http\Controllers\Auth\Register\IndexController::class)
        ->name('register');
    Route::post('/register', \App\Http\Controllers\Auth\Register\PostController::class);
});

// ログアウト（認証済みユーザーのみ）
Route::post('/logout', \App\Http\Controllers\Auth\Logout\PostController::class)
    ->middleware('auth')
    ->name('logout');

// つぶやき一覧（誰でもアクセス可能）
Route::get('/tweet', \App\Http\Controllers\Tweet\IndexController::class)
    ->name('tweet.index');

// 認証が必要なルート
Route::middleware('auth')->group(function () {
    Route::post('/tweet', \App\Http\Controllers\Tweet\CreateController::class)
        ->name('tweet.create');
    // 編集・削除のルートは後で追加
});

Route::get('/tweet/{tweetId}', \App\Http\Controllers\Tweet\Update\IndexController::class)->name('tweet.update.index');
Route::put('/tweet/{tweetId}', \App\Http\Controllers\Tweet\Update\PutController::class)->name('tweet.update.put');
Route::delete('/tweet/{tweetId}', \App\Http\Controllers\Tweet\DeleteController::class)->name('tweet.delete');
────────────────


■P135

●resources/views/tweet/index.blade.php
────────────────
    @auth
    <div>
        <p>投稿フォーム</p>
        @if (session('feedback.success'))
        <p style="color: green">{{ session('feedback.success') }}</p>
        @endif
        <form action="{{ route('tweet.create') }}" method="post">
            @csrf
            <label for="tweet-content">つぶやき</label>
            <span>140文字まで</span>
            <textarea id="tweet-content" type="text" name="tweet" placeholder="つぶやきを入力"></textarea>
            @error('tweet')
            <p style="color: red;">{{ $message }}</p>
            @enderror
            <button type="submit">投稿</button>
        </form>
    </div>
    @endauth
────────────────


■P136

────────────────
sail artisan make:migration add_user_id_to_tweets_table
────────────────


■P137

●database/migrations/xxxx_add_user_id_to_tweets_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::table('tweets', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id')->after('id');
            // usersテーブルのidカラムにuser_idカラムを関連付けます。
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('tweets', function (Blueprint $table) {
            $table->dropForeign('tweets_user_id_foreign');
            $table->dropColumn('user_id');
        });
    }
};
────────────────


■P138

────────────────
sail artisan make:seeder UsersSeeder
────────────────

●database/seeders/UsersSeeder.php
────────────────
<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class UsersSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        User::factory()->create();
    }
}
────────────────

●database/factories/TweetFactory.php
────────────────
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tweet>
 */
class TweetFactory extends Factory
{
		/**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'user_id' => 1,  // この行を追加
            'content' =>$this->faker->realText(100),
        ];
    }
}
────────────────


■P139

●database/seeders/DatabaseSeeder.php
────────────────
<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            UsersSeeder::class,
            TweetsSeeder::class,
        ]);
    }
}
────────────────


■P140

────────────────
sail artisan migrate:fresh --seed
────────────────

●app/Http/Requests/Tweet/CreateRequest.php
────────────────
    public function userId(): int
    {
        return $this->user()->id;
    }
────────────────

●app/Http/Controllers/Tweet/CreateController.php
────────────────
public function __invoke(CreateRequest $request)
{
    $tweet = new Tweet();
    $tweet->user_id = $request->userId();  // ユーザーIDを保存
    $tweet->content = $request->tweet();
    $tweet->save();

    return redirect()->route('tweet.index');
}
────────────────


■P141

●app/Models/User.php
────────────────
    public function tweets()
    {
        return $this->hasMany(Tweet::class);
    }
────────────────

●app/Models/Tweet.php
────────────────
    public function user()
    {
        return $this->belongsTo(User::class);
    }
────────────────

●resources/views/tweet/index.blade.php
────────────────
@foreach($tweets as $tweet)
    <details>
        <summary>{{ $tweet->content }} by {{ $tweet->user->name }}</summary>
        
        …省略…
        
    </details>
@endforeach
────────────────


■P142

●routes/web.php
────────────────
Route::middleware('auth')->group(function () {
    Route::post('/tweet', \App\Http\Controllers\Tweet\CreateController::class)
        ->name('tweet.create');

    Route::get('/tweet/{tweetId}', \App\Http\Controllers\Tweet\Update\IndexController::class)->name('tweet.update.index');
    Route::put('/tweet/{tweetId}', \App\Http\Controllers\Tweet\Update\PutController::class)->name('tweet.update.put');
    Route::delete('/tweet/{tweetId}', \App\Http\Controllers\Tweet\DeleteController::class)->name('tweet.delete');
});
────────────────

●app/Services/TweetService.php
────────────────
<?php

namespace App\Services;

use App\Models\Tweet;

class TweetService
{
    public function getTweets()
    {
        return Tweet::query()->orderBy('created_at', 'DESC')->get();
    }

    public function checkOwnTweet(int $userId, int $tweetId): bool
    {
        $tweet = Tweet::query()->where('id', $tweetId)->first();
        if (!$tweet) {
            return false;
        }

        return $tweet->user_id === $userId;
    }
}
────────────────


■P143

●app/Http/Controllers/Tweet/Update/IndexController.php
────────────────
<?php

namespace App\Http\Controllers\Tweet\Update;

use App\Http\Controllers\Controller;
use App\Models\Tweet;
use App\Services\TweetService;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class IndexController extends Controller
{
    public function __invoke(Request $request, TweetService $tweetService)
    {
        $tweetId = (int) $request->route('tweetId');

        if (! $tweetService->checkOwnTweet($request->user()->id, $tweetId)) {
            throw new AccessDeniedHttpException;
        }
        $tweet = Tweet::query()->where('id', $tweetId)->firstOrFail();
        return view('tweet.update')->with('tweet', $tweet);
    }
}
────────────────


■P144

●resources/views/tweet/index.blade.php
────────────────
        @foreach($tweets as $tweet)
            <details>
                <summary>{{ $tweet->content }} by {{ $tweet->user->name }}</summary>
                @if(\Illuminate\Support\Facades\Auth::id() === $tweet->user_id)
                    <div>
                        <a href="{{ route('tweet.update.index', ['tweetId' => $tweet->id]) }}">
                            編集
                        </a>
                        <form action="{{ route('tweet.delete', ['tweetId' => $tweet->id]) }}" method="post">
                            @method('DELETE')
                            @csrf
                            <button type="submit">削除</button>
                        </form>
                    </div>
                @else
                    編集できません
                @endif
            </details>
        @endforeach
────────────────
