Laravel で Ruby on Rails チュートリアル【7章】フォーム

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

## 7.1 ユーザーを表示する

### 7.1.1 デバッグとRails環境

デバッグ用のパッケージを追加

```bash
sail composer require barryvdh/laravel-debugbar --dev
```

### 7.1.2 Usersリソース

artisan tinkerでユーザー作成

```php
User::create(["name" => "Michael Hartl", "email" => "mhartl@example.com", "password" => bcrypt("foobar")]);
```
ユーザーを表示するページを作成

```php:routes/web.php
Route::resource('users', UsersController::class)->except(["create"]);
```
```html:/resources/views/users/show.blade.php
{{ $user->name }}, {{ $user->email }}
```
```php:/app/Http/Controllers/UsersController.php
    public function show(User $user)
    {
        return view('users.show')->with('user', $user);
    }
```

### 7.1.3 debuggerメソッド

### 7.1.4 Gravatar画像とサイドバー

ユーザー表示ページ修正

```php:/resources/views/users/show.blade.php
@extends('layouts.application')

@section('title', $user->name)

@section('content')
    <h1>
        <img src="{{ gravatar_url($user) }}" class="gravatar" alt="Example User">
        {{ $user->name }}
    </h1>
@endsection
```
```php:/app/helpers.php
if (!function_exists('gravatar_url')) {
    function gravatar_url(User $user)
    {
        $gravatar_id = md5(strtolower($user->email));
        return $gravatar_url = "https://secure.gravatar.com/avatar/{$gravatar_id}";
    }
}
```

ユーザーのメールアドレスを更新

```bash
$ sail artisan tinker
>>> User::find(1)->update(["name" => "Example User", "email" => "example@railstutorial.org", "password" => bcrypt("foobar")]);
```

#### 演習

```php
if (!function_exists('gravatar_url')) {
    function gravatar_url(User $user, array $options = ["size" => 80])
    {
        $gravatar_id = md5(strtolower($user->email));
        return $gravatar_url = "https://secure.gravatar.com/avatar/{$gravatar_id}?s={$options["size"]}";
    }
}
```

## 7.2 ユーザー登録フォーム

### 7.2.1 form_forを使用する

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

@section('title', 'Sign up')

@section('content')
    <h1>Sign up</h1>

    <div class="row">
        <div class="col-md-6 offset-md-3">
            {{ Form::model($user, ['url' => ['users']]) }}
            <div class="mb-3">
                {{ Form::label('name', 'Name', ['class' => 'form-label fw-bold']) }}
                {{ Form::text('name', null, ['class' => 'form-control']) }}
            </div>
            <div class="mb-3">
                {{ Form::label('email', 'Email', ['class' => 'form-label fw-bold']) }}
                {{ Form::email('email', null, ['class' => 'form-control']) }}
            </div>
            <div class="mb-3">
                {{ Form::label('password', 'Password', ['class' => 'form-label fw-bold']) }}
                {{ Form::password('password', ['class' => 'form-control']) }}
            </div>
            <div class="mb-3">
                {{ Form::label('password_confirmation', 'Password confirmation', ['class' => 'form-label fw-bold']) }}
                {{ Form::password('password_confirmation', ['class' => 'form-control']) }}
            </div>
            {{ Form::submit("Create my account", ["class" => "btn btn-primary w-100"]) }}
            {{ Form::close() }}
        </div>
    </div>
@endsection
```

### 7.2.2 フォームHTML

## 7.3 ユーザー登録失敗

### 7.3.1 正しいフォーム

コントローラにユーザーを保存するアクションを追加

```php:/app/Http/Controllerss/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();
        return redirect()->route("signup");
    }
```

### 7.3.2 Strong Parameters

### 7.3.3 エラーメッセージ

エラーメッセージを表示するようレイアウト修正

```html:/resources/views/users/create.blade.php
        <div class="col-md-6 offset-md-3">
            {{ Form::model($user, ['url' => ['users']]) }}
            @include('shared.error_messages')
            <div class="mb-3">
                {{ Form::label('name', 'Name', ['class' => 'form-label fw-bold']) }}
                {{ Form::text('name', null, ['class' => 'form-control']) }}
            </div>
            <div class="mb-3">
                {{ Form::label('email', 'Email', ['class' => 'form-label fw-bold']) }}
                {{ Form::email('email', null, ['class' => 'form-control']) }}
            </div>
            <div class="mb-3">
                {{ Form::label('password', 'Password', ['class' => 'form-label fw-bold']) }}
                {{ Form::password('password', ['class' => 'form-control']) }}
            </div>
            <div class="mb-3">
                {{ Form::label('password_confirmation', 'Password confirmation', ['class' => 'form-label fw-bold']) }}
                {{ Form::password('password_confirmation', ['class' => 'form-control']) }}
            </div>
            {{ Form::submit("Create my account", ["class" => "btn btn-primary w-100"]) }}
            {{ Form::close() }}
        </div>
```
```html:/resources/views/shared/error_messages.blade.php
@if ($errors->any())
    <div id="error_explanation">
        <div class="alert alert-danger">
            The form contains {{ $errors->count() . Illuminate\Support\Str::plural('error', $errors->count()) }}.
        </div>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

