カテゴリーアーカイブ Laravel

著者:杉浦

【Laravel】一プロセス内限定のSQLのキャッシュを作る

 ORMは便利ですが、何度も重複するクエリを発行しがちです。複数のモデルオブジェクトeachしてリレーション先を参照する時は特に危険です。どれくらい重複してるかは
 barryvdh/laravel-debugbar: Laravel Debugbar (Integrates PHP Debug Bar)
 などで調べるのが楽です。duplicatedとuniqueに分けてくれます。

 やり方の方針はクエリが発行される度にそのクエリに対応するキャッシュはあるかチェック、あるならばキャッシュを結果として取得、なければ実際に発行して結果を取得してキャッシュに保存、です。
 一つのクエリに使う場合は次です。

        $key = __FILE__.' '.__FUNCTION__.' '.__LINE__ .' '.$id;
        app()->offsetExists($key) ?: app()->singleton($key, static function () use ($id){
            return Member::where('id',$id)->get();
        });

 コードの場所と引数に関して一意になるキーを設定。クエリの結果をシングルトンに登録することによって、同じ場所が同じ引数で繰り返し呼ばれる際、同じクエリを発行することなく、Laravel内にバインドされたシングルトンを呼ぶようになります。変数なのでプロセスが終わったら揮発します。
 作った後しばらくして気づきましたがarrayドライバ(array型の変数内にキャッシュを保存する機能)でキャッシュ使った方がシンプルですね。
 全体のクエリに使う場合はDBのselect等データ取得時のリスナーに同じ処理を入れます。updateした時のリスナーにキャッシュ消す処理忘れるとバグの元になります。

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

【PHP】DI(依存性注入)を用いたコードの追い方

 DI(Dependency injection)(依存性注入)とはコード内で外部への依存を直書きするのでなく、外部から引数、セッターなどで依存する部分を与える実装のことです。例えば、次です。

use Illuminate\Contracts\Hashing\Hasher as HasherContract;

class DatabaseUserProvider implements UserProvider
{
    /**
     * The hasher implementation.
     *
     * @var \Illuminate\Contracts\Hashing\Hasher
     */
    protected $hasher;

    /**
     * Create a new database user provider.
     *
     * @param  \Illuminate\Contracts\Hashing\Hasher  $hasher
     * @return void
     */
    public function __construct(HasherContract $hasher)
    {
        $this->hasher = $hasher;
    }
    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(UserContract $user, array $credentials)
    {
        return $this->hasher->check(
            $credentials['password'], $user->getAuthPassword()
        );
    }
}

 DatabaseUserProviderクラスがインスタンス化された際にHasherContract型の引数$hasherを要求し、$hasherを保持します。保持した$hasherをvalidateCredentialsメソッド内で用います。
 外部から依存するモノを与えるため、テストし易かったり変更し易かったりするのですが、あまりに自由で今稼働しているコードでは何が実体化しているのかわかりにくく、追うのが少し難しいです。上記コードなら$this->hasherって何?となってIDEの機能で型を追ってもHasherContractというインターフェースが現れて終わりです。HasherContractを満たすどのHasherクラスが使われているか分かりません。
 hasherの中身、ひいては実際の動作を知りたい時は、まず依存性が実際に注入された際のコードを動かし、その時のhasherの中身に何が入ってるのか調べる必要があります。PHPならば次の様に出来ます。

    public function __construct(HasherContract $hasher)
    {
        var_dump(get_class($hasher));exit;
        $this->hasher = $hasher;
    }

PHP: get_class – Manual
 get_class関数でインスタンスそのものに、あなたのクラス名は何ですか、と尋ねます。得られたクラス名が今稼働しているシステムで注入されているクラスの名前です。例えば、ここでBcryptHasherという実装クラスが呼ばれているとわかったら次の様に型付けをします。

class DatabaseUserProvider implements UserProvider
{
    /**
     * The hasher implementation.
     *
     * @var \Illuminate\Hashing\BcryptHasher
     */
    protected $hasher;

    /**
     * Create a new database user provider.
     *
     * @param  \Illuminate\Hashing\BcryptHasher  $hasher
     * @return void
     */
    public function __construct(BcryptHasher $hasher)
    {
        $this->hasher = $hasher;
    }
    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(UserContract $user, array $credentials)
    {
        return $this->hasher->check(
            $credentials['password'], $user->getAuthPassword()
        );
    }
}

 こうするとIDEのジャンプ機能はインターフェースHasherContractでなくクラスBcryptHasherを参照するようになり、静的にコードを追うことが格段に楽になります。注意点として直し忘れがあります。DIを駆使したコードほど直し忘れると型エラーで派手に事故ります。

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

