takanori-matsushita / laravel-practice

http://laraveltutorial.herokuapp.com/
0 stars 0 forks source link

rails tutorial 6章をlaravelで実装 #4

Open takanori-matsushita opened 4 years ago

takanori-matsushita commented 4 years ago

第6章ユーザーのモデルを作成する

branch: modeling-users

takanori-matsushita commented 4 years ago

6.1 Userモデル

6.1.1 データベースの移行

リスト 6.1: Userモデルを生成する LaravelでははじめからUserモデルが用意されているので、そちらを使う。 app/User.php

takanori-matsushita commented 4 years ago

リスト 6.2: (usersテーブルを作るための) Userモデルのマイグレーション database/migrations/create_users_table.php が元から用意されているのでこちらを使用する

.envのデータベースの設定を変更(今回はpostgreSQLを使用する)

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=データベース名
DB_USERNAME=ユーザー名
DB_PASSWORD=パスワード

マイグレーション実行 php artisan migrate

もし上のコマンドでうまくいかないときは、設定のキャッシュクリアしてもう一度マイグレーションを実行する php artisan config:clear

takanori-matsushita commented 4 years ago

演習

  1. 省略
  2. $ php artisan migrate:rollback
takanori-matsushita commented 4 years ago

6.1.2 modelファイル

app/User.php に最初から用意してある。

takanori-matsushita commented 4 years ago

演習

  1. php arisan tinker
    >>> get_parent_class($user)
    => "Illuminate\Foundation\Auth\User"
    >>> $parentClass = get_parent_class($user)
    => "Illuminate\Foundation\Auth\User"
  2. >>> get_parent_class($parentClass)
    => "Illuminate\Database\Eloquent\Model"
takanori-matsushita commented 4 years ago

6.1.3 ユーザーオブジェクトを作成する

php arisan tinker

>>> $user = new User();
=> App\User

>>> $user->name = 'Michael Hartl'
=> "Michael Hartl"
>>> $user->email = 'mhartl@example.com'
=> "mhartl@example.com"
>>> $user->password = 'password'
=> "password"
>>> $user->save()
=> true

>>> $user
=> App\User {#3047
     updated_at: "2020-04-12 18:04:18",
     created_at: "2020-04-12 18:04:18",
     name: "Michael Hartl",
     email: "mhartl@example.com",
     id: 3,
   }
takanori-matsushita commented 4 years ago

カラム情報の取り出し

>>> $user->name
=> "Michael Hartl"
>>> $user->email
=> "mhartl@example.com"
>>> $user->updated_at
=> Illuminate\Support\Carbon @1586714658 {#3043
     date: 2020-04-12 18:04:18.0 UTC (+00:00),
     timezone: "UTC",
   }

createメソッド

$foo = User::create([
  "name" => "A Nother",
  "email" => "another@example.org",
  "password" => "password"
])
takanori-matsushita commented 4 years ago

レコードの削除

>>> $foo->delete()
=> true
takanori-matsushita commented 4 years ago

6.1.4 ユーザーオブジェクトを検索する

>>> User::find(1)
=> App\User {#3068
     id: 1,
     name: "Michael Hartl",
     email: "mhartl@example.com",
     email_verified_at: null,
     created_at: "2020-04-12 18:16:49",
     updated_at: "2020-04-12 18:16:49",
   }

findメソッド

>>> User::find(3)
=> null

whereメソッド

>>> User::where("email", "mhartl@example.com")->first()=> App\User {#3064     id: 1,
     name: "Michael Hartl",
     email: "mhartl@example.com",
     email_verified_at: null,
     created_at: "2020-04-12 18:16:49",
     updated_at: "2020-04-12 18:16:49",
   }

firstメソッド

>>> User::first()
=> App\User {#3042
     id: 1,
     name: "Michael Hartl",
     email: "mhartl@example.com",
     email_verified_at: null,
     created_at: "2020-04-12 18:16:49",
     updated_at: "2020-04-12 18:16:49",
   }

allメソッド

>>> User::all()
=> Illuminate\Database\Eloquent\Collection {#3071
     all: [
       App\User {#3043
         id: 1,
         name: "Michael Hartl",
         email: "mhartl@example.com",
         email_verified_at: null,
         created_at: "2020-04-12 18:16:49",
         updated_at: "2020-04-12 18:16:49",
       },
       App\User {#3060
         id: 2,
         name: "A Nother",
         email: "another@example.org",
         email_verified_at: null,
         created_at: "2020-04-12 18:25:48",
         updated_at: "2020-04-12 18:25:48",
       },
     ],
   }
