ytake Hatena

Web Application Developer

ytake/gardening HomesteadライクなCentOS

ytake/gardening という、
HomesteadのCentOS版みたいなものを公開中です。

laravel/homestead との違いは、
OSはUbuntu14.04ではなく、CentOS7となっています。

またwebサーバが、ApacheとNginxが切り替えて利用できるようになっています。
この環境ではデフォルトで用意していませんが、リバースプロキシなども利用できると思います。

またPHPはPHP7が標準となっており、
hhvmは3.9が利用できます。

hhvmを利用する場合は、プロジェクト直下に.hhconfigも設置されますので、
hackもすぐに利用できます。

インストール済みのものは下記の通りです。

  • Git
  • PHP 7.0(remi repository)
  • HHVM(3.9)
  • Apache(2.4.6)
  • Nginx(1.8)
  • MySQL(5.6)
  • Sqlite3
  • PostgreSQL(9.4)
  • Composer
  • Node.js (With Grunt, and Gulp)
  • Redis
  • Memcached
  • Elasticsearch
  • MongoDB
  • Java(1.8)
  • fluentd
  • Couchbase

PHPはremiリポジトリとなっていますので、アップデート時などにはremiリポジトリを指定してください。

Homesteadと利用方法はほとんど同じですが、
上記のもののうち、
MongoDB、Elasticsearch、fluentd、couchbaseはvagrant起動時にオフにするなど、
利用状況に合わせてオンオフができるようになっています。

詳しい利用方法や、設定項目はGitHubでご確認ください。
タイムゾーンは日本に設定してありますので、ほとんどなにもせずに開発環境が利用できます。

急ぎでCentOSの開発環境が必要な方でHomesteadのようなものが必要であれば、利用してみてください。

2015年ytakeまとめ

本年は特にたくさんの方とご一緒する機会が多く、
周りの環境も多く変わる一年となりました。

今年作ったもの

今年はLaravel関連のパッケージを作ることが多かった一年でした。
後悔しているパッケージの他にも自作フレームワーク作りを行い、
何度も作っては削除、作っては削除・・の繰り返しですが、
作ることで学べることがたくさんありますので、来年度も引き続き何かしら作っていこうと思っています。

Laravel.Smarty

LaravelでBladeと併用してSmartyを使用可能にするパッケージです。
海外の開発者が作った同種のパッケージは、テストコードもなく、
パッケージにSmarty自体が内包されていたり、ほとんど拡張性がないものであったりと、
実際に使う上で頭を悩ますことも多く、当時自分で利用していたものをパッケージとして切り出したのが始まりです。

このパッケージ自体は去年作ったものですが、
Smartyを根強く使い続けている海外の方からの問い合わせも多く、
テンプレートのキャッシュをファイル以外にmemcachedやredisなどにも対応させて、
色々改良を加えました。

github.com

Laravel-Aspect

これはLaravelでAOPを利用可能にするパッケージで、
文字通りMVC(Model, View, Controller)としてフレームワークを利用している方にはあまり利用する機会がないかもしれませんが、
PHPにおいてもコンポーネント指向や、設計パターンなどに注力される方も増え、
特にドメイン駆動などで利用できるアスペクトによる関心の分離などをLaravelでサポートするものです。

ドメイン駆動開発ではアスペクトは必須ではありませんが、
多くのヒントや、実際に適所に使うとさらに考え方を広めてくれると思います。
これは後述するアスペクト指向の書籍で色々考えさせられることが多かったため、
実際の開発でも利用することが多くなり、来年度も引き続き利用していこうと思っています。
個人的には自分で作ったライブラリの中で一番使っているものかもしれません。
改良点はまだ多くありますので、引き続きバージョンアップを行う予定です。

github.com

Laravel-FluentLogger

LaravelのログをFluentdと連携するためのパッケージです。
大規模開発や、ログ解析などを行うことも多くなり、
Laravel向けのパッケージもなかったのでパッケージとして切り出したものです。

LogのデフォルトをFluentdに変更することや、pushHandlerで追加なども簡単にできるようになっています。
実際の開発でも使っています。

github.com

上記のパッケージのうち、Laravel-FluentLoggerとLaravel.SmartyはともにLumenでも利用できるようになっており、
どのパッケージも最新のLaravel5.2でも利用できるようになっています。

Laravel-Couchbase

