Laravelのurl()asset()関数は大変便利なツールで、アプリ内の遷移やファイルのパス指定に欠かせない存在。

キャッシュ対策で画像やcssやjsファイルになどバージョンをコントロールしたり、デフォルトのルートパラメータを指定したりするには、デフォルトのUrlGenerator(aliasはurl)というClassを継承しないといけない。

通常、app()->singleton('url', \App\My\UrlGenerator::class);で自前のUrlGeneratorで既存のコンポーネントを上書きできるが、urlに絡まるコンポーネントが異常に多いので、そう簡単にはいけなかった。

調べたところ、フレームワークは次のようにurlコンポーネントを登録している:https://github.com/laravel/framework/blob/1003c27b73f11472c1ebdb9238b839aefddfb048/src/Illuminate/Routing/RoutingServiceProvider.php#L49

それを参考してApp\Providers\AppServiceProvider::bootに自前の\App\My\UrlGeneratorを登録:

$this->app->singleton('url', function ($app) {
    $routes = $app['router']->getRoutes();
    $app->instance('routes', $routes);

    // 自前のClass
    $url = new \App\My\UrlGenerator(
        $routes, 
        $app->rebinding('request', function ($app, $request) {
            $app['url']->setRequest($request);
        }), 
        $app['config']['app.asset_url']
    );

    $url->setSessionResolver(function () {
        return $this->app['session'];
    });
    $url->setKeyResolver(function () {
        return $this->app->make('config')->get('app.key');
    });
    $app->rebinding('routes', function ($app, $routes) {
        $app['url']->setRoutes($routes);
    });
    return $url;
});

\App\My\UrlGeneratorはこんな感じ:

<?php

namespace App\My;

class UrlGenerator extends \Illuminate\Routing\UrlGenerator
{
    /**
     * @param string $path
     * @param null $secure
     * @return string
     */
    public function asset($path, $secure = null)
    {
        $path_parts = pathinfo($path);
        if (!in_array($path_parts['extension'], ['js', 'css'])) {
            return parent::asset($path);
        }
        $v = config('app.version', 0);
        if (config('app.env') != 'production') {
            // ランダム
            $v = md5(microtime(uniqid()));
        }
        $path .= '?v=' . $v;
        return parent::asset($path, $secure);
    }
}

そうすると、asset('/js/app.js')すれば、勝手に?v=(...)が付くようになる。

もちろん、開発者次第でいろんなカスタマイズができる。参考まで!