Laravel で Ruby on Rails チュートリアル【8章】ログイン

## 環境
- Laravel 8.6
- PHP 8.0
- Bootstrap 5.1

## 8.1 セッション

### 8.1.1 Sessionsコントローラ

ログイン処理とログアウト処理

```php:/app/Http/Controllers/SessionsController.php
class SessionsController extends Controller
{
    public function create()
    {
        return view("sessions.create");
    }
}
```
```php:/routes/web.php
Route::get('login', [SessionsController::class, 'create'])->name('login');
Route::post('login', [SessionsController::class, 'store']);
Route::delete('logout', [SessionsController::class, 'destroy'])->name('logout');
```
```php:/tests/Unit/SessionsControllerTest.php
class SessionsControllerTest extends TestCase
{
    public function testGetCreate()
    {
        $response = $this->get(route("login"));
        $response->assertStatus(200);
    }
}
```

### 8.1.2 ログインフォーム

```html:/resources/views/sessions/create.blade.php
@extends('layouts.application')

@section('title', 'Log in')

@section('content')
    <h1>Log in</h1>

    <div class="row">
        <div class="col-md-6 offset-md-3">
            {{ Form::open(['route' => 'login']) }}

            <div class="mb-3">
                {{ Form::label('email', 'Email', ['class' => 'form-label fw-bold']) }}
                {{ Form::text('email', "", ["class" => "form-control"]) }}
            </div>

            <div class="mb-3">
                {{ Form::label('password', 'Password', ['class' => 'form-label fw-bold']) }}
                {{ Form::password('password', ["class" => "form-control"]) }}
            </div>

            {{ Form::submit("Log in", ["class" => "btn btn-primary w-100"]) }}
            {{ Form::close() }}

            <p>New user? {{ Html::link(route("signup"), "Sign up now!") }}</p>
        </div>
    </div>
@endsection
```

### 8.1.3 ユーザーの検索と認証

```php:/app/Http/SessionsController.php
    public function store(Request $request)
    {
        $user = User::where("email", Str::lower($request->email))->first();
        if ($user && Hash::check($request->password, $user->password)) {
            // ログイン後にユーザー情報のページにリダイレクトする
        } else {
            // エラーメッセージを作成する
            return back()->withInput();
        }
    }
```

### 8.1.4 フラッシュメッセージを表示する

```php:/app/Http/Controllers/SessionsController.php
session()->flash('message', ['danger' => 'Invalid email/password combination']);
```

### 8.1.5 フラッシュのテスト

```php:/tests/Feature/UsersLoginTest.php
    public function testInvalidLogin()
    {
        $response = $this->get(route('login'));
        $response->assertViewIs('sessions.create');
        $response = $this->followingRedirects()
            ->post('login', [
                'email'    => "",
                'password' => "",
            ]);
        $response->assertViewIs('sessions.create');
        $response->assertSeeText('Invalid email/password combination');
        $response = $this->get('/');
        $response->assertDontSeeText('Invalid email/password combination');
    }
```

## 8.2 ログイン

### 8.2.1 log_inメソッド

```php:/app/Http/Controllers/SessionsController.php
    public function store(Request $request)
    {
        $user = User::where("email", Str::lower($request->email))->first();
        if ($user && Hash::check($request->password, $user->password)) {
            Auth::login($user);
            return redirect()->route("users.show", $user);
        } else {
            session()->flash('message', ['danger' => 'Invalid email/password combination']);
            return back()->withInput();
        }
    }
```

### 8.2.2 現在のユーザー

```php
Auth::user();
```

### 8.2.3 レイアウトリンクを変更する

```html:/resources/views/layouts/header.blade.php
<header>
    <nav class="navbar navbar-expand fixed-top navbar-dark bg-dark p-0">
        <div class="container">
            {{ link_to_route('home', "sample app", [], ["id" => "logo", "class" => "navbar-brand"]) }}
            <ul class="navbar-nav">
                <li class="nav-item">{{ link_to_route("home", "Home", [], ["class" => "nav-link"]) }}</li>
                <li class="nav-item">{{ link_to_route("help", "Help", [], ["class" => "nav-link"]) }}</li>
                @if (auth()->check())
                    <li class="nav-item">{{ link_to_route("users.index", "Users", [], ["class" => "nav-link"]) }}</li>
                    <li class="dropdown">
                        <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown">Account</a>
                        <ul class="dropdown-menu">
                            <li>{{ link_to_route("users.show", "Profile", [auth()->id()], ["class" => "dropdown-item"]) }}</li>
                            <li>{{ link_to_route("home", "Settings", [], ["class" => "dropdown-item"]) }}</li>
                            <li>
                                <hr class="dropdown-divider">
                            </li>
                            <li>
                                <a href="javascript:document.logoutform.submit()" class="dropdown-item">Log out</a>
                                {{ Form::open(["route" => "logout", "method" => "delete", "name" => "logoutform"]) }}
                                {{ Form::close() }}
                            </li>
                        </ul>
                    </li>
                @else
                    <li>{{ link_to_route("login", "Log in", [], ["class" => "nav-link"]) }}</li>
                @endif
            </ul>
        </div>
    </nav>
</header>
```

