カテゴリーアーカイブ Laravel

著者:杉浦

【Laravel】任意のseederを動かすartisanコマンドを追加するlaravel-artisan-seeder-selectorの紹介

 Laravelにはシーディングという仕組みがあります。

シーダ(初期値設定)クラスを使用し、テストデーターをデーターベースに設定するシンプルな方法もLaravelには備わっています。全シーダクラスはdatabase/seedsに保存します。シーダクラスには好きな名前を付けられます。しかしUsersTableSeederなどのような分かりやすい規則に従ったほうが良いでしょう。デフォルトとしてDatabaseSeederクラスが定義されています。このクラスからcallメソッドを使い他の初期値設定クラスを呼び出すことで、シーディングの順番をコントロールできます。
データベース:シーディング 5.8 Laravel

 シーディングはartisan db:seedコマンドを叩くだけでデータベースに初期データを挿入する仕組みで、テストデータの生成やマスタなどの初期データの生成のために使われます。この二つの生成はテスト環境用、本番環境用と明確に使いたい状況の異なる生成です。そのためシーダークラスを分けてつくり両立させます。大体DatabaseSeederクラスに本番環境用データSeederの呼び出しメソッド、謎のテストやデプロイ用らしきクラスにテストデータ生成用メソッドが含まれています。これを–class=[className]オプションで使い分けます。
 –class=[クラス名]オプションは便利なのですがクラス名を知らなければならないという障壁があります。laravel-artisan-seeder-selectorはその障壁すら取り除きます。
Asperhsu/laravel-artisan-seeder-selector: Artisan command for DB Seeder selector
 インストール方法はapp/Console/Commands以下にSeederSelector.php1ファイルをコピペするだけです。もはやスニペットですからそんなものです。そうするとartisan db:seed-selectコマンドが実行できるようになり、次の様にクラス名の代わりに番号で選択する画面が出せます。

 db:seedは手作業で打つことの多いコマンドでもありますからGUI的に使えるこういったコードはありがたいです。

  • この記事いいね! (1)
著者:杉浦

【Laravel】【Vue】propsを使わずVue上で組んだページ部にLaravelのBladeから値を表示する

 LaravelからVueに値を渡す時によくやる手法は次です。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
            v-bind:hoge="{{json_encode($hoge)}}"
            v-bind:fuga="{{json_encode($fuga)}}"
            ...
            ...
        ></upload-image>
<template>
  <div>
    {{ hoge }}
    {{ fuga }}
    ...
    ...
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
    hoge: {type: String, required: true},
    fuga: {type: String, required: true},
    ...
    ...
  },

 引数をjson化してpropsとして渡し、Vue内のテンプレートに埋め込みます。やり方はシンプルなのですが情報が増えるにつれpropsの肥大化がひどいことになります。なんらかのオブジェクトにまとめるのも手ですが、それはそれでバリデーションが辛くなります。この問題の回答の一つがVueのslotの仕組みを使うことです。
スロット — Vue.js
 slotは指定された位置のHTMLコードをVueコンポーネントを呼び出す階層から決定するための仕組みです。そのため先ほどの例ならば次の様に書けます。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
        >
          <div slot="slot-no-namae">
              {{ $hoge }}
              {{ $fuga }}
              ...
              ...
          </div>
        </upload-image>
<template>
  <div>
    <slot name="slot-no-namae"/>
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
  },

 Blade上のHTMLエスケープエコーの短縮構文{{}}を実行してHTMLコードを生成。それのHTMLコードをslotに割り当てます。これにより、propsを介することなくPHP内部で扱っていた変数の値を持つHTMLコードをVue上で組んでwebページのテンプレート内に組み込めました。propした値を後から変更しない、Vue内の関数で参照する予定が無い、といった値は大体この方法に組み込むとスッキリしつつ問題のないコードを作るのに貢献してくれます。

  • この記事いいね! (1)
著者:杉浦

