20200925のlaravelに関する記事は5件です。

[Laravel] 特定のマイグレーションだけをマイグレート、ロールバックしたい!!!

初めに

入社してから与えられた課題で、特定のマイグレーション以外をロールバックして、修正、特定のマイグレーション以外を再び実行するという操作をしたくなったのですが、なかなか情報が見つからず、たくさんのサイトを見て得た情報と最終的に見つけた自己流の解決策を紹介します。

マイグレーションは連続的なものであるという前提

いざというときにLaravelがデータを安全にロールバックできるようにするための仕組みがマイグレーションというものらしいです。
だから、マイグレーションはそれらが作成された順にひとつずつ実行しています。

この前提から特定のマイグレーションだけを実行することは邪道なのではという疑念を抱きつつ、解決策へ、、、

解決策

特定のマイグレーションだけをマイグレートする

php artisan migrate:refreshを応用して使います。

マイグレーションを指定したファイルから1つだけマイグレートする
php artisan migrate:refresh  --step=1 --path=/database/migrations/ファイル名

step=○の数字を変えることで、マイグレートするファイルの数を変更できます。

特定のマイグレーションだけをロールバックする

コメントアウトを使います。

public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            #$table->increments('id');
            #$table->string('name');
            #$table->string('airline');
            #$table->timestamps();
        });
    }
    public function down()
    {
        #Schema::drop('flights');
    }

ロールバックしたくないファイルを上記のようにコメントアウトし、ロールバックすると、このファイルが無視されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ポエム】Laravelのバリデーションルールの指定方法について考える

今回は ポエム です。技術的には全然高度なお話ではなく、ただ、気持ち悪かったのをどうにかしたかったというお話です。ポエムが苦手な方は「戻る」ボタンを押すことをオススメします。

前提

  • PHP 7.4.9
  • Laravel 8.5.0

3行でわかるまとめ

Laravel のバリデーションルールの指定方法が気持ち悪い(とくにクラス定数を使ったとき)。
いろいろ試してみたが、気持ち悪さを解消できなかった。
自作パッケージを作って、ようやく気持ち悪さを解消できた。

クラス定数問題

Laravel では、バリデーションルールは 文字列 で指定します。例えば、 title 属性に「必須入力であること」と「1000文字以下であること」というルールを指定する場合は、次のように書きます。

$request->validate([
    'title' => 'required|max:1000',
]);

| で区切らずに 配列 で指定することもできます。

$request->validate([
    'title' => ['required', 'max:1000'],
]);

しかし、マジックナンバーは極力回避したいので 1000 を変数にします。PHP の仕様により、次のように文字列の中に変数を埋め込むことができます。

$titleMaxLength = 1000;

$request->validate([
    'title'     => ['required', "max:{$titleMaxLength}"],
]);

$titleMaxLength は処理の中で変化するものではありません。こういうときは、できれば 定数 にしたいところです。

しかし、定数は文字列の中で展開されないため、次のように書いてもうまく動きません。

class TodoController extends Controller
{
    const TITLE_MAX_LENGTH = 1000;

    public function store(Request $request)
    {
        // この書き方では期待通りの動きをしない。
        $request->validate([
            'title'     => ['required', "max:{self::TITLE_MAX_LENGTH}"],
        ]);

        // ...
    }
}

もちろん、文字列演算子 を使って、文字列に定数を結合すれば、うまく動きます。

class TodoController extends Controller
{
    const TITLE_MAX_LENGTH = 1000;

    public function store(Request $request)
    {
        // この書き方ならば期待通りの動きをする。
        $request->validate([
            'title'     => ['required', 'max:' . self::TITLE_MAX_LENGTH],
        ]);

        // ...
    }
}

このように書くのが定石でしょう。私も実際に仕事でそう書いたことがありますし、それが悪いというつもりは全くありません。

ただ、個人的にはどうしても居心地の悪さを感じるのです。例えば、「10文字以上であること」というルールを追加すると、このようになります。

class TodoController extends Controller
{
    const TITLE_MIN_LENGTH = 10;
    const TITLE_MAX_LENGTH = 1000;

    public function store(Request $request)
    {
        $request->validate([
            'title'     => ['required', 'min:' . self::TITLE_MIN_LENGTH, 'max:' . self::TITLE_MAX_LENGTH],
        ]);

        // ...
    }
}

随分と横に長くなってしまいました。でも、あらかじめ変数に入れておけば改善することができます。

class TodoController extends Controller
{
    const TITLE_MIN_LENGTH = 10;
    const TITLE_MAX_LENGTH = 1000;

    public function store(Request $request)
    {
        $titleRules = [
            'required',
            'min:' . self::TITLE_MIN_LENGTH,
            'max:' . self::TITLE_MAX_LENGTH,
        ];

        $request->validate([
            'title'     => $titleRules,
        ]);

        // ...
    }
}

それでも、個人的にはどうもしっくり来ません。 'max:' . self::TITLE_MAX_LENGTH という風に文字列結合しないといけないのでしょうか?

メソッド化

