著者アーカイブ 杉浦

著者:杉浦

【Laravel】Collection中のitemが未定義でもnullでもないか検査したい時はisset()でなくget()!==nullを使うべき

 CollectionはLaravelの持つ配列をラッピングしたクラスです。Collectionを用いることによってPHPの素の配列操作よりも多彩な配列操作を一貫性を保ったまま容易に実現できます。

Illuminate\Support\Collectionクラスは配列データを操作するための、書きやすく使いやすいラッパーです。以下の例をご覧ください。配列から新しいコレクションインスタンスを作成するためにcollectヘルパを使用し、各要素に対しstrtoupperを実行し、それから空の要素を削除しています。
コレクション 5.7 Laravel

 Collectionは素晴らしいですが、題名にあるisset()の関連部分では特殊な挙動を見せます。

$arr = ['hoge' => null];
dump(isset($arr['hoge']));// false
dump(isset($arr['fuga']));// false

$col = new \Illuminate\Support\Collection(['hoge' => null]);
dump(isset($col['hoge']));// true
dump(isset($col['fuga']));// false

 Collectionを介したせいで結果が素の配列と異なってしまいました。これはCollectionのArrayAccessインタフェースの実装の仕方に原因があります。
 ArrayAccessインタフェースはオブジェクトを配列として呼び出すためのインタフェースです。これを実装すると次の画像の様にオブジェクトを配列として呼び出せます。
 
PHP: ArrayAccess – Manual
 ArrayAccessの中でissetと直接的に絡んでいるメソッドがoffsetExistsです。

このメソッドが実行されるのは、ArrayAccess を実装したオブジェクト上で isset() あるいは empty() を使用した場合です。
PHP: ArrayAccess::offsetExists – Manual

 \Illuminate\Support\CollectionのArrayAccess::offsetExistsは次の様に実装されています。

    /**
     * Determine if an item exists at an offset.
     *
     * @param  mixed  $key
     * @return bool
     */
    public function offsetExists($key)
    {
        return array_key_exists($key, $this->items);
    }

 $this->itemsがCollection内で扱われている配列です。これはkeyが存在すれば値がnullであってもissetでtrueを返すということです。このため

$col = new \Illuminate\Support\Collection(['hoge' => null]);
dump(isset($col['hoge']));// true

となります。
 一見バグの様に見えますが、Determine if an item exists at an offset.とコメントされている上、実装内容を

    public function offsetExists($key)
    {
        return isset($this->items[$key]);
    }

と変えるとこの変更が原因でLaravel自体のテストで失敗が起きます。つまり仕様です。

 このアンチパターンと化したisset()の代わりになる判定方法が次のコードです。

$col = new \Illuminate\Support\Collection(['hoge' => null]);
dump($col->get('hoge') !== null);// false
dump($col->get('fuga') !== null);// false

https://implode.io/5yDeL4
 Collection中のgetメソッドを用います。getメソッドはキーの値を返すメソッドです。次の引用の様にキーが存在しない場合、例外でなくnullを返します。

getメソッドは指定されたキーのアイテムを返します。キーが存在していない場合はnullを返します。

 これによりgetメソッドの返り値はキーの値がnullであっても、キーが存在しなくともnullになります。これでissetと同じく、変数がセットされていること、そしてNULLでないことを検査することができます。

著者:杉浦

【Laravel】Laravel付きのPHPオンラインテストページImplode.io

Implode.io
 Implode.ioは簡易なPHPをテストする環境を持ったwebサービスです。Implode.ioにはコードを簡単に書き、共有できるような環境が整っています。Implode.ioの特長はフレームワークLaravelを使えることです。図の様にフレームワークを指定することで組み込まれているクラス、関数などが使えます。

 図のNewからセットを選ぶとモデルやテンプレートを試すこともできます。

 下図中の赤丸部分の画面上部にある検索ボックスから今まで大勢に作られてきたコードを探せます。

 これでLaravelのみに関わるテストでも、開発中のコードを触ったり余計な計算資源を食ったりせずに済みます。

著者:杉浦