データベースのドライバ拡張と、SessionやCacheでCouchbaseを利用するパッケージです。
このデータベースがサポートしているN1QLにも対応したもので、
Laravelのクエリービルダで利用できるようにしました。

<?php 

$this->app['db']->connection('couchbase')
    ->table('testing')->key($key)
    ->returning(['*'])
    ->where('click', 'to edit')->update(
        ['click' => 'testing edit']
    );

github.com

書籍

今年は2冊執筆させていただく機会がありました。
初めての執筆でいろんなアドバイスを頂いたり、うまく進まず迷惑をかけたりなどもあり、
反省するとともに貴重な経験となり、今後の糧としていきたいと思います。

Laravelリファレンス

www.amazon.co.jp

Laravelエキスパート養成読本

www.amazon.co.jp

嬉しいことに来年も引き続き執筆させていただける機会をいただいており、
嬉しい悲鳴が続きそうです。
Laravelリファレンス執筆時に掲載から漏れたスピンオフ電子版書籍は近いうちに詳細をお伝えできるかもしれません。

発表

今年はPHP勉強会をはじめ、PHPカンファレンス福岡、PHPカンファレンスなどで発表させていただきました。
いろんな方との交流も増え、参加して学ぶことも多くあり大変有意義に過ごしました。

php開発で使うタスクランナー gulp

今のバージョンでは動かないと思いますが、一応・・

www.slideshare.net

Laravel / Lumen 次の一歩

中級者へのヒントとなるような内容を発表しました

www.slideshare.net

Laravel5.1 Release

Laravel5.1発表直前に発表したものです。

www.slideshare.net

phpspecで始めるBDD

www.slideshare.net

LaravelとMVCの先へ

www.slideshare.net

PHPデプロイツールの世界

www.slideshare.net

zend-expressiveを触ってみよう

今年の後半に発表されたzend expressiveを実際の開発に利用した時の話をまとめたものです

www.slideshare.net

後半になるにつれてLaravel以外のもに変わっていってるのも面白いところです。
執筆時期と重なっていたため、あまりLaravel関連のことを発表しませんでした。

今年できなかったもの

LaravelJp Recipe

Laravelレシピサイトの5.1、もしくは5系以降のサポートサイトが途中まで進めたにもかかわらず、
書籍執筆に注力していたため、あまり進捗がない状態となってしまいました。

recipes.laravel.jp

来年は、このレシピサイトを5系以降にも対応させるか、
Laravel学習サイトとして、初心者から中上級者向けのサイトへと変更するかを検討しているところです。
さすがに古くなってきたので何かしないと・・・

仕事、その他

今年は転職したこともあり(去年もしたではないか!)、
身の回りも大きく変わり、いろんなチャレンジができる良い環境となりました。 また、今年はPHPに加えてgolangも取り入れることになり、幾つかに導入したりしていました。

家も建ち、ローンを返済する身となりました。

まとめ

今年はいろんな方に声をかけてもらうことが多く、
なかなか応えられないこともありましたが、大変貴重な体験を多くさせていただきありがとうございました。
2016年はさらに加速させて、いろんな物事にチャレンジしていこうと思います。

2015年ありがとうございました。

Lumen活用

これは Laravelリファレンス発売記念、販売促進アドベントカレンダー www.adventar.org の2015年12月18日分です。

今回は書籍では取り上げていませんが、Lumenについてです。 弊社のAPIでもLumen(とgolang)を活用していたりします。

Lumenおさらい

LumenはLaravelのilluminateコンポーネントを組み合わせて構成されているフレームワークで、
マイクロフレームワークともいわれていますが、多くの機能を有していて、
通常のフレームワークと言っても差し支えないのかもしれません。

Laravelとの違いは

  • RouterはfastRouteを利用
  • configなどは必要なものしか読み込まない(コンテナから取得時に読み込まれる)
  • サービスプロバイダは単純にコンテナに登録するのみ
  • ファサードを利用するかしないか選択可
  • APP_KEY生成コマンドなし

ぐらいでしょうか。

簡単な使い方はブログに書かれている方や、公式マニュアルを見るだけで十分理解できると思います。

.env使う場合

bootstrap/app.phpで忘れずにDotenv::loadのコメントアウトを外しておきましょう。

<?php

require_once __DIR__.'/../vendor/autoload.php';

