Open2

Laravelに関するメモ

よしよし

モデル周り

マイグレーションファイルを変更してマイグレーションをかけるときは、これがいるっぽいのでいれておく。

$ composer require doctrine/dbal

UUID 導入

参考:Laravel Eloquent の主キー(ID)を UUID に変更する

インストール

Laravel のバージョンに合わせて、対応しているバージョンを入れる。

$ composer require goldspecdigital/laravel-eloquent-uuid:^7.0

モデルファイルの継承元を変更

User モデルの場合

use Illuminate\Foundation\Auth\User as Authenticatable;

use GoldSpecDigital\LaravelEloquentUUID\Foundation\Auth\User as Authenticatable;

User モデル以外のモデルの場合

生成コマンドを使うと、この継承元にしてくれる

$ php artisan uuid:make:model [モデル名]
use GoldSpecDigital\LaravelEloquentUUID\Database\Eloquent\Model;

timestamp 型を datetime 型へ

参考:Laravel MySQL timestamp型 2038年問題に対応する

マイグレーションファイルの timestamp の部分を置き換えるだけ

Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });

Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->dateTime('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->dateTime('created_at')->nullable();
            $table->dateTime('updated_at')->nullable();
        });

主キー名変更

参考:

Laravel のモデルのデフォルト主キー名はidであるため、変更するには以下の手順を行う。

  • マイグレーションファイルのカラム名を変更する
  • モデルファイルで、主キー名をオーバーライドする
protected $primaryKey = 'user_id';

リレーション設定

モデルファイルのリレーション定義では、キー名を自動推測するようになっているので、変更した主キーを使ってもらうよう指定しておく。

Users(1):Memos(多)の場合の例

User.php
     /**
     * リレーション - Memos
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function memos()
    {
        // デフォルトでは、自分自身のモデル名の「スネークケース」に _id のサフィックスをつけた名前と想定 
        return $this->hasMany('App\Memo', 'user_id');
    }
Memo.php
    /**
     * リレーション - users
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        // デフォルトでは、リレーションメソッド名に _id のサフィックスを付けた名前をデフォルトの外部キー名と想定
        // 親のモデルの主キーが id でない、もしくは子のモデルと違ったカラムで紐付けたい場合は、親テーブルのカスタムキー名を第3引数に指定
        return $this->belongsTo('App\User', 'user_id', 'user_id');
    }
よしよし

フォームリクエスト

リクエストのバリデーションはフォームリクエストで行うとよい。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class MemoRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // リクエストを許可するか
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        // バリデーションルール
        return [
            'title' => 'present|string|max:100',
            'content' => 'present|string|max:65535'
        ];
    }

    /**
     * Get custom messages for validator errors.
     *
     * @return array
     */
    public function messages()
    {
        // カスタムバリデーションメッセージ
        return [
            'title.string' => 'メモタイトルは文字列である必要があります。',
            'title.max' => 'メモタイトルの最大文字数100を超えています。',
            'content.string' => 'メモ内容は文字列である必要があります。',
            'content.max' => 'メモ内容の最大文字数65535を超えています。'
        ];
    }
}

リクエストパラメータを加工

Laravel 5.4 から ConvertEmptyStringsToNull ミドルウェアにより、リクエストパラメータの値に空文字があると null に変換される仕様になっている。
これは空文字のままデータ保存をしたい時に不都合なので、フォームリクエストで再度変換を書けるとよい。

  /**
     * Get data to be validated from the request.
     *
     * @return array
     */
    public function validationData()
    {
        リクエストパラメータを加工したい時に記述
        $all = parent::validationData();

        // 値がnullの時は空文字を入れる
        if (is_null($all['title'])) {
            $all['title'] = '';
        }
        if (is_null($all['content'])) {
            $all['content'] = '';
        }

        return $all;
    }

加工したパラメータを使うには、コントローラーでこのフォームリクエストを適用し、$request->validated()で使用する。
直接 $request->titleなどとすると、加工前のパラメータになるので注意。

    /**
     * (ログインユーザで)メモ作成API
     *
     * @param MemoRequest $request
     * @return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory
     */
    public function create(MemoRequest $request)
    {
        // パラメータは直接使うのでなく、フォームリクエストで加工したものを使う
        $memo = new Memo($request->validated());

        /** @var App\User $user */
        $user = Auth::user();
        $user->memos()->save($memo);

        return response($memo, 201);
    }

フォームリクエストのテスト

dataProvider を使えば、ひとつのテストメソッドで複数のパターンのテストを実行できる。

<?php

namespace Tests\Unit\Requests;

use App\Http\Requests\MemoRequest;
use Illuminate\Support\Facades\Validator;
// ファザードを使用するのでLaravelが提供するTestCaseを継承する
use Tests\TestCase;

class MemoRequestTest extends TestCase
{
    /**
     * MemoRequestのバリデーションテスト
     *
     * @param array 項目名の配列
     * @param array 値の配列
     * @param boolean 期待値(true:バリデーションNG、false:バリデーションOK)
     * @dataProvider dataMemoExample
     */
    public function testMemoRequest(array $keys, array $values, bool $expect)
    {
        $dataList = array_combine($keys, $values);

        $request = new MemoRequest();
        $rules = $request->rules();
        $validator = Validator::make($dataList, $rules);

        // バリデーションに引っ掛かるとtrue
        $result = $validator->fails();
        $this->assertEquals($expect, $result);
    }

    public function dataMemoExample()
    {
        return [
            'OK' => [
                ['title', 'content'],
                ['テスト タイトル', 'テスト 内容'],
                false
            ],
            'OK:空文字' => [
                ['title', 'content'],
                ['', ''],
                false
            ],
            'NG:titleが存在しない' => [
                ['content'],
                ['テスト 内容'],
                true
            ],
            'NG:titleが文字列でない' => [
                ['title', 'content'],
                [123, 'テスト 内容'],
                true
            ],
            'NG:titleがnull' => [
                ['title', 'content'],
                [null, 'テスト 内容'],
                true
            ],
            'OK:titleが100文字以内' => [
                ['title', 'content'],
                [str_repeat('a', 100), 'テスト 内容'],
                false
            ],
            'NG:titleが100文字より多い' => [
                ['title', 'content'],
                [str_repeat('a', 101), 'テスト 内容'],
                true
            ],
            'NG:contentが存在しない' => [
                ['title'],
                ['テスト タイトル'],
                true
            ],
            'NG:contentが文字列でない' => [
                ['title', 'content'],
                ['テスト タイトル', 123],
                true
            ],
            'NG:contentがnull' => [
                ['title', 'content'],
                ['テスト タイトル', null],
                true
            ],
            'OK:contentが65535文字以内' => [
                ['title', 'content'],
                ['テスト タイトル', str_repeat('a', 65535)],
                false
            ],
            'NG:contentが65535文字より多い' => [
                ['title', 'content'],
                ['テスト タイトル', str_repeat('a', 65536)],
                true
            ],
        ];
    }
}