My I/O

インプットしたことをアウトプットするブログ

PHPはどのように動くのか~第1、2章感想

PHP内部構造について

PHPスクリプトがどのように解析されて実行されるかということが分かりやすく示されていました。

自分の書いたPHPのコードが解析・実行されるということが、あたりまえのことであるのにほとんど意識したことがないことに少し恥ずかしさを覚えました..。

1章2章を読むことだけでも、

  • 型を意識しない比較
  • 引数の数
  • コメント
  • 同じ実行結果が得られる場合の記述の選択

等々について考えて開発のをすることで、パフォーマンスだけでなくコードの質も変わってくると感じています。

特に引数の数という点については、クラス設計にも関わってくる事柄であり、自分が開発する上でパフォーマンスを考えた設計・プログラミングができるように頑張りたいです。

オペコードとオペランド

主要な構文のオペコード・オペランドが記載されており、目を通すと自分が記述するPHPスクリプトから実行されるオペコード・オペランドがなんとなく想像がつくようになってきました。

気を付けたいと思ったことは、以下です。

  • オペコードの数を減らす意識でコーディングする
  • PHP側(ZendEngine側というのでしょうか)に判断を任すような曖昧なコーディングをしない

本文には、四則演算や比較や条件分岐時等々のオペコード・オペランドの例が記載されていて、この場合はこの理由でコストが高くなる、という解説がありました。

処理速度の計測はサーバスペック・状態・OSパラメータなど、様々な要因に左右されますが、PHPのコードをオペコードまで考えた 定石 の記述を意識してコストパフォーマンスの良いコーディングしたいと思いました。

2章は何度か読み返したい内容です。

Laravel5.5 ファサード

ファサードは、サービスコンテナで動くサービスの静的プロキシ。 サービスコンテナで提供されるサービスは、

  • コントローラのコンストラクタの引数
  • サービスプロバイダのbootの引数
  • $this->app->make()

などで利用できるが、静的に利用することで、

テストの行いやすさ・柔軟性を保ちながらも、簡潔で記述的

ファサード 5.5 Laravel

となる。

ファサードの多用注意

簡単に使用できるため使いすぎる傾向がある。 クラスが大きくなりすぎないよう、責任範囲を小さくするように気を払うこと。

依存注入を使用すれば、クラスが大きくなりすぎることに伴う、大きなコンストラクタの視覚的なフィードバックにより、この危険性は抑制されます。

依存注入=コンストラクタの引数を使う

DBのファサード利用例

use Illuminate\Support\Facades\DB;


$user =  DB::table('users')
            ->where('user_id', 1)
            ->get();

ファサード追跡

上のDBファサードを使ってレコードを取得する流れを追ってみました。

1 Illuminate\Support\Facades\DBにはtable()はない

親クラスを追跡。

namespace Illuminate\Support\Facades;

class DB extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'db';
    }
}

2. 親のFacadeクラスにもないが、__call()が定義されている

$instance = static::getFacadeRoot(); でコンテナに登録したDBコンテナのオブジェクトを取得しており、 return $instance->$method(...$args); でオブジェクトからtable()を実行している。

...$args ってなんだろう..。 ... って。文字列結合に使うドットを3つ並べるとなんなんだ..) ( getFacadeRoot() では resolveFacadeInstance() が呼ばれているだけ。 FacadeRoot という言葉を使っているのはなにを想定してだろうかな?)

    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }
}

3. DBがどのオブジェクトと結合されているのか確認してtable()を存在を確かめる

