The Rank of F

〜渋谷で働く底辺のブログ〜

雑記 : Laravelの嫌いなところ

Laravelは好きだけど

Laravelは今最もトレンドなphp言語向けWebアプリケーションフレームワークで,5.1 LTSのリリース時から使い続けています。

以前使っていたZend Framework 2よりはるかに直感的に扱え,まさに「作りたい物を作りたいところだけ」作れる,そんな理想の存在がLaravelでした。

が,使い続けていくうちにだんだんと嫌なところも見えてきます。今回はそんなLaravelの嫌なところをまとめてみました。

1. Facadeが嫌い

Laravelにはまるで静的メソッドのようにインスタンスの動的なメソッドを呼び出すことの出来るFacadeという便利な仕組みがあります。

これはFacadeと呼ばれる静的クラスが,php言語のマジックメソッドを用いてDIコンテナにアクセスし,取得したインスタンスの動的メソッドを実行するというものです。

確かにこの構造は便利で,場所を気にせず好きなところでロジックを呼び出せます。

しかしこの構造はとてもいびつであり,DIコンテナとコンストラクタ・メソッドインジェクションによって下げられた疎結合性を一気に密結合なものへと変えてしまいます。

また,Facadeを用いた処理の実行は毎回マジックメソッドを経由して行われる為,実行コストも高いものとなります。そもそも強力なコンストラクタ・メソッドインジェクションがあるLaravelにおいては,Facadeの使用は必要最小限,もしくは完全に撤廃してしまっても問題ないように思います。

幸いにも,Laravel 5.5 LTSの時点ではFacadeの使用は任意であり,作成したプロジェクトのいくつかのファイルを修正することでFacadeを用いずにアプリケーションを作ることも可能です。また,そういった手法を用いることを好んでいる方も居ます(私もその一人です)

2. ヘルパ関数が嫌い

1.のFacadeが嫌いとほぼ同じ理由です。が,これに関しては回避手段がありません。LaravelはDIコンテナに対しstringで値を登録していることが多く,そのような値を取得するには最悪でもServiceLocator アンチパターンを用いるしかなくなってしまいます。

例えば,プロジェクトのストレージパスを取得するstorage_path()ヘルパ関数の実装がどのようになっているのか見てみましょう。

<?php

// 省略
if (! function_exists('storage_path')) {
    /**
     * Get the path to the storage folder.
     *
     * @param  string  $path
     * @return string
     */
    function storage_path($path = '')
    {
        return app('path.storage').($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
}
// 省略

まずはapp()ヘルパ関数に対し,path.storageという文字列を与えているのがわかります。

ではapp()ヘルパ関数の実装を見てみます。

<?php
// 省略
if (! function_exists('app')) {
    /**
     * Get the available container instance.
     *
     * @param  string  $abstract
     * @param  array   $parameters
     * @return mixed|\Illuminate\Foundation\Application
     */
    function app($abstract = null, array $parameters = [])
    {
        if (is_null($abstract)) {
            return Container::getInstance();
        }

        return Container::getInstance()->make($abstract, $parameters);
    }
}
// 省略

はい,そうなのです。path.storageという文字列を抽象として,パスという文字列を具象として登録しています。

文字列は紛れもない具象です。文字列を表す抽象は存在し得ず,またオブジェクトではない為,コンストラクタ・メソッドインジェクションのどちらも使うことが出来ません。

Laravelは多くの箇所でこのような文字列に対する具象登録を行っており,ServiceLocatorパターンを用いない限り登録された具象を取得することが出来ません。これは回避不可能です。

3. Contractsが嫌い

Laravelは具象と抽象を区別するためにContractsという名前空間を用いてインターフェイスを隔離しています。しかし全ての具象に対してContractsが存在するわけではなく,またContracts以下の名前空間の問題からasを用いた別名定義を用いなければ使えない状況が多発しています。

例えば,Controllerのメソッドインジェクションを用いてViewCacheインスタンスを取得しようとすると,以下のようになります。

<?php

namespace App\Http\Controllers;

use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\Contracts\Cache\Factory as CacheFactory;

class TestController extends Controller
{
    public function index(ViewFactory $view, CacheFactory $cache)
    {
        $view->make('welcome');
    }
}

4. Eloquentが嫌い

嫌いというか,非常に学習コストが高いです。ActiveRecordのようにも見えますがActiveRecordにもなりきれておらず,悪く言えば中途半端,良く言えばどうとでも使えるOR Mapperになっています。

というのも,Eloquentは前述のFacadeと親しい部分があり,それがデータベースを表すのか行を表すのかが非常に判別しにくくなっています。

具体的に言うとこんな感じになります。あまりにわかりにくいので変数名にinstanceと命名することでそれが何であるかを明確にせざるを得ませんでした。

それでもLaravelは何よりも現実的

確かに設計の面でいびつな部分も数多く存在するLaravelですが,現在存在するphp言語向けWebアプリケーションフレームワークの中では最も開発効率に優れたものであるということも間違いないであろうと思っています。

Facadeヘルパ関数はコードの品質を下げかねない諸刃の剣ですが,使い方を間違えさえしなければよほどのことにはなりません。仮に密結合なコードを書いてしまったとしても,LaravelのDIコンテナには後からそれを救う機能が備わっています。

結論としては,Laravelはやっぱり良いフレームワークだな,というところでした。