\Dotenv::load(__DIR__.'/../');

.envを利用しない場合は、Laravelと同じように $value = getenv($key); が先に動きますので、
webサーバやphp-fomなどで環境変数を利用できます。
複数台構成の場合は.envで管理するよりもサーバなどで環境変数と組み合わせた方が事故が少ないと思います。

タイムゾーン変更

アプリケーションクラスでAPP_TIMEZONEを利用していますので、
必ず利用したいタイムゾーンを.envまたはsetEnvなどで指定しておきましょう。

APP_TIMEZONE=Asia/Tokyo

アプリケーションクラス継承

アプリケーションに合わせてアプリケーションクラスを継承する場合が多いでしょう。

利用しない機能はLaravel同様に除外できます。
$availableBindingsプロパティを上書きしてコンパクトにすることができます。
Laravel同様いらないものを削除していきましょう。

ルータキャッシュ

Lumenはルータのディスパッチャを変更できますので、fastRouteのキャッシュルータに変更してみましょう。
bootstrap/app.phpなどを利用するといいでしょう。

<?php 

$app->setDispatcher(FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) use ($app) {
    /** @var \Laravel\Lumen\Application $app */
    foreach ($app->getRoutes() as $route) {
        $r->addRoute($route['method'], $route['uri'], $route['action']);
    }
}, [
    'cacheFile' => storage_path('route.cache'),
]));

ルータにクロージャを利用している場合は利用できませんので注意しましょう。
キャッシュファイルがある場合は/app/Http/routes.phpを読み込まないようにするなどしておきましょう

artisanコマンド削除

デフォルトで用意されているコマンドを利用しない場合は、簡単に除外できます。
App\Console\Kernelクラスの$includeDefaultCommandsプロパティを利用しましょう。

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /** @var bool  */
    protected $includeDefaultCommands = false;
    
    // 省略

綺麗に書く

ファサードを使わずに、データベースなどを綺麗に利用したい場合はサービスプロバイダを利用しましょう。
簡単な例を紹介します。

<?php

namespace App\Repositories;

interface CacheRepositoryInterface
{

    /**
     * @return mixed
     */
    public function getHoge();
}
<?php

namespace App\Repositories;

use Illuminate\Cache\CacheManager;

class CacheRepository implements CacheRepositoryInterface
{
    /** @var CacheManager  */
    protected $cache;

    /**
     * CacheRepository constructor.
     *
     * @param CacheManager $cache
     */
    public function __construct(CacheManager $cache)
    {
        $this->cache = $cache;
    }

    /**
     * @return mixed
     */
    public function getHoge()
    {
        return $this->cache->driver()->get('hoge');
    }
}

Laravelではこのまま動作せることができますが、
Lumenの場合は、CacheManagerの依存を自動で解決できません。
コンテナからcacheを取得してコンフィグなどを読み込ませます。

サービスプロバイダ例

    public function register()
    {
        $this->app->bind('App\Repositories\CacheRepositoryInterface', function ($app) {
            return new CacheRepository($app->make('cache'));
        });
    }

あとは利用したいクラスなどでコンストラクタインジェクションなどを使いましょう。
LaravelでもLumenでも、どんなものでも再利用できるようになります。

Laravel services.jsonのなぞ

これは Laravelリファレンス発売記念、販売促進アドベントカレンダー www.adventar.org の2015年12月17日分です。

今回はLaravelに欠かせないファイル、services.jsonについて紹介します。

services.json

Laravel5.1ではbootstrap/cache配下に作成されます。
Laravel4でも存在しています。

このファイルはHttp\Kernel、またはConsole\Kernelのbootstrappersプロパティで指定されている、
Illuminate\Foundation\Bootstrap\RegisterProvidersクラスの内部で利用する、
Illuminate\Foundation\ProviderRepositoryクラスが作成します。

ファイルがない場合はcompileManifestメソッドが作成をしています。
config/app.phpのprovidersに記述されてサービスプロバイダが、配列でこのメソッドに渡され、
各サービスプロバイダのインスタンスを生成して、遅延読み込みかそうでないか、
whenによるregisterが存在するかどうかなどを調べてservices.jsonに書き込みます。

以降はこのファイルを元にフレームワークへサービスコンテナなどの登録が行われます。
このファイルがない場合はどうなるのでしょうか?