【PHP】Adapterパターンであるカラムの値によって大きく振る舞いを変えるテーブルの扱いを多少マシにする

 ときおり扱い難い定義のデータベースを扱う必要が出てきます。特定のカラムによって値の意味や他テーブルとの関係が大きく変わるテーブルはその一つです。
 例えば、usersテーブルにtypeカラムがあり、typeによって仮会員か一般会員か特別会員か区別するとします。それぞれ似た様で確かに違うモノです。仮会員はメールアドレス等いくつかのデータがnullでも良し、の様に別のふるまいが要求され、それが増えてくると、usersテーブルを扱うのはだんだん面倒になってきます。早い時点でuserテーブルをリファクタリングできていれば問題にならないのですが、そうならないまま既存データやコードが積み重なり今更変える時間がない、ということもあります。アダプターパターンを用いることでデータベース定義を変えないままこれを緩和できます。
 アダプターパターンは増補改訂版Java言語で学ぶデザインパターン入門 | 結城 浩 |本 | 通販 | Amazonでは次のように説明されています。

「すでに提供されているもの」と「必要なもの」の間の「ずれ」を埋めるようなデザインパターン、これが
Adapterパターン
です。

 この考えに従えば「すでに提供されているもの」はtypeによってデータの意味が大きく変わるテーブル、「必要なもの」はデータの意味ごとのテーブルです。多くのフレームワークで採用されているORM(オブジェクト関係マッピング)は1テーブルに1モデルの原則で使いやすい様に作られています。これを1つの意味に対して1モデルになる様に用います。
 具体的にはマッピングの時点でwhere句を用いて、あるテーブルの特定のtype1つに対して1モデルの様にします。
 例えばLaravelならば

/** User.php */
class User{
  // 仮会員、一般会員、特別会員に関する大量のメソッド
}

となっている1つのモデル定義ファイルを明示的なテーブル指定とグローバルスコープによって

/** TemporaryUser.php */
class TemporaryUser{
    protected $table = 'users';
    public funtion boot(){
        parent::boot();
        self::addGlobalScope('decide_user_type', static function (Builder $query) {
            $query->where('type', config('const.user.type.temporary'));
        });
    }
    // 仮会員に関するメソッド
}
/** GeneralUser.php */
class GeneralUser{
    protected $table = 'users';
    public funtion boot(){
        parent::boot();
        self::addGlobalScope('decide_user_type', static function (Builder $query) {
            $query->where('type', config('const.user.type.general'));
        });
    }
    // 一般会員に関するメソッド
}
/** SpecialUser.php */
class SpecialUser{
    protected $table = 'users';
    public funtion boot(){
        parent::boot();
        self::addGlobalScope('decide_user_type', static function (Builder $query) {
            $query->where('type', config('const.user.type.special'));
        });
    }
    // 特別会員に関するメソッド
}

と分割した3ファイルに分割します。完全に共有する部分は、ユーザ特性としてtraitを用意し、3会員モデルクラスそれぞれにuseさせることで整理できます。
 テーブルを分割する例を挙げましたが逆に複数のテーブルや設定等をまとめた上でカプセル化されたEntityクラスを作るというのもまた別のAdapterパターンの活用です。

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

Laravelの契約とDIとStrategyパターン

 あるフレームワークを対象にしたライブラリというものはフレームワーク内のある機能を拡張したり、まるで違う中身で似た役割を果たす、といったものが少なくありません。その様なことをしても不具合を起きにくくするためにinterfaceの仕組みを用います。Laravelでは契約という名でまとめられています。
契約 5.8 Laravel
 例えばハッシュ処理なら次です。
 contracts/Hasher.php at 5.8 · illuminate/contracts

