JavaScript で Laravel の route()、dd()、定数取得 のヘルパー関数が使えるようにする

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、私の場合、たまにクライアントの開発者さんと技術についてお話するのですが、その際に複数の方に「たしかに面倒なんですよね…💦」と話したものがあります。

それが・・・・・・??

「JavaScript 内で Laravel のルートを呼び出す」

というものです。

Laravel(PHP)とJavaScriptは別系統ですので、直接お互いから呼び出すことはできません。

そのため、例えばhomeというルート名のURLを取得するには、以下のように、

const url = '{{ route('home') }}';

というカンジでBladeを使って埋め込むようにしています。

なお、上の例はまだシンプルでいいのですが、もしパラメータが必要な場合はめんどうで、私の場合は以下のようにしています。

// replace() で置き換える

const url = '{{ route('item.edit', '*') }}'.replace('*', '1');

これはあまり可読性がよくないので、あまり好きではなかったんですが、仕方ないかなぐらいで考えてました。

そこで❗

今回は、JavaScriptの中でもroute()を擬似的に呼び出せるようにし、さらにせっかくヘルパー関数をつくるので、その他2つの便利関数dd()constant()も一緒に作ってみることにしました。

ぜひ何かの参考になりましたら嬉しいです。

「やっぱり一番カレーが好き 🍛✨」

開発環境: Laravel 9

やりたいこと

JavaScript内で、以下のヘルパー関数が使えるようにします。

  • route() : Laravel のルートを取得できる
  • constant() : Laravel(PHP)の定数を取得できる
  • dd() : console.log() の省略形として使える(※)

※ console.log() と書くのが長ったらしいので、いつも面倒なんです…

前提として

分かりやすく説明するためにどんなデータを使うか考えていたのですが、このあいだ神戸で美味しいカレーを食べたので「カレー 🍛✨」にします。

では、(カレーのにおいを想像しながら)楽しくやっていきましょう❗

コントローラーをつくる

まずはコントローラーです。
以下のコマンドを実行してください。

php artisan make:controller JsHelperController

すると、ファイルが作成されるので中身を以下のようにします。

app/Http/Controllers/JsHelperController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Route;

class JsHelperController extends Controller
{
    public function js()
    {
        $helper_data = [
            'route' => $this->getRouteData(),
            'constants' => $this->getConstantsData(),
        ];
        $last_modified = $this->getLastModifiedData();

        return response()
            ->view('js_helper.js', ['helper_data' => $helper_data])
            ->header('Content-Type', 'application/javascript') // JavaScript として出力
            ->header('Last-Modified', $last_modified); // キャッシュが効くようにします
    }

    private function getRouteData()
    {
        $route_data = [];
        $route_names = config('js_helper.route_names'); // コンフィグからルート名を取得する
        $routes = Route::getRoutes();

        foreach($route_names as $route_name) { // 該当するルートから情報を取得する

            $route = $routes->getByName($route_name);
            $route_data[] = [
                'name' => $route_name,
                'uri' => $route->uri(),
            ];

        }

        return $route_data;
    }

    private function getConstantsData()
    {
        return config('js_helper.constants'); // コンフィグからルート名を取得する
    }

    private function getLastModifiedData() // コンフィグ・ファイルを最後に変更した日時を取得
    {
        $path = config_path('js_helper.php');
        $timestamp = filemtime($path);

        return gmdate('D, d M Y H:i:s', $timestamp) .' GMT';
    }
}

中身は次のとおりです。

js()

ここでは、この後で紹介する2つのメソッドgetRouteData()getConstantsData()からデータを集め、ビューにそのデータを送っています。

なお、今回は「JavaScript ファイルとして」アクセスできるようにしたいので、通常の、

return view('*****');

ではなく、response() でヘッダーの詳細も一緒に返すようにしています。

ちなみに、ヘッダーの中身は以下のとおりです。

  • Content-Type: これは JavaScript として扱ってね、というヘッダー
  • Last-Modified: 同じ内容の「JS 読み込み」を省略できる(つまりキャッシュが効くようにしてね、という)ヘッダー

getRouteData()

この中では後でつくることになる「コンフィグ・ファイル」で指定されたルートに関する情報を取得しています。

※ つまり、コンフィグファイルにルート名を指定しないとroute()URLを取得することはできません。なぜなら、さすがに全ルートを自動的に呼び出せるようにするのはセキュリティ上よろしくないからです。

なお、各データの中身は、次のとおりです。

  • name: ルート名
  • uri: ルートのURI(例 👉 /curry/{curry}/edit )

getConstantsData()

こちらもコンフィグ・ファイルから定数を呼び出しています。

getLastModifiedData()

ここでは、「Last-Modified」ヘッダーに使うための日時を取得するのですが、その日時は、「コンフィグ・ファイルが最後に更新された日時」にしています。

コンフィグ・ファイルをつくる

では、先ほどコントローラーの中で呼び出していた「コンフィグ・ファイル」をつくります。

・・・といっても、ファイルをconfigフォルダ内につくるだけで OK です。

config/js_helper.php

<?php

return [

    // ここに必要な情報を書き込む

];

例えば、カレーを題材にすると次のようになります。

<?php

// 🍛✨ を題材にした例 