```

### 7.3.4 失敗時のテスト

```php:/tests/Feature/UsersSignupTest.php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

class UsersSignupTest extends TestCase
{
    public function testInvalidSignup()
    {
        $this->get("signup");
        $count = User::all()->count();
        $response = $this->followingRedirects()
                        ->post("users", [
                            "name" => "",
                            "email" => "user@invalid",
                            "password" => "foo",
                            "password_confirmation" => "bar"
                        ]);
        $this->assertSame($count, User::all()->count());
        $response->assertViewIs("users.create");
    }
}
```

#### 課題

```php:/tests/Feature/UsersSignupTest.php
        $dom = $this->dom($response->content());
        $this->assertSame(1, $dom->filter('div#error_explanation')->count());
        $this->assertSame(1, $dom->filter('div.alert-danger')->count());
```
```php:/routes/web.php
Route::get('signup', [UsersController::class, 'create'])->name('signup');
Route::post('signup', [UsersController::class, 'store'])->name('signup');
Route::resource('users', UsersController::class)->except(["create", "store"]);
```
```html:/resources/views/users/create.blade.php
{{ Form::model($user, ['route' => 'signup']) }}
```
```php:/tests/Feature/UsersSignupTest.php
        $response = $this->followingRedirects()
                        ->post("signup", [
```

## 7.4 ユーザー登録成功

### 7.4.1 登録フォームの完成

```php:/app/Http/Conotrollers/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();
        return redirect()->route("users.show", $user);
    }
```

### 7.4.2 flash

```php:/app/Http/Coontrollers/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();
        session()->flash("message", ['success' => 'Welcome to the Sample App!']);
        return redirect()->route("users.show", $user);
    }
```
```html:/resources/views/layouts/application.blade.php
<div class="container">
    @if(session('message'))
        @foreach (session('message') as $message_type => $message)
            <div class="alert alert-{{ $message_type }}">{{ $message }}</div>
        @endforeach
    @endif
    @yield('content')
</div>
```

### 7.4.3 実際のユーザー登録

### 7.4.4 成功時のテスト

```php:/tests/Feature/UsersSignupTest.php
    public function testValidSignup()
    {
        $this->get(route('signup'));
        $count = User::all()->count();
        $response = $this->followingRedirects()
                         ->post('/signup', [
                                'name' => "Example User",
                                'email' => "user@example.com",
                                'password' => "password",
                                'password_confirmation' => "password"
                         ]);
        $this->assertSame($count + 1, User::all()->count());
        $response->assertViewIs("users.show");
    }
```

#### 課題

```php
$response->assertSeeText("Welcome to the Sample App!");
```

## 7.5 プロのデプロイ

### 7.5.1 本番環境でのSSL

ミドルウェア追加

```bash
sail artisan make:middleware ForceHttps
```
```php:/app/Http/Middleware/ForceHttps.php
class ForceHttps
{
    public function handle($request, Closure $next)
    {
        if (!$request->secure() && config('app.env') === 'production') {
            return redirect()->secure($request->getRequestUri(), 301);
        }

        return $next($request);
    }
}
```
```php:/app/Http/Kernel.php
    protected $middlewareGroups = [
        'web' => [
...
            \App\Http\Middleware\ForceHttps::class,
        ],
```

「クラウド」ロードバランサプロバイダを使用している場合は下記のように修正

```php:/app/Http/Middleware/TrustProxies.php
class TrustProxies extends Middleware
{
    protected $proxies = '*';
}
```

Sanctumのマイグレーションが不要な場合は下記を追記

```php:/app/Providers/AppServiceProvider
    public function register()
    {
        Sanctum::ignoreMigrations();
    }
```

The maximum column size is 767 bytes.の環境で動かす場合は下記のように修正

```php:/config/database.php
        'mysql' => [
            'driver'         => 'mysql',
            'url'            => env('DATABASE_URL'),
            'host'           => env('DB_HOST', '127.0.0.1'),
            'port'           => env('DB_PORT', '3306'),
            'database'       => env('DB_DATABASE', 'forge'),
            'username'       => env('DB_USERNAME', 'forge'),
            'password'       => env('DB_PASSWORD', ''),
            'unix_socket'    => env('DB_SOCKET', ''),
            'charset'        => 'utf8',
            'collation'      => 'utf8_unicode_ci',
            'prefix'         => '',
            'prefix_indexes' => true,
            'strict'         => true,
            'engine'         => null,
            'options'        => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],
```

### 7.5.2 本番環境用のWebサーバー

### 7.5.3 本番環境へのデプロイ

herokuにmysqlを追加してない場合は追加

# 7.6 最後に

# 参考
https://qiita.com/sutara79/items/9fd442a81001842aeba1
http://kayakuguri.github.io/blog/2017/10/27/laravel-https-loadbalancer/
https://qiita.com/Takao_/items/aeb3570b42a6aeb5461f
https://qiita.com/yukibe/items/5c1910e259ff4e6498db