### 8.2.4 レイアウトの変更をテストする

```php:/database/seedes/TestSeeder.php
class TestSeeder extends Seeder
{
    public function run()
    {
        DB::table('users')->insert([
            'name' => "Michael Example",
            'email' => strtolower("michael@example.com"),
            'password' => bcrypt('password'),
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now(),
        ]);
    }
}
```
```php:/tests/Feature/UsersLoginTest.php
class UsersLoginTest extends TestCase
{
    private $user;
    private $user_pass;

    protected function setUp()
    {
        parent::setUp();
        Artisan::call('migrate:fresh');
        $this->seed('TestSeeder');
        $this->user = User::find(1);
        $this->user_pass = "password";
    }
...
    public function testValidLogin()
    {
        $this->get(route("login"));
        $response = $this->followingRedirects()
                        ->post(route("login"), [
                            'email'    => $this->user->email,
                            'password' => $this->user_pass
                        ]);
        $response->assertViewIs('users.show');
        $dom = $this->dom($response->content());
        $this->assertSame(0, $dom->filter('a:contains("Log in")')->count());
        $this->assertSame(route("logout"), $dom->filter('form[name="logoutform"]')->attr("action"));
        $this->assertSame(route("users.show", $this->user), $dom->filter('a:contains("Profile")')->attr("href"));
    }
}
```
```php:/tests/Feature/UsersSignupTest.php
class UsersSignupTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        Artisan::call('migrate:fresh');
    }
```

テストで使用するDBを指定する

```xml:/phpunit.xml
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
```

### 8.2.5 ユーザー登録時にログイン

```php:/app/Http/Controllers/UsersController.php
    public function store(Request $request)
    {
        $request->email = Str::lower($request->email);
        $request->validate([
            "name"                  => "required|max:50",
            "email"                 => "required|max:255|email|unique:users|email",
            "password"              => "required|confirmed|min:6",
            "password_confirmation" => "required|min:6",
        ]);
        $user = new User;
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $user->save();
        Auth::login($user);
        session()->flash("message", ['success' => 'Welcome to the Sample App!']);
        return redirect()->route("users.show", $user);
    }
```
テスト追加

```php:/tests/Feature/UsersSignupTest.php
$this->assertTrue(Auth::check());
```

## 8.3 ログアウト

```php:/app/Http/Controllers/SessionsController.php
    public function destroy()
    {
        Auth::logout();
        return redirect("/");
    }
```
```php:/tests/Feature/UsersLoginTest.php
    public function testValidLoginToLogout()
    {
        $this->get(route("login"));
        $response = $this->followingRedirects()
                        ->post(route("login"), [
                            'email'    => $this->user->email,
                            'password' => $this->user_pass
                        ]);
        $this->assertTrue(Auth::check());
        $response->assertViewIs('users.show');
        $dom = $this->dom($response->content());
        $this->assertSame(0, $dom->filter('a:contains("Log in")')->count());
        $this->assertSame(route("logout"), $dom->filter('form[name="logoutform"]')->attr("action"));
        $this->assertSame(route("users.show", $this->user), $dom->filter('a:contains("Profile")')->attr("href"));
        $response = $this->followingRedirects()->delete(route("logout"));
        $this->assertFalse(Auth::check());
        $response->assertViewIs("static_pages.home");
        $dom = $this->dom($response->content());
        $this->assertSame(route("login"), $dom->filter('a:contains("Log in")')->attr("href"));
        $this->assertSame(0, $dom->filter('form[name="logoutform"]')->count());
        $this->assertSame(0, $dom->filter('a:contains("Profile")')->count());
    }
```

## 8.4 最後に