return [

    // ルート名
    'route_names' => [
        'curry.index',
        'curry.edit',
        'curry.update',
    ],

    // 定数
    'constants' => [
        'BEST_CURRY_NAME' => env('BEST_CURRY_NAME'), // .env の場合
        'CURRY_SPICES' => \App\Models\Curry::SPICES, // モデルの場合
        'CURRY_TYPES' => \App\Enums\CurryType::cases(), // Enum の場合
    ],
];

ビューをつくる

では、続いてJavaScript本体になるビューをつくっていきます。

resources/views/js_helper/js.blade.php

const helperData = @json($helper_data); // 指定したルートの情報

if(!window.route) {

    window.route = (name, ...parameters) => {

        const targetRoute = helperData.route.find(route => { // name から該当のルートを探す

            return (route.name === name);

        });

        if(targetRoute) { // 該当のルートが存在する場合

            let index = 0;
            const pattern = /\{([\w\-]+)\}/g; // 英数字とアンダースコア、ハイフンが対象
            const baseUri = targetRoute.uri;
            const uri = baseUri.replace(pattern, () => { // URI のパラメーターを置き換える

                const value = parameters[index];
                index++;

                return value;

            });

            return '/'+ uri;

        }

        return '';

    };

}

if(!window.constant) {

    window.constant = (key, defaultValue = null) => {

        return helperData.constants[key] || defaultValue;

    };

}

if(!window.dd) {

    window.dd = (...parameters) => {

        console.log(parameters);

    };

}

※ なお、dd()Laravelのように処理がそこで終了するわけではないですが、タイピングしやすいのでdump()にはしませんでした。m(_ _)m

この中で重要なのが、route()をつくっている部分です。
やっていることは次のとおりです。

  1. 該当するルートを取得(なければそのまま終了)
  2. ルートが存在していれば、URI のパラメータを置き換える
  3. 完成した URL を返す

なお、ここで覚えておいていただきたいのが「…parameters」の部分です。

この...は「うーん」という意味ではなく、この場合は「データを一気に扱うよ👍」という書き方で、「スプレッド構文」と言います。

わかりやすい例で見てみましょう。

function test(...data) {

    console.log(data); // 👈 [1, 2, 3] が入ってる

}

test(1, 2, 3);

関数にパラメータを送って実行する例ですが、スプレッド構文を使うと、一気に配列でそのパラメータを取得することができます。

ちなみに、スプレッド構文は配列やオブジェクトの合体にも使えますので、覚えておくと便利ですよ。

const data1 = [1, 2, 3];
const data2 = [4];
const data3 = [5, 6];

const data = [...data1, ...data2, ...data3]; // 👈 ここで合体
console.log(data); // [1, 2, 3, 4, 5, 6] になる

ルートをつくる

では、最後に「動的 JavaScript」のルートです。

routes/web.php

use App\Http\Controllers\JsHelperController;

// 省略

Route::get('js/helper.js', [JsHelperController::class, 'js']);

これで、「https://******/js/helper.js」にアクセスすることができるようになりました。

テストしてみる

では、実際にテストしてみましょう。
用意したのは、以下のコードです。

resources/views/js_helper_test.blade.php

<html>
    <body>
        <script src="/js/helper.js"></script>
        <script>

            window.onload = () => {

                // ルートの取得
                const url = route('curry.edit', 1);
                console.log(url);

                // 定数の取得
                const bestCurryName = constant('BEST_CURRY_NAME');
                const currySpices = constant('CURRY_SPICES');
                const curryTypes = constant('CURRY_TYPES');
                console.log({
                    bestCurryName,
                    currySpices,
                    curryTypes
                });

                // デバッグ
                const testData = {
                    id: 1,
                    name: 'test'
                };
                dd(testData, 'テスト文字列');

            };

        </script>
    </body>
</html>

実行結果はこうなりました。

成功です😄✨

せっかくなので、パッケージ公開しました

せっかく汎用的に使えそうなコードをつくったのでパッケージ化し、「sukohi/laravel-js-helper」として公開しました。

今のところ、バージョン 9, 8, 7, 6 で使えるはずです。

以下のコマンドでインストールできます。

composer require sukohi/laravel-js-helper

インストールしたら、以下のコマンドを実行することでコンフィグ・ファイルがコピーされます。

php artisan vendor:publish --tag="laravel-js-helper-config"

あとは、コピーされたconfig/js_helper.phpに先ほどご紹介したような「ルート名」や「定数」をセットすれば使えるはずです。

※ 多忙のため、今後ちゃんとメンテナンスするかは不明ですが(ゴメンなさい)、もしご要望が多ければ頑張ります👍

企業様へのご提案

今回の機能を使うと、開発がより効率化できます。

また、今回ご紹介したヘルパー関数だけでなく、「Laravel(PHP)では使えるんだけど、JavaScriptには存在しない」といった関数も独自に提供することができるかと思います。

ぜひそういったご希望がございましたら、ぜひお問い合わせからご連絡ください。お待ちしております。😄✨

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はJavaScriptでヘルパー関数が使えるようにしてみました。

こういったよく使う部分をショートカットできるようにしておけば、「塵も積もれば山となる」方式で「トータルでみたら結構トクしちゃったね」となること間違いなしだと思います。

ぜひ皆さんも独自のヘルパー関数を考えてみてくださいね。

ではでは〜❗


「久しぶりにパッケージ公開して、
オープンソースやってる人たち
への感謝をより感じました」

このエントリーをはてなブックマークに追加       follow us in feedly