App\Http\Kernelクラスで$bootstrappersプロパティを使って上書きます。
といってもコメントアウトするだけです。

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * @var array
     */
    protected $bootstrappers = [
        'Illuminate\Foundation\Bootstrap\DetectEnvironment',
        'Illuminate\Foundation\Bootstrap\LoadConfiguration',
        'Illuminate\Foundation\Bootstrap\ConfigureLogging',
        'Illuminate\Foundation\Bootstrap\HandleExceptions',
        'Illuminate\Foundation\Bootstrap\RegisterFacades',
        // 'Illuminate\Foundation\Bootstrap\RegisterProviders',
        'Illuminate\Foundation\Bootstrap\BootProviders',
    ];

    // 省略

bootstrappersプロパティ

このプロパティを利用してフレームワークの動作を独自のものに変更することもできます。
例えば.envファイルではなく他のものを利用するように変更したり、
前回紹介したファサードの削除、
設定ファイルをphpの配列ではなく別のものにしたり、ということができます。

services.jsonを利用しないように変更すると、
フレームワークデフォルトの状態のままであれば、グローバルミドルウェア通過時にクラスが解決できずに、
エラーとなります。
ミドルウェアを全てコメントアウトしてもフレームワークが依存しているクラスのインスタンスを生成できずに動作できなくなります。

このservices.jsonフレームワークの最もコアとも言えるファイルというわけです。

気をつけること

フレームワークは常にこのserivces.jsonに記述されているprovidersと、config/app.phpのprovidersの個数を比較しているため、
登録済みのサービスプロバイダの一部を遅延や、そうでないものに変更すると反映されません。
この場合はかならずphp artisan clear-compiledを実行して最新のservices.jsonを作成しましょう。

この動作さえ理解していれば、フレームワークを様々な方法で拡張することができるようになるでしょう!
(あまり実用的ではありません)

Laravel データベース拡張のヒント

これは Laravelリファレンス発売記念、販売促進アドベントカレンダー www.adventar.org の2015年12月16日分です。

データベース拡張

開発時に、Laravelがサポートしていないデータベースを使う必要がある場合や、
クエリビルダなどの挙動を変えたい場合があるかもしれません。

サポートしていないデータベースを使うためのパッケージやライブラリは、
packagitやpackalyst、GitHubなどでも簡単に見つけることができます。

ここではこうしたライブラリ開発や、特定の要件を満たせるように拡張する方法を紹介します。

データベースドライバを追加する場合

Illuminate\Database\Connectionクラスを継承して、対応するコネクションクラスを作成します。
Illuminate\Database\ConnectionInterfaceインターフェースを実装して、クラスを独自に作成してもかまいません。

継承したクラスは、サービスプロバイダなどで次のように記述します。

/**
 * {@inheritdoc}
 */
public function register()
{
    // add couchbase extension
    $this->app['db']->extend('couchbase', function ($config) {
        /** @var \CouchbaseCluster $cluster */
        return new CouchbaseConnection($config);
    });
}

上記の例ではcouchbaseという名前のデータベースドライバを追加する、という処理になります。
それぞれの処理に合わせて各メソッドをオーバーライドしていきましょう。

insert

insertの挙動を変えたい場合は、内部でコールされているstatementメソッドを変更します。

public function statement($query, $bindings = [])

update, deleteなど

insert同様に、これらは内部でaffectingStatementが利用されています。

public function affectingStatement($query, $bindings = [])

selectはselectのままです。

クエリビルダの拡張

クエリビルダのSQLや、独自の機能を持たせたい場合があるかもしれません。
この場合はドライバに対応するProcessor,Grammerクラスを作成します。
queryメソッドをオーバーライドします。

    /**
     * Get a new query builder instance.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function query()
    {
        return new QueryBuilder(
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
        );
    }

$this->getQueryGrammar()$this->getPostProcessor()
データベースに対応したクラスのインスタンスが取得されるようにしておきましょう。

クエリビルダは、Grammerクラスを継承して動作を変えることができます。
クエリビルダは内部でcompileXXXXというメソッドがコールされ、
SQLがビルドされていきます。

たとえば、例に挙げたcouchbaseに対応させるため、
このデータベースがもつ独自のupsertをクエリビルダで実装する場合は、 次のようになります。

    /**
     * supported N1QL upsert query
     *
     * @param Builder $query
     * @param array   $values
     *
     * @return string
     */
    public function compileUpsert(Builder $query, array $values)
    {
        $table = $this->wrapTable($query->from);

        if (!is_array(reset($values))) {
            $values = [$values];
        }

        $columns = $this->columnize(array_keys(reset($values)));

        $parameters = [];

        foreach ($values as $record) {
            $parameters[] = '(' . $this->parameterize($record) . ')';
        }

        $parameters = implode(', ', $parameters);

        return "UPSERT into $table ($columns) values $parameters RETURNING *";
    }