【Laravel】Bladeの制御構文の探し方

 LaravelのBladeドキュメントは丁寧でわかりやすいですが、わかりやすさのために切り捨てられている部分もあります。例えばBladeの制御構文がいくつか抜け落ちています。この記事では探し方を紹介します。探し方が分かればバージョンが変わっても平気です。
Bladeテンプレート 5.7 Laravel
 探すことをプログラムに任せる直接的な解決策があります。そういうプラグインのあるエディタを使用してそのプラグインを入れましょう。PhpStormならばBlade Supportがあり次の画像の様に、色付けと予測を行ってくれます。
 Blade Support – Plugins | JetBrains

 Bladeの元を探そうとしてもクラスやメソッドの呼び出しを辿って見つけることは難しいです。というのもLaravelはそのソースコード内で特定の名前のクラス、メソッドをまとめて呼び出したり、その場でPHPの構文を作ったりすることで根本のコードを小さく保っているためです。例えば次の様なコードが現れます。

    /**
     * Register the engine resolver instance.
     *
     * @return void
     */
    public function registerEngineResolver()
    {
        $this->app->singleton('view.engine.resolver', function () {
            $resolver = new EngineResolver;

            // Next, we will register the various view engines with the resolver so that the
            // environment will resolve the engines needed for various views based on the
            // extension of view file. We call a method for each of the view's engines.
            foreach (['file', 'php', 'blade'] as $engine) {
                $this->{'register'.ucfirst($engine).'Engine'}($resolver);
            }

            return $resolver;
        });
    }

 このためLaravelの中を探す時はそれっぽいクラス名、メソッド名を用いた正規表現によって隠された呼び出し元を探す方法が有効です。肝心のBlade構文を字句解析(大雑把いえば文字列を意味のある字句に区切る処理)している部分は次です。この部分はViewのProvider周りから呼び出されています。

// vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.phpの内部
    /**
     * All of the available compiler functions.
     *
     * @var array
     */
    protected $compilers = [
        'Comments',
        'Extensions',
        'Statements',
        'Echos',
    ];
    
    
    /** 省略 **/
    
    /**
     * Parse the tokens from the template.
     *
     * @param  array  $token
     * @return string
     */
    protected function parseToken($token)
    {
        [$id, $content] = $token;

        if ($id == T_INLINE_HTML) {
/** ここから */
            foreach ($this->compilers as $type) {
                $content = $this->{"compile{$type}"}($content);
            }
/** ここまでが重要 */
        }

        return $content;
    }

 $this->{“compile{$type}”}($content);を満たすメソッドで字句解析をしています。これを満たすメソッドで@以下を解析しているのが次のcompileStatementsです。

    /**
     * Compile Blade statements that start with "@".
     *
     * @param  string  $value
     * @return string
     */
    protected function compileStatements($value)
    {
        return preg_replace_callback(
            '/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) {
                return $this->compileStatement($match);
            }, $value
        );
    }

    /**
     * Compile a single Blade @ statement.
     *
     * @param  array  $match
     * @return string
     */
    protected function compileStatement($match)
    {
        if (Str::contains($match[1], '@')) {
            $match[0] = isset($match[3]) ? $match[1].$match[3] : $match[1];
        } elseif (isset($this->customDirectives[$match[1]])) {
            $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3));
/** ここから*/
        } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) {
            $match[0] = $this->$method(Arr::get($match, 3));
        }
/** ここまで重要 ucfirstは最初の一文字が大文字なら小文字にする関数*/
        return isset($match[3]) ? $match[0] : $match[0].$match[2];
    }

 到達しました。BladeCompilerのインスタンスに引っ付いているcompileHogeなメソッドがあれば@hogeが動作するということです。後はBladeCompilerに生えているメソッド一覧を見て終わりです。


 多いです。使いこなせている人はいるのでしょうか。

著者:杉浦

【Laravel】Laravel5.6以降のヘルパ関数abortは結構自由

 LaravelにはHTTP例外を投げるヘルパ関数abortが用意されています。

abort関数は、例外ハンドラによりレンダーされるであろう、HTTP例外を投げます。

abort(403);

例外のレスポンステキストと、カスタムヘッダを指定することもできます。

abort(403, 'Unauthorized.', $headers);