【Laravel】Laravelのレスポンスは暗黙の型変換が強い

 Laravelはコントローラが何を返そうがサーバがクライアントにレスポンスを返せる様にがんばります。暗黙の型変換を行うPHPの鑑ですね。
 Laravelはミドルウェア、ルートバインディングをなんやかんや通って、コントローラのメソッドを実行、返り値をレスポンスにします。
 肝心のレスポンスはvendor/laravel/framework/src/Illuminate/Routing/Router.phpのtoResponseメソッドで整形されます。

    /**
     * Static version of prepareResponse.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  mixed  $response
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }

        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response);
        }

        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }

        return $response->prepare($request);
    }

 $responseの型を判定し、適したメソッドを呼ぶか、適したオブジェクトに変換してます。見るからにレスポンスにできるものはレスポンスにする、という貪欲さが見えます。とりあえず特長的なのは普通にhogehogeResponseという型で判定していない部分である次の条件文です。

        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {

 とりあえずjsonにできるならなんでもいい、arrayでもいい、という条件文です。これのおかげでControllerの中でのアダプターを作る手間がいくらか省けます。

  • この記事いいね! (0)
著者:杉浦

【Laravel】Eloquent自動生成ライブラリeloquent-model-generatorの紹介

krlove/eloquent-model-generator: Eloquent Model Generator
 LaravelのEloquentモデルをORM(オブジェクト関係マッピング)として自在動かすにはDB中の1テーブルに対して1モデルを定義、更にテーブル間の関係をメソッドに記述、とする必要があります。これはいつも同じことを記述する上、大量に必要となるため面倒です。eloquent-model-generatorはこれを解決します。
 eloquent-model-generatorはDB中の定義からEloquentモデルを生成するライブラリです。例によってcomposerでインストール、

composer require krlove/eloquent-model-generator --dev

 artisanから起動できます。基本は次です。

# モデル名を指定(テーブル名はモデル名の複数形)
php artisan krlove:generate:model User
# モデル名とテーブル名を指定
php artisan krlove:generate:model User --table-name=user

 これだけでリレーション、fillableをはじめとした様々なオプション付きのモデルが生成されます。
 テーブル名らをコピペして、コマンドを量産して叩くだけでも便利ですがconfigを使うとさらに便利になります。

<?php

return [
    'namespace' => 'App\Models', // namespaceの冠詞
    'base_class_name' => \Illuminate\Database\Eloquent\Model::class, // Modelの継承元
    'output_path' => '/full/path/to/output/directory', // Modelの出力先ディレクトリ
    'no_timestamps' => true, // created_at, updated_atを使わないか使うか
    'date_format' => 'U', // datetimeカラムからデータを取得する時のフォーマット。nullだとCarbon
    'connection' => 'other-connection', // 不明
    'backup' => true, // 元々あるモデルを壊さないか否か
];

 Eloquent共通の設定が出来ます。
 さらに、この記事を書いている時点(2019/06/10)のプルリクに全てのモデルを生成するコマンドがあります。
Create Command To Generate All Models by tprj29 · Pull Request #46 · krlove/eloquent-model-generator
 このプルリクにあるコマンドファイルをコ用いて、 artisan krlove:generate:all-modelsを実行すると、DB中のテーブルを全て読み取り、全てに対応するEloquentモデルが生成されます。
 さらにこれをartisan ide-helper:modelsartisan test-factory-helper:generateと組み合わせることで素のEloquentが更に充実します。

  • この記事いいね! (1)
著者:杉浦

【Laravel】モデル結合ルートを使ってコントローラの記述を簡略化

 サーバサイドプログラムのリクエスト処理部は、あるURLに対するリクエストを受け取り、リクエストに応じたデータを探して、もしなければ404を返し、と処理を行う必要があります。これを素で書くと例えば次の様になります。

// ルーティングファイル
Route::post('user/{id}', 'UserController@update');


// コントローラファイル
class UserController {
    public function update(){
        $id = request()->id ?: abort(404);
        $user = User::findOrFail($id);
        
        // $userに関連するレコードを更新する処理
        
        return $user;
    }
}

 Laravelのモデル結合ルートはこの処理を簡略化します。使い方は次の通りの名前と型の指定です。

// ルーティングファイル
Route::post('user/{user}', 'UserController@update');


// コントローラファイル
class UserController {
    public function update(User $user){
        // $userに関連するレコードを更新する処理
        
        return $user;
    }
}

 この様にすると、例えばuser/1365とpostを投げた時、User::findOrFail(1365);と同等の動作が実行されupdate関数にはUser::findOrFail(1365);の返り値のUserインスタンスが渡されます。毎度同じ手順で処理を行う必要がなくなり、楽になります。
ルーティング 5.8 Laravel

 上記まではモデル結合ルートの基本的な使い方であり、Laravelのデフォルトです。これは主キーで見つけるfindと同等の動作を仕組みを用いていますが、実際には主キーでない何らかのカラムでwhereをかけてモデルを取得したいパターンがあります。そのような時には次の様にモデル内でルートのwhereに使うカラムの名前を指定します。

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    /**
     * @inheritDoc
     */
    public function getRouteKeyName()
    {
        return 'モデル結合ルートで使いたいカラム名';
    }

 このgetRouteKeyName()は次の様にルーティング用のクエリ構築メソッドresolveRouteBindingty中で使われています。このメソッドで返されるModelインスタンスがモデル結合ルートによって渡されるModelのインスタンスになります。

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    /**
     * Retrieve the model for a bound value.
     *
     * @param  mixed  $value
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value)
    {
        return $this->where($this->getRouteKeyName(), $value)->first();
    }

 getRouteKeyNameを書き換えることでwhere句の指定するカラム名を書き換えました。もっと深く掘り進めてresolveRouteBindingを書き換えることによって更に大きくモデル結合ルートを改造できます。例えば次です。

    public function resolveRouteBinding($value)
    {
        return $this->where($this->getRouteKeyName(), $value)
            ->where('status', '<>', config('const.status.freeze'))
            ->first();
    }

 凍結状態のレコードが選ばれた時404を返す様にします。ちょっとしたグローバルスコープのパターンです。

65

  • この記事いいね! (1)
著者:杉浦

【Laravel】Laravelのdumpメッセージをより便利に使うブックマークレット

 Laravelのdump, dd関数は与えた値をきれいに表示してくれます。しかし、ネストの深いオブジェクトであるとクリック数が多くなってしまいます。

 この記事で紹介するブックマークレットはこれを解決します。
ページ中のaタグを全部クリック
中身を整理すると次です。

(function () {
    document.querySelectorAll('a').forEach(function (e) {
        e.click();
    })
})()

 document以下のaタグを全てクリックするのみです。dump, dd関数の結果の折り畳みの開閉はaタグで行われています。

 このブックマークレットはそれを全てクリックすることによって、開閉を一気に操作します。これによってちまちまクリックすることなく全貌を知ることが出来ます。

  • この記事いいね! (1)
著者:杉浦

【Laravel】null値のプロパティ参照を避けるoptional関数

ヘルパ 5.5 Laravel#optional
 optionalはLaravelのヘルパ関数のひとつです。次の様な使い方でnull値のプロパティを参照しようとする致命的エラーを防げます。

return optional($user->address)->street;// address===nullでもstreetを参照せずにnullをretuenする

{!! old('name', optional($user)->name) !!}// $user===nullでもnameを参照せずにnullをoldの第二引数にする

 これは特にプロパティの連鎖するリレーションで強力です。このヘルパ関数は一見素晴らしいですがその実用性は程々です。issetよりいくらかまし程度です。具体的に何が問題化というと

optional($user->address->city)->name // addressがnullだと引数生成時点でnullのプロパティ参照を行おうとして致命的エラー
optional($user->address)->city->name // cityがnullだとnull値のnameプロパティ参照を行おうとして致命的エラー

 の様に3段先を見る時点で安全性が担保されなくなります。

optional(optional($user->address)->city)->name

 とすると無駄に長くなりissetと変わりません。これはoptional関数が行っていることが、Optionalクラスのインスタンスを生成し、そのマジックメソッドを用いて通常のアクセスに代わって直下のメソッド、プロパティの参照ミスをnull値に書き換える、という処理のみであることが原因です。
 Optionalクラスのファイルはvendor/laravel/framework/src/Illuminate/Support/Optional.phpにあります。任意の型の引数を自由に操るためのクラスというだけあってなかなかの抽象度ですが、100行程の短いクラスにまとめられています。これを参考に自前で再帰optional関数を作るのもよいかもしれません(おそらく呼び出された時点のコードから先を実行前に読み取るか、プロパティもoptional型に加工することになる)。
 

  • この記事いいね! (0)
著者:杉浦

【Laravel】report関数で例外を握りつぶさない様にする

 この記事において”例外を握りつぶす”とは次の2の意味を転じて”例外の情報を記録しないまま処理を終端まで続ける”という動作を意味します。

[動サ五(四)]

1 強く握ってつぶす。「粘土を―・す」

2 意見・提案などを手もとに故意にとどめ、処置しないままにして、うやむやにする。「要求を―・す」
握り潰す(にぎりつぶす)の意味 – goo国語辞書

 つまり、真に握りつぶす

try{
  // 色々な処理
} catch (Exception $exception) {}

のみでなく

try{
  // 色々な処理
} catch (Exception $exception) {
  return JsonResponse::create('hoge');
}

といった例外を記録しない例外処理のことも、例外を握りつぶしている、と扱います。
 Laravelはデフォルトで例外処理をよしなに処理してくれます。具体的にはキャッチしてログに残し、例外発生直後は例外の理由と原因を示すリッチなエラー画面を見せてくれます。Laravel謹製の機能ならばより適した個別の処理もしてくれます(バリデーションならエラーメッセージ付きリダイレクト、など)。内部を詳しく知りたい方はエラー画面やAPIエラーから独自エラーまで! フローチャートでちゃんと理解するLaravelの例外処理とケーススタディ – Qiitaなどを読むとよいです。
 なにもせず放っておけばいい感じになるのですが、try, catchしなければいけない時もあります。例えば、画面遷移するフォームにおける例外処理は400番台エラーページにユーザを飛ばすのでなく、特定のページにリダイレクトさせたいです。Exception\Handlerに記述を逐一増やすことでリダイレクトを対応する方法がありますが、これは危険な密結合をしている巻物コードが生まれやすいです。コントローラ毎にtry, catchすることになるでしょう。
 コントローラ内でtry, catchしてリダイレクトさせるならば次の様なコードが考えられます。

        try {
             // いろいろな処理
        } catch (Exception $exception) {
            return redirect()->route('宛先')->with([
                'msg' => '保存に失敗しました' // エラーメッセージ
            ])->withInput(); // リクエストで送られてきた入力を差し戻す
        }

 このようにすると、ユーザに謎の遷移を見せずに済みます。しかし、これだけでは最初に挙げた”例外を握りつぶすコード”そのものです。Laravelの例外処理を一切通さずいきなりレスポンスを返しているため、ログに何も残りません。デバッグは困難になり、プログラムの運用者、保守者は絶句することでしょう。
 Laravelのreport関数は簡単にこの問題を解決します。

        try {
             // いろいろな処理
        } catch (Exception $exception) {
            report($exception);// 例外の内容を詳しくログに残す

            return redirect()->route('宛先')->with([
                'msg' => '保存に失敗しました' // エラーメッセージ
            ])->withInput(); // リクエストで送られてきた入力を差し戻す
        }

 例外処理の頭にreport($exception);と加えただけです。これだけで例外に適したログをLaravelが残してくれます。

  • この記事いいね! (0)
著者:杉浦

【Laravel】Laravel公式ニュースサイトLaravelNewsの紹介

Laravel News
 Laravel NewsはLaravelに関する最新の情報や特にメジャーな情報が配信されているLaravel公式のニュースサイトです。主なコンテンツはブログ、チュートリアル、パッケージ紹介、です。
 ブログはなんというか雑多なモノをまとめています。tagはあるので探しやすいですが、とにかく広範にわたって記事が載っています。
 チュートリアルは実践的というか中級者向けというかニッチな操作のチュートリアルになります。Laravelの狭い範囲を深く扱ったり、SPAなどの特定の設計での扱い方であったり、外部ツールやパッケージとの連携であったりという、Laravelの外部や深部に関するよい手法が載っています。
 パッケージはその名の通り、パッケージ紹介です。githubのstarが多いパッケージやcomposer, npmでダウンロードが多いパッケージやLaravelメイン開発者、LaravelNews運営者本人が作ったパッケージなどの紹介をしています。

  • この記事いいね! (0)
著者:杉浦

【Laravel】factory自動生成ツールlaravel-test-factory-helperの紹介

mpociot/laravel-test-factory-helper: Generate Laravel test factories from your existing models
 Laravelにはfactoryという機能があります。
データベースのテスト 5.5 Laravel#ファクトリの記述
 次の様にfactoryをdatabase/factories/以下に定義し、

use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
    ];
});
// <a href="https://readouble.com/laravel/5.5/ja/database-testing.html#writing-factories">データベースのテスト 5.5 Laravel#ファクトリの記述</a>より引用

 次の様に呼び出すことでデータを生成し、モデルに代入された状態で使用できます。

public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // モデルをテストで使用…
}

 ダミーの値はfakerというライブラリで生成します。
stympy/faker: A library for generating fake data such as names, addresses, and phone numbers.
 有用なライブラリなのですが様々な値生成方法を選べるためfactoryの定義にも悩みがちです。laravel-test-factory-helperはfactoryを自動生成します。

composer require mpociot/laravel-test-factory-helper

とcomposerで導入。

php artisan test-factory-helper:generate

とartisanを実行。するとモデル定義に従ってデータベースから値を取得、適した型のfakerをつけたfactoryをモデル分全て生成します。

  • この記事いいね! (0)