著者アーカイブ 杉浦

著者:杉浦

【PhpStorm】PHPSTORM 2019.2の新機能

 PhpStormの新バージョンがリリースされました。詳しく機能は次リンクです。

What’s New in PhpStorm 2019.2
 色々増えましたが自分が特に注目したのは次の3つです。
 ”Locate Duplicates on the fly”重複コードを自動検出します。共通化できるコードを自動で見つける機能です。閾値設定次第では共通化すべきでないところまで誤検出してしまいますが、気づかない内に実装してしまった共通化すべきコードを先んじて見つけてくれます。サービスとかtraitとかabstract classとかにまとめてるべきモノは後になって気づきがちなのでかなりありがたいです。
 ”Better support for Vue.js”Vue.jsのサポートが強力になりました。新機能としては注目してませんでしたが、更新した日の内に威力を感じられたので紹介。Vueデフォルトの機能の予測、検証が強力になりました。Vue.useやVue.componentなどのグローバル定義を追いかけるようになり、単体テストを考えなければかなり楽に使用ライブラリの定義が出来ます。
 ”Syntax highlighting for over 20 languages.”Ruby, Python, Goなど20以上の言語のシンタックスハイライトをカバーする様になりました。PhpStormですがPHP以外もかなりいけます。スニペットぐらいならPyCharm等を立ち上げるまでもなくPhpStormで満足に完結できます。

著者:杉浦

【Laravel】データベースと無関係のEloquentっぽいモデルを作るためのFluentクラス

 LaravelはEloquentクラスにモデルをまとめています。Eloquentはデータベース中のテーブルに対応して扱われることを前提としたORMです。