ヘルパ 5.7 Laravel#method-abort

 abort(403);と一言記述するのみで権限エラーである403のエラーページにクライアントを飛ばせます。この使い方が最も主で伝統的な使い方です。しかしもう少し複雑なHTTPレスポンスを返したい場合もあります。
 使い方が書かれている公式の翻訳ドキュメントにおいて、abort関数はステータスコードとメッセージとヘッダを指定するだけの簡易な関数に見えますが、実際はもっと自由です。ヘルパ関数abortは/vendor/laravel/framework/src/Illuminate/Foundation/helpers.phpにあるabort関数を呼び出しています。その呼び出される部分が次です。

// Laravel5.7版のコード
if (! function_exists('abort')) {
    /**
     * Throw an HttpException with the given data.
     *
     * @param  \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Support\Responsable|int     $code
     * @param  string  $message
     * @param  array   $headers
     * @return void
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    function abort($code, $message = '', array $headers = [])
    {
        if ($code instanceof Response) {
            throw new HttpResponseException($code);
        } elseif ($code instanceof Responsable) {
            throw new HttpResponseException($code->toResponse(request()));
        }

        app()->abort($code, $message, $headers);
    }
}

 注目する部分はPHPDocの$codeで指定されている型と関数が始まった直後のif文部分です。これは$codeにはステータスコードになるintのみでなく\Symfony\Component\HttpFoundation\Responseを継承するか\Illuminate\Contracts\Support\Responsableを実装したクラスのインスタンスを引数にすれば、HTTPレスポンスとして適した形で例外の結果をクライアントに返します。
 PHPのinstanceofは完全にイコールのインスタンスのみでなく継承元、実装元の場合でも真になります。
PHP: 型演算子 – Manual

 このinstanceofの性質からabort関数は例えばこのように使えます。

abort(resnponse()->json("I'm a teapot", 418));

 ExceptionクラスやHandlerクラスを気にすることなく例外の結果をJSON型のHTTPレスポンスとしてクライアントに投げられます。
 LaravelにはインタフェースIlluminate\Contracts\Support\Jsonableがあり、これを実装したクラスはJSON化が担保され、至る所でJSONとして扱えます。JsonableはModelやCollectionを始めとして多くのもので実装されています。このため次の様なこともできます。

// User.php
class User extends Model implements Responsable
{
    public function isOne()
    {
        return $this->getKey() === 1;
    }
    public function toResponse($request)
    {
        return response()->json($this);
    }
}


// 適当な別ファイル
$a = User::inRandomOrder();
$a->isOne() ?: abort($a);

 Modelの場合、プロパティがJSON化されます。abortに渡されたUserインスタンスのtoResponseメソッドが動作し、response()->json($this)によりJSON化されたUserインスタンスがHTTPレスポンスとして返されます。
 この変更はLaravel5.6時に追加されました。
allow giving a response to abort · laravel/framework@4e29889

著者:杉浦

【Vue.js】連なった単一ファイルコンポーネント間のイベント発火

 Vue.jsはwebページを部品単位で構築することによって開発を支えるJavaScriptのフレームワークです。
Vue.js
 Vue.jsには単一ファイルコンポーネントという仕組みがあります。これは次のコードの様にあるwebページの部品のHTML、JavaScript、CSSを一ファイルにまとめる仕組みです。

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
module.exports = {
  data: function () {
    return {
      greeting: 'Hello'
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

単一ファイルコンポーネント — Vue.js
 単一ファイルコンポーネントを用いるとDOMツリーならぬコンポーネントツリーともいうべき構造のコードを記述することになります。下図はあるページのコンポーネントの階層構造です。まとめ役のmain-template、その下に検索ボックスのsearch-box-templateと各モーダル、それぞれに入力欄のform-group-row-inputが連なっています。

 これはまだシンプルに保ててますが、モノによってはもっと深い構造なることもあります。
 離れた場所にある異なるメニュー内に共通アクションのボタンをつけるなど、異なるコンポーネント内で定義されているイベントを発火したい時があります。そういった時は$emitで親のイベントを発火することができます。
API — Vue.js#vm-emit
 サンプルコードなどでもよく現れ、よく使う形はmethod内部での$emitです。次の様に子ファイルの内部で$emit(‘イベント名’,引数)とすると、親ファイル中でhoge-eventが動き、$emitで渡された引数が格納された$event付きのhogeEventAction()が動作します。

// 親ファイル
<first-template
  @hoge-event="hogeEventAction($event)"
/>


// 子ファイル
method{
  fuga(){
    // 何か色々処理
    this.$emit('hoge-event', 'hogehoge');
  }
}

 これがよくある書き方ですが孫の様な直下よりも下にあるコンポーネントから親のイベントを発火する際には冗長です。

// 親ファイル
<first-template
  @hoge-event="hogeEventAction($event)"
/>


// 子ファイル
<second-template
  @hoge-event="hogeEventAction($event)"
/>
// ---省略---
method{
  hogeEventAction(argv){
    this.$emit('hoge-event', argv);
  }
}


// 孫ファイル
method{
  fuga(){
    this.$emit('hoge-event', 'hogehoge');
  }
}

 イベントの定義時にはイベントに対応する関数を記述するのがよくあるパターンですが、この部分はワンライナーならば何でも入れられます。このため中間になるコンポーネントには次の子ファイルの様に、親のイベントを発火する親と同名のイベントを定義すると簡潔なコードになります。

// 親ファイル
<first-template
  @hoge-event="hogeEventAction($event)"
/>


// 子ファイル
<second-template
  @hoge-event="$emit('hoge-event', $event)"
/>


// 孫ファイル
method{
  fuga(){
    this.$emit('hoge-event', 'hogehoge');
  }
}

 子ファイルの内部を汚さずに孫から親のイベントを発火できました。

著者:杉浦

PhpStormで提案された検査の修正をまとめて適用する

 PhpStormにはファイルの中を検査して動作しないと思われる記述や非推奨の記述をエラーや警告といった形で伝えてくれる機能があります。この機能は便利な拡張も出回っています。
Php Inspections (EA Extended) – Plugins | JetBrains
 この検査結果は新たなルールを導入すると大量に現れがちです。しかも直し方が画一的なものも多く、まともにこつこつ一つずつ直していくととても面倒です。次図は全く警告のなかったコードに上の拡張を入れた直後のものです。

 この画像の中で特に面倒なのはメソッド名についている波線の警告の”返り値の型を宣言するべき”です。今までPHPDocに書いてあれば十分としてなっていましたが、上の拡張ではより安全にできるのにしていないのは十分警告の対象とのことです。各メソッドにつきいちいち記述していく手間はかけられません。PhpStormの機能でまとめて修正します。
 まずコード→コードのインスペクションないしAlt+Shift+Iでインスペクションをまとめて下のボックスに表示させます。

 次いで一種類のインスペクション内容になるまで展開して、赤丸の電球ボタンを押すだけで自動で修正がかかります。

 あっという間によりきれいで安全なコードになりました。あとはテストにかけて問題が出ていないことを確認か問題の修正をすればきれいで安全で十分な機能を持ったコードになります。

著者:杉浦

【JavaScript】Leafletを使って任意の画像を地図らしく装飾する

 Leafletは地図を表現するためのJavaScriptのライブラリです。これはよく次のコードの様にタイルレイヤーとして様々な世界地図を読み込ませることで動かされます。
Leaflet

var mymap = L.map('id-of-map-div').setView([51.505, -0.09], 13);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
    attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
    maxZoom: 18,
    id: 'mapbox.streets',
    accessToken: 'your.mapbox.access.token'
}).addTo(mymap);

 このLeafletですがmapに登録できるものには任意の画像もあります。次の様にmapのサイズと背景画像設定のコードを入れるだけで地図=単体の画像となり自由に加工できます。

// 画像サイズ兼地図サイズ
const height = 354;
const width = 663;
const bounds = [[0,0], [height,width]]; 
// leafletに地図として画像を登録
const map = L.map('map', {crs: L.CRS.Simple});// {crs: L.CRS.Simple}で座標系が決まるため忘れるととてもめんどくさい
L.imageOverlay('https://i2.wp.com/cpoint-lab.co.jp/wp-content/uploads/2019/03/storm_diagram_00.png?ssl=1', bounds).addTo(map); // 画像をboundsの大きさとして地図に追加
map.fitBounds(bounds); // 描画領域をboundsに設定

 この地図は左下を原点、右方向がXの正、上方向がYの正となるXY座標系です。第一象限ですね。これに従って加工すると次のJSFiddleみたいなことができます。

著者:杉浦

PhpStormのダイアグラム機能によるクラス図自動生成

 コードを読む時、しばしばクラス間の関係が記述されたクラス図が欲しくなる時があります。あると大変助かるのですが、大体そういったドキュメントはありません。PhpStormにはダイアグラム機能がついており、これはクラス図を始めとしたいくつかの図を自動生成する機能です。
ダイアグラムの操作 – 公式ヘルプ | PhpStorm
例えば、Laravelのソースコードの一部であるHTTPフォルダ以下のクラス図は次の様になります。

 図の矢印の意味は、矢印元は矢印先を参照して作られています、というもので、互いの依存関係を表しています。青の濃い矢印は直接の継承、緑の破線矢印はインタフェースの実装、白の破線ひし形矢印はトレイトの包含です。ただファイルを見るだけでは結構な量があり全貌をつかむまで時間がかかりがちですが、これならばすぐわかります。次の図はクラス図にしたHTTPフォルダ以下のフォルダ構造の木です。気持ち程度しか関係性が読み取れません。

 クラス図の例はPHPでしたが次の様にデータベース、JavaScriptなどにも対応しており、レイアウトのテンプレートも様々です。

著者:杉浦

Pythonのグローバル変数の命名における慣習

 Pythonはファイル全体をスコープとした記述が出来、例えば次の三行だけのファイルを動かせます。

print('---')
print('Hello, World!')
print('---')

 インタプリタらしく一行一行コマンドを読み込み、実行していく感じです。この一行一行がくせもので必ずファイル全体をスコープとしたグローバル環境にコードを記述する必要があります。例えば関数を定義して呼び出す際、最後はグローバルから呼び出すことになります。

def hello_world():
    return 'Hello, World!'


hello_world()

 この仕様によるグローバル汚染を防ぐためにPythonにはいくつか慣習があります。例えば、実行ファイル以外のコードが不意に走ることを防ぐためのif文があります。

def hello_world():
    return 'Hello, World!'


if __name__ == '__main__':# このファイルがメインの実行ファイルならばtrue
    hello_world()

 同じ様にグローバル変数には命名の慣習があり、ローカル内で誤ってグローバル変数を参照することを避けます。内容はいたってシンプル、先頭の_です。

def hello_world():
    return 'Hello, World!'


_msg = hello_world()
if __name__ == '__main__':# このファイルがメインの実行ファイルならばtrue
    print(_msg)

 この様にすることで複数ファイルを連携させた際に参照したい変数、参照したくない変数を操りやすくします。この慣習は根付いており、Python実行ファイル上において_hogeな変数とfugaな変数は同等に扱われるにもかかわらず、IDE等のPython対応のエディタにおいて_hogeな変数はグローバル変数用の扱い、fugaな変数はローカル変数用の扱いと区別されます。

著者:杉浦

【PHP】PHPDocからドキュメントページを作成するphpDocumenterの紹介

phpDocumentor
phpDocumentor/phpDocumentor2: Documentation Generator for PHP
 phpDocumentorはPHPDocを読み取り、PHPDocに従ってドキュメントを表すHTMLページを作成してくれます。インストールは次の画像やphpDocumentorの右側参照、要はvendor/bin以下にphpDocumentorの実行ファイルが置ければ何でもよいです。

 実行方法は次の画像の通りPHPを介して実行。コマンドは

php [phpDocumenter実行ファイル] -d [対象ファイル、フォルダのパス] -t [出力先のパス]

です。

 こうして出来上がったページが次の画像です。この様にnamespaceに従った階層でPHPDocに従ってメソッド、プロパティとその説明などが記述されます。
 
 自動生成で相当リッチなページが作られます。注意点としてリッチすぎてPHPDocを厳格に扱っていない場合、空欄による穴あきだらけになる点があります。