こうすることで簡単にクエリビルダメソッドが追加できます。

同じ要領でバッククォートを外すなど、オーバーライドするだけで、
フレームワーク本体に手を加えることなく簡単にドライバ追加、または挙動変更が行えます。

データベースの基本は、Laravelリファレンスでも解説しています。

Laravel リファレンス[Ver.5.1 LTS 対応] Web職人好みの新世代PHPフレームワーク

Laravel ファサードを利用しないメリット

これは Laravelリファレンス発売記念、販売促進アドベントカレンダー www.adventar.org の2015年12月15日分です(1日遅れ更新中)。

必ずしも簡単 が重要ではない

Laravelを選ぶ利用の一つとして、
フレームワークが持つ豊富な機能と、
静的メソッドに見えるファサードという記法がみやすく、分かり易い!
EloquentORM
という点が挙げられると思います。

このEloquentとファサードは賛否いろいろ意見はありますが、
アプリケーションのモックや、小規模アプリケーションでは使い勝手が良く、
ある程度動かすところまで開発するには大変便利です。

複数人で開発を行い、長期的に運用、保守していかなければならないような場合に、
この便利な機能たちとどう付き合っていくべきでしょうか?

どこでも利用できるが故に複雑になり易い

ファサードを利用する場合、多くの方はインスタンス生成や、
クラスに依存しているものなどを考えることが少ないかもしれません。

<?php

namespace App\Http\Controllers;

use App\User;
use App\Entry;

class SampleController extends Controller
{
    /**
     * @param $id
     *
     * @return $this
     */
    public function apply($id)
    {
        $validator = \Validator::make(\Input::all(), ['content' => 'required']);
        if ($validator->fails()) {
            return redirect()->route('hoge')->withInput()->withErrors($validator);
        }

        try {
            $result = User::find($id);
            $requests = \Input::all();
            $entry = Entry::create($requests);
            \Log::info('added entry', $requests);

            return view('form')->with('user', $entry);
        } catch (\Exception $e) {
            $session = \Session::get('from_values');
            ...
        }
    }
}

たとえばコントローラで上記のように実装してしまうと、このクラスをテストするのは難しくなります。
このコントローラはデータベースに強く依存しているため、
拡張がなく、使い回すこともできない状態です。

このクラスの依存関係を表すと次のようになります。

    /**
     * SampleController constructor.
     *
     * @param User            $userModel
     * @param Entry           $entryModel
     * @param FactoryContract $validator
     * @param Request         $request
     * @param SessionManager  $session
     * @param LoggerInterface $log
     * @param Redirector      $redirect
     */
    public function __construct(
        User $userModel,
        Entry $entryModel,
        FactoryContract $validator,
        Request $request,
        SessionManager $session,
        LoggerInterface $log,
        Redirector $redirect
    ) {
        $this->userModel = $userModel;
        $this->entryModel = $entryModel;
        $this->validator = $validator;
        $this->request = $request;
        $this->session = $session;
        $this->log = $log;
        $this->redirect = $redirect;
    }

多少極端な例ではありますが、このクラスが依存しているものです。

ここではコントローラクラスを例としていますが、もしかすると、セッションやリダイレクトに依存しているモデルクラス、
データベースに依存しているログクラスなどを実装してしまっているのかもしれません。

実際にファサードを使っているクラスに対して、どんなものが依存しているか確認してみてください。

ファサードを使わないメリット

メリットとしては、上記の例のようにクラスの依存関係がわかりやすくなり、
テストがしやすくなるのはもちろん、再利用性が高くなるのがあげられます。

たとえばこれから開発していくもので、明らかにモジュールとして用意できる機能や、
拡張して実装していけば良い、といったものを実現するには疎結合なアプリケーション作りを心がけていなければ難しくなります。
これはパッケージ開発も同じです。
(Laravelを主に考えたものであってもインターフェースなどに依存させていれば、Laravel以外でも利用できます)

