Laravelのビューコンポーザを自動で登録する

**ビューコンポーザ**は、表示に必要なデータの取得処理をコントローラ等から切り出して定義できるLaravelの機能です。

詳細はドキュメントをご参照ください。
英語：https://laravel.com/docs/5.6/views#view-composers
日本語：https://readouble.com/laravel/5.6/ja/views.html#view-composers

表示用ロジックを分離できるので多用しているのですが、
画面が増えてきて登録処理が大変なことになってしまったので、
自動的に登録される仕組みを入れてみました。

# `ComposerServiceProvider`

まず、公式サイトに載っているビューコンポーザの登録処理を見てみます。

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function boot()
    {
        // Using class based composers...
        View::composer(
            'profile', 'App\Http\ViewComposers\ProfileComposer'
        );

        // Using Closure based composers...
        View::composer('dashboard', function ($view) {
            //
        });
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
```

サービスプロバイダの`boot()`の中で`(ビューファイル名), (クラス名)`という形で登録します。
`composers()`メソッドを使うと、以下のようにまとめて書くこともできます。

```php
View::composers([
    \App\Http\ViewComposers\App\AboutComposer::class => 'app.about',
    \App\Http\ViewComposers\App\TermsComposer::class => 'app.terms',
]);
```

こっちは`(クラス名) => (ビューファイル名)`となります。
`composer()`と`composers()`で位置が逆になるのがなんとも気持ち悪い・・・

# ビューコンポーザが増えてくると

とにかく、上記のようにすればビューコンポーザを登録することができるのですが、
これを多用していると、画面が増えるたびに定義も増えていき、クラス名もフルパスなので、見た目も残念なことになっていきます。

```php
View::composers([
    \App\Http\ViewComposers\App\AboutComposer::class => 'app.about',
    \App\Http\ViewComposers\App\TermsComposer::class => 'app.terms',
    \App\Http\ViewComposers\App\LawComposer::class => 'app.law',
    \App\Http\ViewComposers\App\HelpComposer::class => 'app.help',
    \App\Http\ViewComposers\Register\Account\IndexComposer::class => 'register.account.index',
    \App\Http\ViewComposers\Register\Defaults\IndexComposer::class => 'register.default',
    // ...以下、画面の分だけ続く
```

これをなんとかしてみましょう。

# `ComposerServiceProvider`自動読み込み版

```php
// ViewComposerディレクトリにあるビューコンポーザをテンプレートと紐づける
// 「Hoge/FooBarComposer」は「hoge/foo_bar.blade.php」と自動的に紐づけられる
$viewComposersPath = __DIR__.'/../Http/ViewComposers';
$allFiles = \File::allFiles($viewComposersPath);
foreach ($allFiles as $file) {
    $pathInfo = collect(explode(DIRECTORY_SEPARATOR, str_replace('Composer.php', '', $file->getRelativePathname())));

    $viewName = $pathInfo->map(function($path){
        return snake_case($path);
    })->implode('.');

    $className = '\\App\\Http\\ViewComposers\\' . $pathInfo->map(function($path){
        return studly_case($path);
    })->implode('\\') . 'Composer';

    //composeメソッドが実装されていて、自動結合の対象外に指定されていなければ結合
    if(method_exists($className, 'compose') &&
        (! method_exists($className, 'isExcludedAutoCompose') || ! $className::isExcludedAutoCompose()))
    {
        \View::composer($viewName, $className);
    }
}
```

これで、`ViewComposer`ディレクトリ以下にあるビューコンポーザクラスが、自動的に登録されるようになります。
（例）`ViewComposers/Hoge/FooBarComposer.php` => `view/hoge/foo_bar.blade.php`

画面が増えても、ビューコンポーザのクラスを所定の名前で作るだけで、
登録の定義を増やす必要はありません。

やっていることはファイルを読み込んでゴリゴリ文字列変換しているだけです。

- `\File::allFiles`でビューコンポーザのファイルリストを取得してループ
- クラス名を取得して、対応するビューファイル名に変形
- `compose()`メソッドが存在し、後述の除外に指定されていなければ`View::composer`で登録

# 手動で登録したい場合

複数のファイルに紐づけるなど、手動で登録したい場合は、
上記処理の後に普通に紐づけ処理を記述すればOKです。

```php
\View::composers([
    \App\Http\ViewComposers\AppComposer::class => '*', // 全ページ共通
]);
```

# 自動登録から除外したい場合

また、自動的に登録してほしくないビューコンポーザもあると思います。
（たまたま他の目的で作られたビューファイルと名前が一緒になってしまった場合など）
その場合は以下の`trait`を`use`することで、対象から除外されるようにしてみました。

```php:App/Http/ViewComposers/ExcludedAutoCompose.php
<?php

namespace App\Http\ViewComposers;

trait ExcludedAutoCompose
{
    public static function isExcludedAutoCompose()
    {
        return true;
    }
}
```

```php:App/Http/ViewComposers/Hoge/JogaiShitaiComposer.php
<?php

namespace App\Http\ViewComposers\Hoge;

use App\Http\ViewComposers\ExcludedAutoCompose;
use Illuminate\Support\Facades\View;;

class JogaiShitaiComposer
{
    // 自動結合から除外する
    use ExcludedAutoCompose;

    public function compose(View $view)
    {
        // ....
    }
}
````

# まとめ

以上、ビューコンポーザを自動で登録する方法でした。
利便性を優先したため、パフォーマンスは考慮していません。
先に結合設定を自動生成してしまうようにすれば少しは早くなるかもしれません。

また、ビューコンポーザの本来の使い方とは違う！　など、指摘ありましたらご教授くださればと思います。