ytake Hatena

Web Application Developer

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フレームワーク