汚いものには蓋をする。 メソッド化の目的をそのように説明したら、きっと四方八方から石を投げつけられると思います。でも、試して見る価値はあるのではないかと思ったのです。

getConstant() というメソッドを作り、引数で渡された定数名をもとに、定数の値を取得します。その中で使っている constant()PHP マニュアル)は定数の値を返す標準関数です。定数が見つからなかった場合に E_WARNING レベルのエラーを発生させ、Laravel では例外が送出されます。1

class TodoController extends Controller
{
    const TITLE_MIN_LENGTH = 10;
    const TITLE_MAX_LENGTH = 1000;

    public function store(Request $request)
    {
        $titleRules = [
            'required',
            "min:{$this->getConstant('TITLE_MIN_LENGTH')}",
            "max:{$this->getConstant('TITLE_MAX_LENGTH')}",
        ];

        $request->validate([
            'title'     => $titleRules,
        ]);

        // ...
    }

    public function getConstant($constantName) {
        return constant('self::' . $constantName);
    }
}

たしかに、これは動きます。でも、「いや、そこまでしなくても...」という声が聞こえてきそうです。頑張ったわりに、そこまで見やすくもないですし...

余談1

ちなみに、クラス定数を取得するならば、 ReflectionClass::getConstant()PHP マニュアル)を使うこともできます。

public function getConstant($constantName) {
    return (new \ReflectionClass(__CLASS__))->getConstant($constantName);
}

しかし、定数が見つからなかった場合の動作が constant() と異なります。 constant()E_WARNING レベルのエラーを発生させます。一方、ReflectionClass::getConstant() はエラーを発生させないため( FALSE を返すだけです)、Laravel でも例外になりません。あえて ReflectionClass::getConstant() を使う理由は無いように思いました。23

パッケージ化

話が脱線しましたが、そもそも、どこが嫌だったかと言うと、 文字列結合 していることでした。この点をもう少し考えてみます。

'max:1000' というバリデーションルールは、以下の2つの部分から構成されています。

  • ルール名
    • max
  • ルールの引数
    • 1000

この2つを : で繋いで1つの文字列にしなければならないところに、そもそも無理があるように思います。素直に、それぞれを分けて指定できるようにすればいいのではないでしょうか?

例えば、 addRule(適用対象, ルール名, ルールの引数) という風に書くことができれば、クラス定数も文字列結合することなく渡すことができます。以下の例では、 addRule() でルールを追加し、 format() で Laravel のバリデータに渡せる配列形式に変換してから、バリデーションを実行しています。

class TodoController extends Controller
{
    const TITLE_MAX_LENGTH = 1000;

    public function store(Request $request)
    {
        // title 属性は必須であること。
        $rules = ValidationRuleFormatter::addRule('title', 'required')
            // title 属性は1000文字以下であること。
            ->addRule('title', 'max', self::TITLE_MAX_LENGTH)
            // Laravel のバリデータに合うようにフォーマットする。
            ->format();

        // バリデーションを実行する。
        $request->validate($rules);

        // ...
    }
}

そして、これを Laravel の パッケージ として作ってみました。以下のように composer でインストールすれば、すぐにお使いいただけます(笑)

composer require aminevsky/laravel-validation-rule-formatter

おそらく需要は皆無だと思っていますが、本人は長年のモヤモヤが解決して満足しています(自己満足)

おしまい。

余談2

なんだかんだ初めて Laravel のパッケージを作ったのですが、約200行ほどのちゃっちいプログラムだったこともあってか、意外と簡単に作ることができました。4

パッケージ開発については Laravel ドキュメント もありますが、 Laravel Package Development が一番よくまとまっているように感じました。

PSR-4 や、composer で ローカルディレクトリをリポジトリに指定する方法 に触れられるのも、パッケージ開発ならではだと思います。


  1. 通常、PHP では E_WARNING が発生しても、処理は継続します。しかし、Laravel では Illuminate\Foundation\Bootstrap\HandleExceptionbootstrap()error_reporting(-1) と設定されているため、全てのエラーと警告が表示されます(PHP マニュアル)。さらに HandleException クラスの handleError()ErrorException へ変換されるため、例外が引き起こされたように見えるわけです。 

  2. コンパイルが無く、実行時にはじめて間違いに気づくことが多い PHP としては、こういう挙動はあまり嬉しくありません。そもそも ReflectionClass インスタンスを作るほどのことでも無いように思えますし。 

  3. error_reporting(-1) に設定している Laravel としては関係ありませんが、そもそも constant()E_WARNING レベルのエラーを出力するだけで処理が継続するのはいいのか?という疑問はあります。ただし、PHP 8 では RFC: Reclassifying engine warnings が採択されたため、 Error 例外が発生するようになるようです。3v4l.org での 動作例 も参照してください。 

  4. 本来ならば、このくらいのプログラムは1日で完成させないといけないところですが、3日もかかってしまったのは、全く恥ずべきことだと思います。いやテストコードも無く、クラス分けもしていない状態であれば1日目で出来上がっていたのですが、クラス分けで悩んだ時間のなんと長かったことか... しかもあれだけ悩んだわりに、出来上がったものを見ると、自分でも「この程度かよ」と思うレベルでしかない。一応5年以上プログラミングをやっていて、未だにこの程度ですからね。本当に向いていないんだろうな。無職なので、いろいろ求人を見るけど、やっぱりどこもいい会社は求めるレベルが高くて、私みたいな雑魚はお呼びでない。「モダンな環境でやりたいな」とか「魅力的な事業をやっている会社で働きたいな」とか理想は多々あるけれども、ようやくわかったよ。そういうのは能力が無いと得られないものなんだということを。私みたいな能力の無い者に残された道は2つしかない。自分の理想を捨てて働きたくないところで我慢して働くか、潔く退場するか。そんなことばかり考えていると、ときどき、頭がパニックになったり過呼吸を引き起こしたりして(とくに午前中になりやすい)、Qiita すら昨日は1文字も書けず、今日、ようやく勢いで、どうにか1つ書けたわけですね。はぁ... 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Laravel8]ログイン機能付きでプロジェクトを作成するまで

  1. composerでプロジェクトを作成する
  2. Jetstreamをインストールする
  3. LivewireかInertiaのどっちかをインストールする
  4. ビルドする
  5. データベースを作る
  6. .envファイルでデータベースを変更する
  7. マイグレーションする

