Laravel5 の UrlGenerator の asset をカスタマイズしてみた

# はじめに
laravel で public ディレクトリー中のファイルへの URL を生成する際、
ヘルパー関数の **asset** を用いるかと思います。

その public ディレクトリ中のファイルでよくあるパターンが、
**「CDN や S3 上にファイルを配置するから、そっちを参照したい。」**
ってやつ。

今回、そのパターンに出くわしたので対応をメモ。

# 前提条件
「ヘルパー関数をもう一個作っちゃえばいいじゃん」ってのもあるのですが、
今後開発する人が asset と意識して使い分ける必要があるため、イケてない。

ということで、下記を目標に実装。

 - 極力、Laravel に合わせる。
 - 開発者は asset 関数を使えば、public ディレクトリ中のファイルがどのホストにあるかは意識しなくてもよい。

# 調査
## フェーズ1
まずは、asset の中。

```php:vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
if (! function_exists('asset')) {
    /**
     * Generate an asset path for the application.
     *
     * @param  string  $path
     * @param  bool    $secure
     * @return string
     */
    function asset($path, $secure = null)
    {
        return app('url')->asset($path, $secure);
    }
}
```

なるほど、 **URL** ファサードの asset メソッド呼び出してるだけなのね。
ファサードってなに？って方は[こちら](http://laravel.com/docs/5.1/facades)を参照してください。

なんだ、あとは **Illuminate\Routing\UrlGenerator** を継承したクラスを作成して、
asset カスタマイズして、
そのクラスをサービスコンテナに登録してあげれば、解決だ！

これは楽勝だなと思ってました。そう、この時までは。

## フェーズ2
Illuminate\Routing\UrlGenerator をサービスコンテナに登録している箇所を探してみると、

```php:vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php
    /**
     * Register the URL generator service.
     *
     * @return void
     */
    protected function registerUrlGenerator()
    {
        $this->app['url'] = $this->app->share(function ($app) {
            ・・・

            $url = new UrlGenerator(
                $routes, $app->rebinding(
                    'request', $this->requestRebinder()
                )
            );

            ・・・

            return $url;
        });
    }
```

いました、 **ServiceProviderクラス**。

よしよし。あとは、お決まりのあそこに記載があるはず！

```php:config/app.ph
    'providers' => [
        ・・・
    ],
```

ありゃ、、、いない。
**Service Providers** はここに記載されるんじゃ。。。
ここを自作 ServiceProvider クラスで置き換えればいいという思惑は見事に外れました。


じゃぁ、一体どこに記載があるかというと、

```php:vendor/laravel/framework/src/Illuminate/Foundation/Application.php
    /**
     * Register all of the base service providers.
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));

        $this->register(new RoutingServiceProvider($this));
    }
```

おぃおぃ、そこにベタで記載されたら容易に置き換えられないよ！
困ったな。。。


# 対応
どうするのがベストプラクティスなのか悩んだ結果、
今回はサービスコンテナに登録されたクラスを置き換える方法で対応しました。


UrlGenerator を継承したクラス名を **AppUrlGenerator** とします。

```php:app/Providers/AppServiceProvider.php
    /**
     * Register any application services.
     */
    public function register()
    {
        $url = $this->app['url'];
        $this->app->singleton('url', function () use ($url) {
            return new AppUrlGenerator($url);
        });
    }
```

実際の、AppUrlGenerator クラス。

```app/Http/AppUrlGenerator.php
namespace App\Http;

use Illuminate\Routing\UrlGenerator;

class AppUrlGenerator extends UrlGenerator
{
    /**
     * Create a new manager instance.
     *
     * @param Illuminate\Routing\UrlGenerator $url
     */
    public function __construct(UrlGenerator $url)
    {
        parent::__construct($url->routes, $url->request);
    }

    /**
     * Generate a URL to an application asset.
     *
     * @param string    $path
     * @param bool|null $secure
     *
     * @return string
     */
    public function asset($path, $secure = true)
    {
        // カスタマイズ
        return カスタマイズ結果
    }
```

ちょっと強引な対応な気もしますが、
これで今まで通りの使い方で、カスタマイズした asset 関数が呼び出されるようになります。

```
<img src="{{ asset("hoge.jp/fuga.png") }}">
```

---

今回、サービスコンテナに登録されたクラスを置き換えているため、
**URL::asset** や
**Laravel Collective** の **Html::image** 、 **Form::image** 、 **etc...**
を使用した場合でも、
カスタマイズした asset 関数が使用されるようになっています。

これにより、実装時に public ディレクトリ中のファイルがどのホストにあるかは意識しなくてもよくなり、
また、修正時も AppUrlGenerator の asset 関数を修正するだけでよくなりました。


現在のところ、特に問題は発生していませんが、
対応方法がイケてるとは言い難いですので、
もし問題が発生した場合は、随時情報を更新していきたいと思います。

# 追記（2019/06/21）
Laravel5.7以上であれば、asset関数のホストを.envで設定できるようになったみたいですね。
[こちら](https://readouble.com/laravel/5.7/ja/helpers.html#method-asset)
(別件でのLaravel5.8へのバージョンアップ作業で気づきました。。。)

わざわざカスタマイズしなくても、環境毎に向き先を変えたりしやすくなりましたね！