takanori-matsushita commented 4 years ago

演習

  1. >>> User::where("name", "Michael Hartl")->first()
    => App\User {#3070
     id: 1,
     name: "Michael Hartl",
     email: "mhartl@example.com",
     email_verified_at: null,
     created_at: "2020-04-12 18:16:49",
     updated_at: "2020-04-12 18:16:49",
    }
  2. >>> $all = User::all()
    => Illuminate\Database\Eloquent\Collection {#3071
     all: [
       App\User {#3043
         id: 1,
         name: "Michael Hartl",
         email: "mhartl@example.com",
         email_verified_at: null,
         created_at: "2020-04-12 18:16:49",
         updated_at: "2020-04-12 18:16:49",
       },
       App\User {#3060
         id: 4,
         name: "A Nother",
         email: "another@example.org",
         email_verified_at: null,
         created_at: "2020-04-12 18:25:48",
         updated_at: "2020-04-12 18:25:48",
       },
     ],
    }
    >>> get_class($all)
    => "Illuminate\Database\Eloquent\Collection"
  3. >>> count($all)
    => 2
takanori-matsushita commented 4 years ago

6.1.5 ユーザーオブジェクトを更新する

reload的なメソッドは探したが用意されていないかも

takanori-matsushita commented 4 years ago

演習

  1. >>> $user->name = "taka"
    => "taka"
    >>> $user->save();
    => true
  2. >>> $user->update(["email"=>"taka@mail.com"]);
    => true
  3. >>> $user->created_at = $user->created_at->subYear()
takanori-matsushita commented 4 years ago

6.2 ユーザーを検証する

6.2.1 有効性を検証する

testファイルの作成 php artisan make:test UserTest リスト 6.4: デフォルトのUserテスト (モックのみ)

class UserTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}
takanori-matsushita commented 4 years ago

フォームリクエストを作成し、以下のように記述する。ここでは、まだバリデーションの設定をしない。 [参考]バリデーションテスト php artisan make:request app/Http/Requests/UserFormRequest.php rulesメソッドを以下のように書く。

  public function rules()
  {
    return [
      "name" => "",
    ];
  }

リスト 6.5: 有効なUserかどうかをテストする green tests/Feature/UserTest.php

class UserTest extends TestCase
{
  /**
   * A basic feature test example.
   * 
   * @dataProvider dataProviderUser
   * @return void
   */

  public function testShouldBeValid($name, $email, $expect)
  {
    $request = new UserFormRequest();
    $rules = $request->rules();
    $item = ["name" => $name, "email" => $email];
    $validator = Validator::make($item, $rules);
    $result = $validator->passes();
    $this->assertEquals($expect, $result);
  }

  public function dataProviderUser()
  {
    return [
      'true' => ["Example User", "user@example.com", true],
    ];
  }
}

データプロバイダを使うと、同じテストコードに違うパラメータを与えることができる。 [参考]データプロバイダとは

takanori-matsushita commented 4 years ago

リスト 6.6: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

6.2.2 存在性を検証する

リスト 6.7: name属性にバリデーションに対するテスト red tests/Feature/UserTest.php dataProviderUserメソッドに以下の文を追加 'name required error' => ["", "user@example.com", false],

takanori-matsushita commented 4 years ago

リスト 6.8: red vendor/bin/phpunit この時点ではバリデーションを実行した結果がtrueで $this->assertEquals($expect, $result); この部分で値が一致しないためFailuresになる。

takanori-matsushita commented 4 years ago

リスト 6.9: name属性の存在性を検証するgreen app/Http/Requests/UserFormRequest.php

  public function rules()
  {
    return [
      "name" => "required",
    ];
  }
takanori-matsushita commented 4 years ago

tinkerでの確認方法 tinkerでバリデーション

>>> Validator::make(["name"=>""],["name"=>"required"])->errors()->toArray()
=> [
     "name" => [
       "The name field is required.",
     ],
   ]
takanori-matsushita commented 4 years ago

リスト 6.10: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

リスト 6.11: email属性の検証に対するテストred

dataProviderUserメソッドに以下を追加 'email required error' => ["Example User", "", false],

takanori-matsushita commented 4 years ago