// config/app.php
    'providers' => [

         ....
        Illuminate\Database\DatabaseServiceProvider::class,
// DatabaseServiceProvider.php

        $this->app->singleton('db', function ($app) {
            return new DatabaseManager($app, $app['db.factory']);
        });

table()はなく __call()内で$this->connection() (戻り値は\Illuminate\Database\Connectionインスタンス)

// DatabaseManager.php

    public function __call($method, $parameters)
    {
        return $this->connection()->$method(...$parameters);
    }

ありました!table() !

// \Illuminate\Database\Connection

    public function table($table)
    {
        return $this->query()->from($table);
    }

リアルタイムファサードを追う

時間切れで終えなかったのでまた今度。

Laravel5.5 サービスプロバイダー

  • サービスプロバイダはアプリケーション設定の中心部
  • サービスプロバイダを利用し、初期起動処理(サービスコンテナの結合、イベントリスナ、フィルター、ルートなどの登録)する
  • config/app.phpファイルのproviders配列に記述されたサービスプロバイダが、ロードされる。
    • 実際に必要なときにのみロードされる「遅延」プロバイダがほとんど

register()はサービスコンテナの記事のときに使ったことがあるので、 boot()と遅延ロードの仕方を勉強。

boot()は他の全サービスプロバイダが登録し終えてから呼び出される

register()で登録した依存を、 boot()のタイプヒンティングで使ってみる。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Contracts\TestContainer;

class MichikoTestServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot(TestContainer $container)
    {
        var_dump('boot() ' . $container->hoge());die;
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {

        $this->app->bind(
            'App\Contracts\TestContainer',
            'App\Test\MichikoContainer'
        );
    }
}

f:id:michikoxxx:20180221111712p:plain

できました!

先にregister()でサービスコンテナでTestContainerとMichikoContainerが結合されて、  boot()でTestContainerをタイプヒンティングしたので、MichikoContainer使えるようになってました。 でもこの例の使い方じゃ意味ないかな…。

デフォルトのサービスプロバイダでいうと、encrypterやcookieなどなどがboot()時には使えるようになっているので config/app.phpのprovidersをチェックしてみるとこれbootで使えるんじゃんとなるのかな、と。

遅延ロードにしたいなら protected $defer = true;provides() を用意する

プロバイダを作る上で、基本遅延ロードにする頭の片隅にあるとよさそうな気がする。 最初からロードしておきたいものばっかり作ることは少ないだろうし。 遅延ロードで困ったとなったら戻せばいいしね。

    protected $defer = true;

  …

    public function provides()
    {
        return [TestContainer::class];
    }

利用の場面を考える

例えば会員登録をして利用するWebアプリケーションの場合、 ユーザーモデル的な物が欲しいわけで、 シングルトンでサービスコンテナで依存投入かな?

そして会員ランクや、管理者ログインなどの場合で機能が違う場合は $this->app->when(..).needs(..).give(); とかで実装を振り分けられるかな?

ちょっともっといい例が出せればいいけれども...会員登録があるWebサービスを趣味で作るっていってもなんか目的が必要だし… 思ったことは以上です。

Laravel5.5 サービスコンテナ

依存注入 という言葉がある。

イントロダクションでは、 コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」

という説明があるが、自分の言葉で解釈すると、 クラス内で特定のサービスの記述をせず、コードの変更無くほかの実装に付け替えできるようにしよう

ということだと思う。それは仕事でもちょいちょい言われていること…。 Laravelサービスコンテナの説明を見て、普段の設計に生かせるようにもなりたいが、、 話を戻して、サービスプロバイダでサービスコンテナで依存注入を実際にやってみる。

実践・サービスコンテナ

準備する→。コントローラー、サービスプロバイダ、インターフェイス、その実装

$ php artisan make:controller ContainerTestController
$ php artisan make:provider MichikoTestServiceProvider
$ php artisan make:model Contracts/TestContainer
$ php artisan make:model Test/MichikoContainer

プロバイダーのregister()で依存注入。

    public function register()
    {
        $this->app->bind(
            'App\Contracts\TestContainer',
            'App\Test\MichikoContainer'
        );
    }

コントローラーの__constructの引数にタイプヒンティングして、なんのオブジェクトが得られるか確かめる。

use App\Contracts\TestContainer;

class ContainerTestController extends Controller
{
    private $container;

    public function __construct(TestContainer $container) {
        $this->container = $container;
    }

    public function show() {
        var_dump($this->container->hoge());die;
    }
}

f:id:michikoxxx:20180220175223p:plain

実装クラスのMichikoContainerが取得できていました!

というわけで依存(MichikoContainer)を注入できました。 うーむすごい。

Laravel5.5 ライフサイクルの図解

理解を深めるために図解してみました。

リクエストのライフサイクル 5.5 Laravel

f:id:michikoxxx:20180217225355p:plain

図について付けたし。

  • 私の解釈です!信じないで!!
  • リクエストはHTTPリクエストに限った図にしており、consoleの場合どこで処理が分かれているのかは見つけられてない(だからHTTPリクエストに限った図に..)
  • App\Http\Kernel は、 Illuminate\Foundation\Http\Kernel を継承しており、リクエストが処理される前に行う必要がある処理等の定義をしている。(以下ドキュメント丸写し)
    • エラー処理
    • ログ設定
    • アプリケーション動作環境の決定
    • HTTPセッションの読み書き
    • アプリケーションがメンテナンスモードであるかの決定
    • CSRFトークンの確認
  • コードでは、$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); のようにinterface名が引数になっているにも関わらずHTTPカーネルApp\Http\Kernel が使われるのは、Laravelインスタンス生成時に、このインスタンスが指定されたらこのクラスを使用すると指定しているから。(bootstrap\app.php)
  • Laravelは全てがすごい ファサードの機能もまだきちんと理解しがたく、interfaceをちゃんと使って開発したことのない身にとっては修行。けどちゃんと使えれば、開発が楽で安全で早くなるのは分かる...!

Laravel5.5ルーティング

Laravel勉強中です。ルーティングの覚書。

ルーティング記述場所

ルーティングは、

プロジェクト名\routes\web.php

に記述し、

$ php artisan route:list

でルーティングを一覧で確認できる。

ルーティング記述

// Indexコントローラーのshow()にルーティング
Route::get('/', 'Index@show');

// 301リダイレクトしたいとき
Route::redirect('/oldpage', '/newpage', 301);

// {huga}は任意の値
Route::get('/hoge/{huga}', 'Hoge@show');

// {huga}の値を正規表現でバリデーション
Route::get('/hoge/{huga}', 'Hoge@show')
    ->where(['huga' => '[a-z]+');

// GET,POSTもGetAndPostコントローラで対応
Route::match(['get', 'post'],'/getpostcameon', 'GetAndPost');

コントローラ名に@メソッド名と記述しない場合は、 __invoke()が呼ばれ、これはシングルアクションコントローラということになる。