Eloquent:利用の開始 5.8 Laravel
 webサービスにおいてモデルが欲しい時は大体、対応したテーブルを用意するのですが、時にはデータベースに関係なくモデルを作りたい時があります。具体的には別のサービスで吐き出されたファイルの中身を扱う時などです。そういった時に手製のクラスファイルを作成するのも一つの手ですが、何かしたいことが増える度に簡単なことでも一々コードを書く必要があります。何かしら継承なりuseしたいですがEloquentを継承するのは危険です。誤った呼び出し一つで存在しない場所を参照するSQLを投げるがPHP的には正しいコードが出来上がります。
 FluentはLaravelの\Illuminate\Support\Fluentに位置しているEloquentの様なプロパティ制御とjson、array扱いの要素(ArrayAccess, Arrayable, Jsonable, JsonSerializableのinterfaceを満たしている)を持った素朴なクラスです。Laravel内部で扱われているのが基本でドキュメントなしのクラスです(Laravel4とかの頃のモデルクラスっぽい?)。Fluentのコンストラクタは次の様になっており、適宜new Fluent(パラメータ配列)をすることでデータベースに関係ないEloquentライクなモデル実体を扱えます。

    /**
     * Create a new fluent instance.
     *
     * @param  array|object  $attributes
     * @return void
     */
    public function __construct($attributes = [])
    {
        foreach ($attributes as $key => $value) {
            $this->attributes[$key] = $value;
        }
    }

 継承して、そのクラスをnewしただけでインスタンスにさせたいこと、インスタンスから知りたいことの設定が完了します。クラスだけ作って何も中身がないのに自在に動く様はEloquentを彷彿とさせます。
 簡単にnewする対象を探索する処理を入れるとなお便利です。例えば次の様なstaticメソッドを生やすことでなお扱いやすくなります。

    /**
     * ファイル名で探す。
     * @param  string $filename
     * @return mixed
     */
    public static function find(string $filename)
    {
        $disk = Storage::disk('hoge_files');
        if(!$disk->exist($filename)){
            return null;
        }
        $meta_data = $disk->getMetadata($filename);
        $body_data = self::parse($disk->get($filename));
        
        return new self(array_merge($meta_data, $body_data);
    }

 バリデータを作ってコンストラクタ中で呼び出す様な仕組みを作るとプロパティに何があるのかきっちり決まり更に更に安全安心に使えます。

著者:杉浦

【PHP】関数implodeの引数が類を見ないくらい自由だった話

 文字列の結合と分割はよく行われる操作であり、PHPにもそのための組み込み関数implode, explodeが用意されています。
PHP: implode – Manual
PHP: explode – Manual
 それぞれドキュメント上では次の様な呼び出し方が説明されています。

implode ( string $glue , array $pieces ) : string
implode ( array $pieces ) : string
explode ( string $delimiter , string $string [, int $limit = PHP_INT_MAX ] ) : array

 第一引数に区切り文字、第二引数に対象が入るのだな、と理解できます。しかしこのimplode実は次の様にも動きます。

 第二引数に対象、第一引数に区切り文字でも動きます。暗黙の内によしなにしてくれるのはPHPあるあるですが流石にこれは凄まじいです。ちなみにexplodeはダメです。公式で次の様に注意喚起されています。

注意:

歴史的理由により、implode()
はいずれのパラメータ順も受け入れることができますが、
explode() はそうできません。
string 引数の前に必ず
delimiter 引数がくるように確認する必要があります。

あまりにも自由すぎたのかPHP7.4から非推奨になります。RIP.
PHP: rfc:deprecations_php_7_4#implode_parameter_order_mix

著者:杉浦

【Vue】改行とv-htmlとXSS対策

 Vue.js上で文字列を展開したい時があります。例えばそれは誰かが投稿したコメントをコンポーネント上に表示する時です。こういった時コメントの改行が反映されていない場合、見栄えが悪くなります。改行を行う必要があります。
 HTML上の改行と言えばbrタグです。PHPにはnl2brという改行コードを改行タグに変換する組み込み関数があるぐらいです。
PHP: nl2br – Manual
 改行の実現でありがちで危険なアンチパターンはこのnl2br関数を用いたコメントをそのまま表示しようと考えるものです。Vue.jsにはv-htmlというディレクティブがあり、これを用いると普段かかっている安全装置のHTMLエスケープを外し、HTMLとしてパース、実行します。brタグとv-htmlを用いることで改行コードを改行タグに変換して表示できます。
 v-htmlはXSSをまあまあ容易に招く危険性を持ちます。まあまあ危険というのは単純な

<script>alert('XSS')</script>

ぐらいならVueの仕組み上実行されず済むからです。とはいえ

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>

の様な多少の変化球(scriptタグをbase64形式でエンコード、埋め込んだコード中でデコードして実行)であっさり破られるのでやはり危険です。

XSS 脆弱性を容易に引き起こすので、ウェブサイトで動的に任意のHTMLを描画することは、非常に危険です。信頼できるコンテンツにだけ HTML 展開を利用してください。ユーザーから提供されたコンテンツに対しては決して使用してはいけません。

テンプレート構文 — Vue.js

 これの対策は実現したい機能に関しての実装を生のHTMLに頼るのでなく個々の別手法を用いるのが一番でしょう。
 例えば改行に関してはCSSのwhite-space実現できます。white-spaceは要素内のホワイトスペースをどう扱うか決定するスタイルであり、ホワイトスペースの一種(少なくとも正規表現の\sグループでまとめられる)である改行文字の制御もこれでできます
white-space – CSS: カスケーディングスタイルシート | MDN
 上記リンクから引用した次の表の’改行を残す’スタイルを用いれば
と生のHTML実行を用いるまでもありません。

  改行 空白とタブ文字 テキストの折り返し 行末の空白
normal まとめる まとめる 折り返す 除去
nowrap まとめる まとめる 折り返さない 除去
pre 残す 残す 折り返さない 残す
pre-wrap 残す 残す 折り返す ぶら下げ
pre-line 残す まとめる 折り返す 除去
break-spaces 残す まとめる 折り返す 折り返す
著者:杉浦

【Laravel】Duskのテストケースを楽に作るChrome拡張Laravel TestToolsの紹介

 DuskはLaravelと密にかかわっている自動ブラウザテストツールです。実際にブラウザを開いて、あるページへ移動できるか、そのページである操作はできるか、操作の結果は想定した通りか、といったテストを自動で行ってくれます。ブラウザを用いる以上しょうがないかもしれませんが実行時間が長いのが玉に瑕です。
Laravel Dusk 5.8 Laravel
 テストコードは例えば次の様に書けます。

    public function testBasicExample()
    {
        $user = factory(User::class)->create([
            'email' => 'taylor@laravel.com',
        ]);

        $this->browse(function ($browser) use ($user) {
            $browser->visit('/login')
                    ->type('email', $user->email)
                    ->type('password', 'secret')
                    ->press('Login')
                    ->assertPathIs('/home');
        });
    }
    /**
     * <a href="https://readouble.com/laravel/5.8/ja/dusk.html">Laravel Dusk 5.8 Laravel</a>から引用
     */

 テストユーザを作成。/loginへ訪れて、name=”email”のinputタグにテストユーザのemailを入力、name=”password”のinputタグに’secret’と入力、Loginと書かれたボタンを押し、押した後のページのアドレスが/homeであることを確かめるコードです。簡単に記述するために相当暗黙知が入っています。慣れれば楽なのですが、慣れるまで具体的にどこの何が何であれば期待した通りに動作するのか、少し戸惑います。Laravel TestToolsは操作の記述を楽にしてくれます。
Laravel TestTools – Chrome ウェブストア
 具体的に何をするかというと操作の記録とコード起こしです。次のgifの様に録画ボタンを押すと動作を記録します。

 書き起こされたコードをコピペするだけで操作分の記述完成です。これでtype()やpress()を記述する際にそう悩む必要がありません。後はassertで確かめるだけです。

著者:杉浦

【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した時のリスナーにキャッシュ消す処理忘れるとバグの元になります。

著者:杉浦

【Vue】jQueryとVueを共存させる

 デザインはjQueryで作られる、実装はVue.jsで行う。なんやかんやあってこの方式でwebページを作る必要が出てくることがあります。一貫していた方が作りやすいはずなのですが、なんやかんやあってこうなります。
 デザインの実装の際、jQueryとVue.jsを読み込ませるとデザイン時に動いていたイベントが消える時があります。これはVueがテンプレートエンジンも兼ねていることが原因で起きがちです。例えば次の様なシナリオで起きます。

// デザイン時
HTML読み込み完了
↓
jQueryによるイベント付与実行
↓
全てのHTMLに付与されるべきイベントが付与されている

// 実装時
HTML読み込み中、Vue構築中
↓
HTML読み込み完了
↓
jQueryによるイベント付与実行
↓
Vue構築完了。Vue内で定義されたHTML構築を持ってきて描画

 特にVueでAPIの結果をHTMLの一領域に描画する仕組みを作った時はイベントの再定義の絡みで大体こうなります。こうなる時にはVueの中でVueが読み込み完了した場合に実行されるイベントでjQueryによるイベントを定義すると良いです。

export default {
  components: {
  },
  props: {
  },
  data() {
    return {
      
    };
  },
  mounted() {
    // jQueryのイベント付与処理
  },

 このように記述するとコンポーネントが読み込まれるたびにmounted関数が実行され、テンプレート内にjQueryのイベントが付与されます。
 Vue.jsとjQueryの同時実行はJavaScriptファイルが巨大になり初期読み込みが遅くなるため積極的に行うべきでないのですが、デザイナーからいただけるデザインにjQueryでアニメーションがついていることがしばしばあります。そういった際にこのやり方を知っておくと、Vue.jsの便利さから離れないままアニメーションを崩すことも再構築することもなくデザインを再現できます。

著者:杉浦

【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を駆使したコードほど直し忘れると型エラーで派手に事故ります。

著者:杉浦

【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パターンの活用です。

著者:杉浦

Gitで”最も一般的に使用されているgitのヒントとテクニック”を紹介しているリポジトリgit-tips/tips

 Gitでコード管理をするだけならコード管理を目的としたGUIアプリやIDE備え付けの機能を使った方が楽です。一方コマンドラインからGitを用いるならば、見た目や操作性は微妙ですがより自由自在にGitの機能を使えます。アプリやIDE自体、ボタンやモニターにGitのコマンドと装飾された結果を結びつけているものですから、コマンドラインの方がより自由なモノになります。コマンドラインの方が自由とはいえ、使い勝手が悪いのは事実で面白い使い方の例などが欲しくなります。git-tips/tipsにはその様な例が載っています。
git-tips/tips: Most commonly used git tips and tricks.
isotai/git-tips: 最もよく使われるgitの小技と裏技(少し翻訳停滞中)
 例えば、’Group commits by authors and title’という説明でgit shortlogというコマンドが載っています。これは次の様な結果を見せます。

 コミット数と名前とコミットメッセージの一覧です。こういった結果を元にシェルでちょちょいと加工するだけで興味深いモノが出力できます。例えば次です。

git shortlog | grep ^[^\ ] | awk -F [\(\)] '{print $2,$1}' | sort -h -r

 grepで行頭に空白のない文字列であるコミット数と名前だけに絞り込み、awkで()区切りで文字列操作してコミット数が行頭になる様に文字列加工、sortで人間にとって自然な順番の反対で並び替え、とすると

の様にユーザごとのコミット数降順が分かります。同様に変更した行数やらマージされたプルリク数やら色々解析できます。また、git help -aで全てのgitコマンドのリストが出るのでそこから自分で探るのも手です。