環境:XAMPP for Windows, Laravel Framework 8.6.0

1. composerでプロジェクトを作成する

まずはcomposerを使ってプロジェクトを作成します。
以下のコードの場合、『your-project』という名前のプロジェクトを作成します。

composer create-project --prefer-dist laravel/laravel your-project

2. Jetstreamをインストールする

cdコマンドでプロジェクト内に移動したら、Jetstreamをインストールします。

composer require laravel/jetstream

3. LivewireかInertiaのどっちかをインストールする

LivewireかInertiaのどっちかを選択してインストールします。
--teams は、チーム機能を付ける為のオプションです。必要なければ取って良し。

php artisan jetstream:install livewire --teams

もしくは、

php artisan jetstream:install inertia --teams

4. ビルドする

ビルドします。

npm install && npm run dev

5. データベースを作る

私の場合、今回XAMPPを使っているので、XAMPPでサクッとデータベースを作っておきます。

6. .envファイルでデータベースを変更する

.envファイルのデータベース名を忘れずに変更しておきます。

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_project ←ここ
DB_USERNAME=root
DB_PASSWORD=

7. マイグレーションする

マイグレーションします。

php artisan migrate

すると、以下画像のように8個のテーブルが入った状態で出来上がります。
image.png

artisanコマンドでサーバ起動後にトップページを確認すると、以下画像のように右上にLoginとRegisterが表示されます。

php artisan serve

image.png

以上です。

参考サイト:
https://jetstream.laravel.com/1.x/installation.html
https://blog.capilano-fw.com/?p=7827

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPUnitでスローされた例外をテストする

結論

以下のように書こう?

    /**
     * @test
     * @return void
     */
    public function insertHoge_異常_{エラー内容}()
    {
        // スローを期待する例外の内容
        $this->expectException(例外クラス);
        $this->expectExceptionMessage(例外メッセージ);

        $param = 不正な値;

        // 例外をスローするテスト対象メソッド
        $this->reportService->insertHoge($param);
    }

事例

    /**
     * @test
     * @return void
     */
    public function addRecord_異常_外部キー不正()
    {
        $this->expectException(Exception::class);
        $this->expectExceptionMessage('a foreign key constraint fails');

        $id = 'invalidId';

        $this->reportService->insertHoge($id);
    }

Tips

  • expectExceptionMessage()による例外メッセージ検証は、部分一致で判定できる
  • timestampの値を観点にしないテスト等、
    観点に含まれない無いが実行タイミングによって変化する値を含むエラーメッセージの検証を行う場合は便利

例)
 上記事例のテスト実行時に返却される例外メッセージ全文

'SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`hoge_db`.`hoge_table`, CONSTRAINT `user_id_foreign` FOREIGN KEY (`id`) REFERENCES `users` (`id`)) (SQL: insert into `hoge` (`id`, `updated_at`, `created_at`) values (0, 2020-09-25 11:31:05, 2020-09-25 11:31:05))'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 乱数の生成

目的

  • Laravelで桁数を指定した乱数の生成方法をまとめる

  • 下記のようにヘルパ関数を用いて乱数生成する。

    Str::random(生成桁数);
    
  • 本処理はPHPのrandom_bytes関数を使用して乱数を生成している。

  • 余談であるがPHPのrandom_bytes関数が使用する不規則性のソースはOSやプラットフォームによって異なっているらしい。どれも使えないとき(例外時)は基底クラスを投げるらしい。

    • Windows: CNG-API
    • Linux: getrandom(2)
    • その他: /dev/urandom # 具体例
  • 30桁の乱数を生成し$strに格納する処理を下記に記載する。

    $str = Str::random(30);
    

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む