ファサードを利用すれば簡単に開発できるのかもしれません。
ですがこのファサードはそれぞれのプロジェクトで自由に変更できます。
モジュールとして提供するものにファサードが含まれている場合、それは実装に制約をあたえ、
かつLaravel以外の選択肢を与えないことになります。

    'aliases' => [

        'AppLication'       => Illuminate\Support\Facades\App::class,
        'ArtisanConsole'   => Illuminate\Support\Facades\Artisan::class,
        'Db'        => Illuminate\Support\Facades\DB::class,
    // 省略

config/app.phpで上記のようにファサードが変更されているプロジェクトがあれば、すでに利用できません。
また不特定多数の方に提供するパッケージも同様にファサードに依存してはいけません。
(peclのEventエクステンションを利用している環境ではEventファサードを変更しなければスムーズに動きません)

ファサードの仕組み、実態を知る

これについては書籍でも扱っています。
また公式のリファレンスでもファサードと実クラス、コンテナのサービス名の一覧が用意されています。

データベースの例

よく利用する機会が多いDBファサードを利用しない場合は次のようになります。

<?php

namespace App\DataAccess;

use Illuminate\Database\DatabaseManager;

class Sample
{
    /** @var DatabaseManager */
    protected $db;

    /**
     * @param DatabaseManager $db
     */
    public function __construct(DatabaseManager $db)
    {
        $this->db = $db;
    }

    /**
     * @param array $attributes
     *
     * @return int
     */
    public function add(array $attributes)
    {
        return $this->db->connection()->insertGetId($attributes);
    }
// 省略

このデータベースを利用するクラスに依存する抽象レイヤは次のようになります。

<?php

namespace App\Repositories;

use App\DataAccess\Sample;

/**
 * Class SampleRepository.
 */
class SampleRepository
{
    /** @var Sample */
    protected $sample;

    /**
     * @param Sample $sample
     */
    public function __construct(Sample $Sample)
    {
        $this->sample = $sample;
    }

    /**
     * @param array $attributes
     *
     * @return int
     */
    public function add(array $attributes)
    {
        return $this->sample->add($attributes);
    }
// 省略
}

ファサードを使う場合はDBファサードと、ファサードを解決するための仕組みに強く依存していましたが、
上記のようにすると、DatabaseManagerのみに依存したクラスとなります。
極端なことを言うと、Laravelでなくても上記のコードはどんなフレームワークでも、フレームワークではなくても動作します。

Laravelのサービスコンテナは、コンストラクタインジェクションによる解決ができますので、
DatabaseManagerやSessionManagerなどのクラスの他に、インターフェースを記述して利用できます。
インターフェースに依存するように記述することで、よりフレームワークから分離されたコードになり、
汎用性も高く、どんなシステムにも組み込めるコードになります。

Laravelを使う上で一通りの機能を理解して、中級者へステップアップする際にはこうした実装方法を必ず覚えておきましょう。

ファサードを無効にして少しの高速化

ファサードを利用しないことで、コンテナへのアクセサを利用しなくなりますので、
多少なりとも高速に動作させれるようになるかもしません。  

ファサード自体を無効にする場合は、config/app.phpのaliases配列を空にするだけでも問題ありませんが、
このalasesを利用しているのは、Illuminate\Foundation\Bootstrap\RegisterFacadesクラスです。
このクラスは、App\Http\Kernel、App\Console\Kernelで利用されますので、
両クラスの基底クラスのbootstrappersプロパティからRegisterFacadesを利用しないように上書きましょう。

ファサードを無効にした場合はide_helperは生成してもクラスなどは出力されません。
ですが、こうしたプラグインがなくても、全て補完できるようになってます。

コード例をたくさん載せようと思っていたら、前振りだけで長くなってしまいました・・・。
この続きはまた今度・・。

Laravelリファレンスにはファサードを利用しない場合のサンプルも多く載っています。
アプリケーション作りの参考にもなると思いますので、
書店などで手にとってみてください。

Laravel リファレンス[Ver.5.1 LTS 対応] Web職人好みの新世代PHPフレームワーク

Laravel Broadcast Eventを使いこなそう

これは Laravelリファレンス発売記念、販売促進アドベントカレンダー www.adventar.org の2015年12月14日分です(1日遅れ更新中)。

Laravel Events

LaravelのEventはフレームワークに登録されているコアのイベントを監視して、 実行させるイベント、 またはユーザーが実装したイベントを実行できます。 このイベントについては書籍「Laravelリファレンス」でも解説しています。

さらにこのイベントにはブロードキャストイベントがあります。

このブロードキャストはwebsocketを利用して、 イベント実行できますブラウザに通知するなどの リアルタイム性あるwebアプリケーションの開発が簡単に行えます。

ただし、このブロードキャストは言語仕様上PHPのみで実装することが難しく、 Node.jsなどと併用する必要があるということに 注意しておきましょう。 このブロードキャストは書籍の一部で取り上げていましたが、 JavaScriptにも強く依存しているため、 解説を外した悲しい機能です。

公式リファレンスにも簡単な実装方法が載っています。 公式リファレンスを照らし合わせながら、 理解を深めてみましょう。

Broadcastに対応したEventクラス

Eventはmake:eventで作成できます。

$ php artisan make:event Broadcast\\NotificationEvent

上記のコマンドで実行すると、Events配下にPSR-4に沿ったディレクトリを作成し、クラスが配置されます。
(Eloquentクラスも全て同じです。直下が置き場所ではありません。ソースコードに書いてあります:) )

作成されたクラスは次のとおりです。

<?php

namespace App\Events\Broadcast;

use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class NotificationEvent extends Event
{
    use SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return array
     */
    public function broadcastOn()
    {
        return [];
    }
}

Broadcastを利用する場合は、Illuminate\Contracts\Broadcasting\ShouldBroadcastインターフェースを実装します。
実装する必要があるものは、broadcastOnメソッドだけです。
このメソッドはwebsocketで利用するチャンネルを意味します。

SerializesModelsとは?

Illuminate\Queue\SerializesModelsトレイトは、
イベントクラスのプロパティにEloquentORMのインスタンスが利用されている場合にのみ作用します。

このトレイトには、下記のsleep、wakeupメソッドがあり、
これらはQueueでserialize、 unserializeする場合に作用するマジックメソッドです。

<?php

// 一部抜粋
    public function __sleep()
    {
        $properties = (new ReflectionClass($this))->getProperties();

        foreach ($properties as $property) {
            $property->setValue($this, $this->getSerializedPropertyValue(
                $this->getPropertyValue($property)
            ));
        }

        return array_map(function ($p) {
            return $p->getName();
        }, $properties);
    }


    public function __wakeup()
    {
        foreach ((new ReflectionClass($this))->getProperties() as $property) {
            $property->setValue($this, $this->getRestoredPropertyValue(
                $this->getPropertyValue($property)
            ));
        }
    }

イベント実装

作成したクラスを使ってブロードキャストイベントの準備をします。
EloquentORMを利用すれば簡単にできますが、ここではEloquentを利用せずに実装します。

(アプリケーション実装時にデータベースアクセスの全てをEloquentで実装するのはお勧めしません。
実装に利用するのは簡単ですが、イーガーローディングなどを使ったとしても、
発行されるIN句や、その他のクエリについて理解しておくべきだからです。
これについては長くなるのでまた別の機会に・・)

作成されたクラスにあるbroadcastOnメソッドを次のようにします。

<?php

namespace App\Events\Broadcast;

use App\Events\Event;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class NotificationEvent extends Event implements ShouldBroadcast
{
    /**
     * @return array
     */
    public function broadcastOn()
    {
        return [
            'Laravel.Reference'
        ];
    }
}

次にコントローラやサービス、その他のクラスなどからこのイベントを利用してみましょう。 コントローラの場合は次のようになるでしょう。

<?php

namespace App\Http\Controllers;

use Illuminate\Contracts\Events\Dispatcher;
use App\Events\Broadcast\NotificationEvent;

/**
 * Class IndexController
 */
class IndexController extends Controller
{
    /**
     * @param Dispatcher $dispatcher
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function index(Dispatcher $dispatcher)
    {
        $dispatcher->fire(new NotificationEvent());
        return view('index');
    }
}

ファンクショナルテストであれば次のように出力内容が確認できます。

<?php

class BroadcastTest extends TestCase
{
    /** @var \Illuminate\Filesystem\Filesystem */
    private $filesystem;

    public function setUp()
    {
        parent::setUp();
        $this->filesystem = new \Illuminate\Filesystem\Filesystem;
    }

    public function testBroadcastLogHasBeenOutput()
    {
        $this->call('GET', '/');
        $this->assertResponseStatus(200);
        $path = storage_path('logs/laravel.log');
        $log = $this->filesystem->get(storage_path('logs/laravel.log'));

        $this->assertNotFalse(strpos($log, '[Laravel.Reference]'));
        $this->beforeApplicationDestroyed(function() use ($path) {
            $this->filesystem->delete($path);
        });
    }
}

phpunit.xml 記述を忘れないようにしましょう

ブラウザなどで確認するにはconfig/broadcasting.phpのdefaultをlog、または.envファイルでBROADCAST_DRIVERを利用します。

次のようにlogに出力されているはずです。

testing.INFO: Broadcasting [App\Events\Broadcast\NotificationEvent] on channels [Laravel.Reference] with payload:

動作を確認したところで実際にwebsocketで確認してみましょう。

websocket利用の手引き

この機能を利用するには、pusherを利用するか、 Redisのpubsubを利用する必要があります。

忘れずに下記のライブラリをcomposer.jsonに記述するか、コマンドでインストールしてください。

"pusher/pusher-php-server": "~2.0"
"predis/predis": "~1.0"

pusherを利用する場合

Pusher | Leader In Realtime Technologies pusherを利用する場合はユーザー登録を行い、
必要な情報を記述してconfig/broadcasting.phpや.envを使ってKEYなどを指定してください。

つぎのコードを使って動作させてみましょう。

<script src="//js.pusher.com/3.0/pusher.min.js"></script>
<script>
    $(document).ready(function () {
        var pusher = new Pusher('{{{ config('broadcasting.connections.pusher.key') }}}');
        var channel = pusher.subscribe('Laravel.Reference');
        channel.bind('App\\Events\\Broadcast\\NotificationEvent', function (data) {
            console.log(data);
        });
    });
</script>

イベント名を変更

デフォルトのまま利用すると、イベント名が実行クラス名となり、あまり好ましくありません。
必ずbroadcastAsメソッドを使って変更するようにしてください。

送信データ

Eloquentを利用する場合は、自動で値を利用してくれますが、
それ以外の場合は、broadcastWithメソッドを使ってデータを指定します。
サービスクラスやリポジトリなどを利用してもいいでしょう。

これらを踏まえるとサンプルコードは以下の通りです。

<?php

namespace App\Events\Broadcast;

use App\Events\Event;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class NotificationEvent extends Event implements ShouldBroadcast
{
    /**
     * @return array
     */
    public function broadcastOn()
    {
        return [
            'Laravel.Reference'
        ];
    }

    /**
     * @return string
     */
    public function broadcastAs()
    {
        return 'reference.event';
    }

    /**
     * @return array
     */
    public function broadcastWith()
    {
        return [
            'message' => 'laravelリファレンス'
        ];
    }
}

jsのコードも次のように変更しましょう。

<script>
    $(document).ready(function () {
        var pusher = new Pusher('{{{ config('broadcasting.connections.pusher.key') }}}');
        var channel = pusher.subscribe('Laravel.Reference');
        channel.bind('reference.event', function (data) {
            console.log(data);
        });
    });
</script>

Queueと組み合わせる

BroadcastはWebsocketにフォーカスしがちですが、Queueと組み合わせて利用できることを忘れてはいけません。
クラス作成時に実装するShouldBroadcastは、デフォルトに設定されているqueueに登録して実行されます。
つまり、sync以外のものがデフォルトにされている場合は、queueを利用しなければ送信されません。

常にqueueqドライバをsyncとしたい場合は、
Illuminate\Contracts\Broadcasting\ShouldBroadcastNowインターフェースを利用してください。

Queueを利用する場合は、onQueueメソッドを利用します。

public function onQueue()
{
    return 'broadcast';
}

こうすることで、ジョブ登録後、
php artisan queue:work --queue=broadcast
という具合で任意のタイミングで配信などが行えます。

Broadcastの簡単な利用方法と、公式リファレンスに記述されているものに少し解説を付け加えて紹介しました。

アプリケーション作りに役立てみてください。