<?php
namespace Illuminate\Contracts\Hashing;
interface Hasher
{
    /**
     * Get information about the given hashed value.
     *
     * @param  string  $hashedValue
     * @return array
     */
    public function info($hashedValue);
    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array   $options
     * @return string
     */
    public function make($value, array $options = []);
    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = []);
    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = []);
}

 ハッシュ処理用の機能を作るならば、このインターフェースを実装しなさい、という契約です。Laravelの内部機能でハッシュ処理を行う際はこの契約によって作られることが保証されたメソッドのみを呼び出します。
 機能ができたならばどうにかそれを適用する必要があります。DI(依存性注入)というやり方によってこれを容易にします。依存性注入とはあるクラスへの依存を既に存在しているインスタンスや今まさに作られるインスタンスに注入するようなものです。例えば、次です。

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * ユーザーリポジトリの実装
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * 新しいコントローラインスタンスの生成
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * 指定ユーザーのプロフィール表示
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}
<a href="https://readouble.com/laravel/5.8/ja/container.html">サービスコンテナ 5.8 Laravel</a>より引用

 __construct(UserRepository $users)の部分でUserControllerクラスのインスタンスの持つプロパティであるusersはUserRepositoryインターフェースという契約を満たすことを強制されています。UserRepositoryインターフェースには必ずfindメソッドが実装する契約が記述されています。このため

        $user = $this->users->find($id);

 の部分は決して未定義エラーにならず、UserRepositoryを満たした何らかのクラスのインスタンスusers独自のfindメソッド処理を働かせます。
 DIは、ある目的を達成するためのアルゴリズムをごっそり代えることを容易にするプログラミングパターンであるStrategyパターンそのものです。既にDIを用いている部分は後から好き勝手コードを追加、変更したとしても注入口の記述を変えるだけで簡単にバグなく動かせます。

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

【Laravel】【Vue】LaravelとVueを結びつけるパッケージは割と多い

 LaravelはPHPのフレームワークです。PHPのフレームワークですがプロジェクトを建てた時点で一緒に使えと言わんばかりにフロントエンド側のVue.jsのコードと設定が渡されます。

 このためか巷にはLaravelとVue.jsを同時に使うことを前提としたパッケージが少なくないです。
 例えば、npmです。`vue laravel`でググると170件出てきます。

 一番上のlaravel-vue-pagination – npmはLaravelのクエリビルダのpaginateメソッドをレスポンスにしたAPIとVueを組み合わせてページネーションを実現するコンポーネントの様です。こういうのを作ろうとすると端の方のページを見る時の作りが面倒になりますが、作る手間を省けます。
 Packagistにはvueタグ付きのライブラリが144件出てきます。laravel-vue-generatorsは名前だけでもう相当ですね。Laravel内のコードからVueのコードを生成する様です。

 GitHubともなるとほぼ両者の和集合に加えて、草の根開発まで加わりさらに増えます。

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

【Laravel】【PhpStorm】Laravelプラグインの参照、移動機能

 Laravelはよく文字列で特定のパスの対象を指定します。それは配列であったり、ファイルであったり、メソッドで合ったり様々です。プラグインはこの指定の入力補完と移動を容易にします。これがあるとどこかしらを見ながら手打ちするといった手間を省けます。
Laravel – 公式ヘルプ | PhpStorm
Laravel – Plugins | JetBrains
Haehnchen/idea-php-laravel-plugin: Laravel Framework Plugin for PhpStorm / IntelliJ IDEA
 下画像のcompletion, gotoが予測と参照先への移動のついた機能です。

 コード補完は次の様に説明されています。

 静的解析の時点でコード補完が出来るぐらいに宣言を把握できる、ということは宣言の場所に移動することもまた可能です。Ctrl+Bはここでも便利です。

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

【Laravel】【Docker】Laravel用Dockerパッケージlaradocの紹介

 Dockerは仮想マシンを用意するツールです。何かと便利なのですが、仮想マシン中のソフトそれぞれについての知識がそれなりに必要になります。知識なしのままデフォルト設定から離れるとバグを生みがちです。
If it works in Docker, it works on CircleCI – CircleCI
 そうなるとDockerのデフォルト設定でなくLaravel用の安全な設定が欲しくなります。Laradocはそういった要素のパッケージです。Laradock
 サーバ機能、各データベース、jenkinsなどCIツール、Laravel推奨パッケージ、などが詰まっています。パッケージに命令を発する。docker-composeファイルは次の様な巻物になっています。この中から探すと大体メジャーなソフトやパッケージが見つかります。また、扱いやすい様に高頻度で変えられるパラメータなどが変数化されており、簡単安心に基本的な使い方ができます。
 
 日本語のチュートリアルは色々な人が詳しく書いているのがたくさんあるのでググれば目的のものが大体あります。

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

【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)