リスト 6.12: email属性の存在性を検証するgreen app/Http/Requests/UserFormRequest.php

  public function rules()
  {
    return [
      "name" => "required",
      "email" => "required", //追加
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.13: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

演習

  1. >>> Validator::make(["name"=>"","email"=>""],["name"=>"required","email"=>"required"])->errors()->toArray()
    => [
     "name" => [
       "The name field is required.",
     ],
     "email" => [
       "The email field is required.",
     ],
    ]
  2. >>> Validator::make(["name"=>"","email"=>""],["name"=>"required","email"=>"required"])->errors()->first(email)
    => "The email field is required."
takanori-matsushita commented 4 years ago

6.2.3 長さを検証する

リスト 6.14: nameの長さの検証に対するテスト red tests/Feature/UserTest.php dataProviderメソッドに追加

'name max_length error' => [str_repeat("a", 51), "user@example.com", false],
'email max_length error' => ["Example User", str_repeat("a", 244) . "@example.com", false],
takanori-matsushita commented 4 years ago

リスト 6.15: red vendor/bin/phpunit

takanori-matsushita commented 4 years ago

リスト 6.16: name属性に長さの検証を追加するgreen

  public function rules()
  {
    return [
      "name" => "required|max:50",
      "email" => "required|max:255",
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.17: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

演習

  1. 省略
  2. >>> Validator::make(["name"=>str_repeat("a",55)],["name"=>"max:50"])->errors()->toArray()
    => [
     "name" => [
       "The name may not be greater than 50 characters.",
     ],
    ]
takanori-matsushita commented 4 years ago

6.2.4 フォーマットを検証する

リスト 6.18: 有効なメールフォーマットをテストするgreen tests/Feature/UserTest.php dataProviderUserメソッドに追加

'email user@example.com' => ["Example User", "user@example.com", true],
'email USER@foo.COM' => ["Example User", "USER@foo.com", true],
'email A_US-ER@foo.bar.org' => ["Example User", "A_US-ER@foo.bar.org", true],
'email first.last@foo.jp' => ["Example User", "first.last@foo.jp", true],
'email alice+bob@baz.cn' => ["Example User", "alice+bob@baz.cn", true],
takanori-matsushita commented 4 years ago

リスト 6.19: メールフォーマットの検証に対するテスト red tests/Feature/UserTest.php dataProviderUserメソッドに追加

'email  user@example,com' => ["Example User", "user@example,com", false],
'email  user_at_foo.org' => ["Example User", "user_at_foo.org", false],
'email  user.name@example.' => ["Example User", "user.name@example.", false],
'email  foo@bar_baz.com' => ["Example User", "foo@bar_baz.com", false],
'email  foo@bar+baz.com' => ["Example User", "foo@bar+baz.com", false],
takanori-matsushita commented 4 years ago

リスト 6.20: red vendor/bin/phpunit

takanori-matsushita commented 4 years ago

リスト 6.21: メールフォーマットを正規表現で検証するgreen Laravelでは、バリデーションにemail:filterを指定すると、メールアドレスのフォーマットかチェックできる。 app/Http/Requests/UserFormRequest.php

  public function rules()
  {
    return [
      "name" => "required|max:50",
      "email" => "required|max:255|email:filter",
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.22: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

演習

  1. スクリーンショット 2020-04-16 2 41 24
  2. Laravelのバリデーションemail:filterでは、これも無効なアドレスとして認識するため、省略

  3. 変更前

    スクリーンショット 2020-04-16 2 41 01

変更後

スクリーンショット 2020-04-16 2 42 16
takanori-matsushita commented 4 years ago

6.2.5 一意性を検証する

リスト 6.24: 重複するメールアドレス拒否のテスト red 実際にデータベースに保存されているデータを取得し、重複するかチェックする。 tests/Feature/UserTest.php

  public function dataProviderUser()
  {
    $this->createApplication();
    $user = User::find(1)->toArray();
    return [
      'email unique error' => [$user["name"], $user["email"], false],
    ];
  }

[参考]Error: Call to a member function connection() on nullの対策

takanori-matsushita commented 4 years ago

リスト 6.25: メールアドレスの一意性を検証する green

  public function rules()
  {
    return [
      ~省略~
      "email" => "required|max:255|email:filter|unique:users",
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.26: 大文字小文字を区別しない、一意性のテスト green tests/Feature/UserTest.php emailテスト用のデータプロバイダを用意する。

  /**
   * A basic feature test example.
   * 
   * @dataProvider emailDataProvider
   * @return void
   */
  public function testEmailUpper($email, $expect)
  {
    $emailUpper = strtoupper($email);
    $result = $this->stringUpper($emailUpper);
    $this->assertEquals($expect, $result);
  }

  protected function stringUpper($emailUpper)
  {
    $users = User::all();
    foreach ($users as $user) {
      if ($emailUpper === strtoupper($user["email"])) {
        return true;
      }
    }
    return false;
  }

~中略~

  public function emailDataProvider()
  {
    $user = User::find(1)->toArray();
    return [
      'email upper lower passed' => ["user@example.com", false],
      'email upper lower error' => [$user["email"], true],
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.27: メールアドレスの大文字小文字を無視した一意性の検証 green 上記でgreenのため省略

takanori-matsushita commented 4 years ago

リスト 6.28: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

ここの操作は不要(参考までに) マイグレーションの作成 Laravelでは、はじめから用意されていたUserモデルのマイグレーションファイルにuniqueが設定してあるため、uniqueの設定は不要。以下は参考までに。 php artisan make:migration add_index_to_users_email

リスト 6.29: メールアドレスの一意性を強制するためのマイグレーション 以下はuniqueを指定する方法。 database/migrations/[timestamp]_add_index_to_users_email

    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->unique('email');
        });
    }
takanori-matsushita commented 4 years ago

リスト 6.30: Userのデフォルトfixture red リスト 6.31: 空のfixtureファイルgreen 省略

takanori-matsushita commented 4 years ago

リスト 6.32: email属性を小文字に変換してメールアドレスの一意性を保証する green Laravelで用意されているUserモデルには、初めから対応しているので省略。

takanori-matsushita commented 4 years ago

演習

  1. 小文字のテスト

    public function testEmaillower($email, $expect)
    {
    $emailLower = strtolower($email);
    $result = $this->stringLower($emailLower);
    $this->assertEquals($expect, $result);
    }
    
    protected function stringLower($emailUpper)
    {
    $users = User::all();
    foreach ($users as $user) {
      if ($emailUpper === strtolower($user["email"])) {
        return true;
      }
    }
    return false;
    }
  2. 省略

takanori-matsushita commented 4 years ago

6.3 セキュアなパスワードを追加する

6.3.1 ハッシュ化されたパスワード 6.3.2 ユーザーがセキュアなパスワードを持っている

Laravelでは用意されているUserモデルに実装済みのため、省略

takanori-matsushita commented 4 years ago

6.3.3 パスワードの最小文字数

リスト 6.41: パスワードの最小文字数をテストする red tests/Feature/UserTest.php testPasswordInvalidメソッドとpasswordDataProviderを追加

  /**
   * A basic feature test example.
   * 
   * @dataProvider passwordDataProvider
   * @return void
   */

  public function testPasswordInvalid($password, $expect)
  {
    $request = new UserFormRequest();
    $rules = $request->rules();
    $item = ["name" => "test", "email" => "example@laravel.com", "password" => $password];
    $validator = Validator::make($item, $rules);
    $result = $validator->passes();
    $this->assertEquals($expect, $result);
  }

  public function passwordDataProvider()
  {
    return [
      'blank password' => [str_repeat(" ", 6), false],
      'minimum length password' => [str_repeat("a", 5), false],
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.42: セキュアパスワードの完全な実装 green app/Http/Requests/UserFormRequest.php

  public function rules()
  {
    return [
      "name" => "required|max:50",
      "email" => "required|max:255|email:filter|unique:users",
      "password" => "required|min:6"  //追加
    ];
  }
takanori-matsushita commented 4 years ago

リスト 6.43: green vendor/bin/phpunit

takanori-matsushita commented 4 years ago

演習

  1. 上記テストで確認済みのため省略

  2. tinkerで確認

    >>> Validator::make(["password"=>"aaa"],["password"=>"min:6"])->errors()->toArray()
    => [
     "password" => [
       "The password must be at least 6 characters.",
     ],
    ]
takanori-matsushita commented 4 years ago

6.3.4 ユーザーの作成と認証

実際のデータベースに保存していたため、以下のコマンドでマイグレーションのやり直し php artisan migrate:refresh

tinkerでデータの登録 php artisan tinker

>>> $user = new User()
=> App\User {#3058}
>>> $user->name = "Michael Hartl"
=> "Michael Hartl"
>>> $user->email = "mhartl@example.com"
=> "mhartl@example.com"
>>> $user->password = "foobar"
=> "foobar"
>>> $user->save()
=> true