Данная статья не описывает стандартные реализации нейминга маршрутов будь то name(), resource() либо apiResource. Информацию по ним Вы можете прочитать в официальной документации здесь, здесь, здесь и здесь.

В статье описывается расширяющий коробочный функционал программный продукт.

Часто разработчики при создании проектов пренебрегают именованием маршрутов по разным причинам, например, когда они вообще не обращаются к ним используя функцию route(). Это обусловлено архитектурой приложения и в этом нет ничего зазорного, но есть и такие проекты, где имена маршрутов являются неотъемлемой частью проекта и случается так, что не ко всем роутам указываются имена. Об этом мы и поговорим.

В таких проектах можно запросто встретить следующий вид имён:

URI

Route Name

GET /api/short/{key}

short_link

POST /api/review

POST /api/phone/confirm

phone.confirm

POST /api/crm/orders

api.crm.

GET /api/crm/orders/{id}

api.crm.

Как мы видим, в коде проекта используется обращение всего к двум маршрутам из данного списка, или не используется - этот факт не доказан, пока мы сами не найдём использование.

Так или иначе, данные имена как минимум вводят в заблуждение об использовании и имеют некрасивый и непрезентабельный вид, а способствует этому простая лень.

Исправить ситуацию поможет пакет dragon-code/laravel-route-names, позволяющий в автоматическом режиме формировать имена маршрутов на основании uri.

Например, вышеприведённые адреса получат следующие имена:

URI

Route Name

GET /api/short/{key}

api.short.show

POST /api/review

api.review.store

POST /api/phone/confirm

api.phone.confirm

POST /api/crm/orders

api.crm.orders.store

GET /api/crm/orders/{id}

api.crm.orders.show

Как мы видим, имена стали осмысленные и автоматически заполненные.

Ещё одной особенностью данного пакета является то, что он совместим со всеми программными решениями по расширению списка маршрутов, например, dragon-code/extended-routes.

Установка и настройка

Основная проблема работы с маршрутами такого уровня во фреймворке Laravel заключается в том, что они инициализируются при старте приложения, и если пытаться заменить корневой Kernel через сервис-провайдеры, то это плохо скажется на производительности, так как сперва будут инициализированы стандартные маршруты, и только после этого будут перезаписаны обработанные. В итоге получим что маршруты инициализируются два раза, чего нам совсем не нужно.

Поэтому мы подменим его на старте приложения, таким образом, сохраним функциональность.

Итак, для начала нужно установить зависимость через Composer:

composer require dragon-code/laravel-route-names

Затем в файле bootstrap/app.php приложения необходимо заменить Illuminate\Foundation\Application на DragonCode\LaravelRouteNames\Application.

И всё ????

Да, это правда всё. Отныне все маршруты приложения будут автоматически формировать имена в красивом, понятном и очевидном виде.

app('router')
    ->name('pages.')
    ->prefix('pages')
    ->group(function () {
        app('router')->get('/', [Controller::class, 'index']);
        app('router')->post('/', [Controller::class, 'store']);
        app('router')->get('{page}', [Controller::class, 'show']);
        app('router')->delete('{page}', [Controller::class, 'destroy']);
    });

До:

GET     /pages         pages.
POST    /pages         pages.
GET     /pages/{page}  pages.
DELETE  /pages/{page}  pages.

После:

GET     /pages         pages.index
POST    /pages         pages.store
GET     /pages/{page}  pages.show
DELETE  /pages/{page}  pages.destroy

Также не забывайте на продакшене использовать кэширование роутов, вызывая команду php artisan route:cache или, ещё лучше, php artisan optimize. Они позволят закэшировать результаты и не прибегать к динамическому формированию не только маршрутов, но и остальных данных в системе. Подробнее про оптимизацию деплоя можно почитать в официальной документации Laravel.

А в скором времени будет поддержка динамических имён в плагине Laravel Idea. Разработка уже ведётся ????

Коллизии

Пакет также решает одну из важных проблем - коллизии при использовании имён маршрутов. Кто не в курсе, коллизии маршрутов - это одинаковые имена роутов при разных конечных URL. То есть, в коде подразумевается обращение к одному URL, а, по факту, будет сформировано совершенно к другому.

Например, в коробочном функционале можно легко достичь коллизии следующими роутами:

app('router')
    ->middleware('api')
    ->prefix('admin')
    ->group(function () {
        app('router')->resource('books', BookController::class);
        // ...
    });

app('router')
    ->middleware('web')
    ->group(function () {
        app('router')->resource('books', BookController::class);
        // ...
    });

Коробочный функционал сгенерирует следующие данные:

GET     /admin/books              books.index
GET     /admin/books/{book}       books.show
GET     /admin/books/{book}/edit  books.edit
DELETE  /admin/books/{book}       books.destroy

GET     /books                    books.index
GET     /books/{book}             books.show
GET     /books/{book}/edit        books.edit
DELETE  /books/{book}             books.destroy

Применяя пакетное решение:

GET     /admin/books              admin.books.index
GET     /admin/books/{book}       admin.books.show
GET     /admin/books/{book}/edit  admin.books.edit
DELETE  /admin/books/{book}       admin.books.destroy

GET     /books                    books.index
GET     /books/{book}             books.show
GET     /books/{book}/edit        books.edit
DELETE  /books/{book}             books.destroy

Да, Вы можете использовать коробочный функционал на ресурсах, например:

app('router')->resource('admin.books', BookController::class);
app('router')->resource('books', BookController::class);

Но в этом случае изначально в архитектуру приложения должно быть заложено использование подобного рода маршрутов без использования "обычных" вроде get(), post() и так далее, иначе в них теряется смысл.

Описанный в посте пакет как раз решает именно эти проблемы.

Комментарии (40)


  1. feycot
    14.03.2022 16:14
    +1

    Можно использовать соглашения об именовании и использовать именованный роутинг всегда.

    Тогда для всех ресурсов будут автоматом создаваться имена для роутов. 99% роутов и вообще функционала в приложении - это обычный круд (или то, что ляжет на круд).

    К сожалению, в Laravel нет всех фишечек из Ruby On Rails. Например в рельсе можно создать одиночный ресурс или root для неймспейса\модуля (условно для / или /admin). Но можно также использовать ресурсный нейминг с одним роутом.

    Не очень понял, что делает библиотека. Почему просто не проименовать все роуты? В чем разница будет между обычным обращением вида foo/bar/baz и boo.bar.baz если имя роута создается из uri?


    1. Helldar Автор
      14.03.2022 17:39

      Тем, что foo/bar/baz - это uri, а foo.bar.baz - имя маршрута по которому определяется URL при обращении через хелпер.


      1. feycot
        14.03.2022 17:48
        +2

        Так а разница? Давай с точки зрения разработчика.

        Почему вообще нужны именованные роуты? Например если изменится uri, то имя роута сохранится. Плюс само имя роута не влияет на uri (и обратно).
        Получается, что никакого преимущество создание имени роута из uri не дает.


        1. Helldar Автор
          14.03.2022 18:04
          -1

          Ну давай с точки зрения архитектуры и разработчиков.

          С точки зрения архитектуры правильно говоришь, имя роута никак не должно жёстко связываться с конечным URI и наоборот.

          А вот с точки зрения разработки названия в большинстве случаев повторяют то, что в URI находится, так как именно такой подход является интуитивно понятным. И точно также при корректировке URI, например, из api/page в api/pages и имена меняются из api.page в api.pages, что никак не противоречит архитектуре, зато сохраняет юзабилити.

          Таким образом, всё же имеем некую привязку URI к неймингу и наоборот.


          1. feycot
            14.03.2022 18:21
            +1

            Ок, если я правильно понял, то плагин просто решает проблему ручного добавления имен к роутам.


            1. Helldar Автор
              14.03.2022 18:29

              Да, правильно.


    1. Helldar Автор
      14.03.2022 17:41

      Почему не проставить имена? Можно и вручную проставлять, но именно данный метод позволит значительно сократить время на внедрение автоматики.

      Для примера, у меня проект на рефакторинге с 1233 маршрутами.


      1. feycot
        14.03.2022 17:49
        +1

        Но ведь если uri поменяется - поменяется и имя роута, верно?


        1. Helldar Автор
          14.03.2022 18:06
          -1

          Да. Точно также, как:

          // before
          app('router')->get('page', [Controller::class, 'index'])->name('page');
          
          // after
          app('router')->get('pages', [Controller::class, 'index'])->name('pages');


          1. feycot
            14.03.2022 18:18
            +1

            Ну вот, получается, что либа нужна для консистентности (использовать всегда хелпер, а не обращаться к роуту напрямую).


            1. Helldar Автор
              14.03.2022 18:45
              -1

              Не совсем. К имени роута обращаться можно, а либа нужна для автоматического формирования этого самого имени.


    1. Helldar Автор
      14.03.2022 17:49
      -1

      Можно использовать соглашения об именовании и использовать именованный роутинг всегда.

      В идеале не просто можно, а нужно. А для облегчения процесса можно прикрутить хелпер к проекту.


  1. ITairoff
    14.03.2022 16:38

    А не проще всегда использовать:

    action([Controller::class, 'methodName']);

    IDE в таком случае позволяет прыгать в эти методы.


    1. Helldar Автор
      14.03.2022 17:39

      Можно, но тогда возникает необходимость всегда использовать конструкцию данного вида. Куда проще и быстрее имя роута прописать нежели вспоминать в какой контроллер он смотрит.


      1. ITairoff
        14.03.2022 17:48

        Мне кажется здесь равносильно помнить имена роутов и названия контроллеров. Да, возможно дело привычки, но все же вариант с action имеет плюс, о котором я писал выше.


        1. Helldar Автор
          14.03.2022 17:51

          Дак и при ctrl+click по имени роута также без проблем прыгается определение роута, откуда уже и в контроллер можно..


          1. ITairoff
            14.03.2022 17:54

            В данном случае вам плагин помогает.


            1. Helldar Автор
              14.03.2022 18:07

              Ну да. Без Laravel Idea на голой IDE сложновато работать. Ни для кого не секрет что Лара содержит магию ¯\_(ツ)_/¯


              1. ITairoff
                14.03.2022 18:11

                Да, но там где можно я предпочитаю ее избегать )


                1. Helldar Автор
                  14.03.2022 18:15
                  -1

                  Ну и зря :)


    1. feycot
      14.03.2022 17:50
      +1

      Laravel Idea позволяет по имени роута ходить в нужные методы.


      1. ITairoff
        14.03.2022 17:53

        Плагин платный. Мой вариант работает нативно, без плагинов для laravel вообще.

        Кстати, вроде даже старый плагин laravel, который бесплатный, для шторма в это умеет.


        1. Helldar Автор
          14.03.2022 18:15

          Статья описывает удобство для разработчиков, а не тип распространения плагинов.

          Как бы, если нет желания платить за мощную помощь, то пользуйтесь бесплатными версиями да не ставьте этот пакет. Никто ж не запрещает, в конце концов ????


          1. ITairoff
            14.03.2022 18:20

            Немного офтоп, но не такая уж и мощная помощь. По крайней мере стоимость как половина самой ide это мне кажется перебор. В целом ничего из функционала этого плагина мне в IDE не нужно. Разве что автокомплит для запросов, но есть бесплатный Laravel Query.


            1. Helldar Автор
              14.03.2022 18:30

              Как знаете :)


  1. Dekmabot
    14.03.2022 20:04
    +1

    Имена для роутов, задаваемые вручную, делаются для того, чтобы при изменении структуры маршрутизации не менять код нигде кроме самого роута.

    Имена дают возможность в коде обращаться к роуту по имени, не зная о его структуре. В результате, если позже структура uri поменяется - имена заданные вручную останутся прежними, менять ничего кроме файла роута не нужно будет.

    В данной библиотеке, если позже api/pages изменится на api/docs - поменяется и автоматическое имя маршрута и придётся менять обращение к маршруту в коде. При использовании вручную прописанных имён маршрутов - этого не потребуется.

    Например:

    # routes/web.php
    Route::get('pages/{page}', [PageController::class, 'view'])
        ->name('view_page');
    
    # PageController.php
    return view('page', [
        'current' => route('view_page', $page),
    ]);

    И теперь, если мы хотим поменять маршрут, код контроллера не изменится.


    1. Helldar Автор
      14.03.2022 20:11
      -2

      Да Вы что?! Вот те на! Ничоси.


  1. Aidar87
    15.03.2022 19:06

    Есть же route resource и нейм из коробки!


    1. Helldar Автор
      15.03.2022 20:29

      use App\Http\Controllers\AuthorController;
      use App\Http\Controllers\AuthorBookController;
      use App\Http\Controllers\BookController;
      
      app('router')->resource('authors', AuthorController::class);
      app('router')->resource('authors/{author}/books', AuthorBookController::class);
      
      app('router')->resource('books', BookController::class);

      Дефолтный нейминг:

      GET|HEAD   authors                             authors.index
      POST       authors                             authors.store
      GET|HEAD   authors/create                      authors.create
      GET|HEAD   authors/{author}                    authors.show
      PUT|PATCH  authors/{author}                    authors.update
      DELETE     authors/{author}                    authors.destroy
      GET|HEAD   authors/{author}/edit               authors.edit
      
      GET|HEAD   authors/{author}/books              books.index
      POST       authors/{author}/books              books.store
      GET|HEAD   authors/{author}/books/create       books.create
      GET|HEAD   authors/{author}/books/{book}       books.show
      PUT|PATCH  authors/{author}/books/{book}       books.update
      DELETE     authors/{author}/books/{book}       books.destroy
      GET|HEAD   authors/{author}/books/{book}/edit  books.edit
      
      GET|HEAD   books                               books.index
      POST       books                               books.store
      GET|HEAD   books/create                        books.create
      GET|HEAD   books/{book}                        books.show
      PUT|PATCH  books/{book}                        books.update
      DELETE     books/{book}                        books.destroy
      GET|HEAD   books/{book}/edit                   books.edit

      Автоматический нейминг:

      GET|HEAD   authors                             authors.index
      POST       authors                             authors.store
      GET|HEAD   authors/create                      authors.create
      GET|HEAD   authors/{author}                    authors.show
      PUT|PATCH  authors/{author}                    authors.update
      DELETE     authors/{author}                    authors.destroy
      GET|HEAD   authors/{author}/edit               authors.edit
      
      GET|HEAD   authors/{author}/books              authors.books.index
      POST       authors/{author}/books              authors.books.store
      GET|HEAD   authors/{author}/books/create       authors.books.create
      GET|HEAD   authors/{author}/books/{book}       authors.books.show
      PUT|PATCH  authors/{author}/books/{book}       authors.books.update
      DELETE     authors/{author}/books/{book}       authors.books.destroy
      GET|HEAD   authors/{author}/books/{book}/edit  authors.books.edit
      
      GET|HEAD   books                               books.index
      POST       books                               books.store
      GET|HEAD   books/create                        books.create
      GET|HEAD   books/{book}                        books.show
      PUT|PATCH  books/{book}                        books.update
      DELETE     books/{book}                        books.destroy
      GET|HEAD   books/{book}/edit                   books.edit


      1. Helldar Автор
        15.03.2022 20:34

        @Aidar87, что Вам мешает использовать дефолтный нейминг с ресурс-контроллерами?


        1. Aidar87
          16.03.2022 05:05

          Ну так я про это и написал


          1. Helldar Автор
            16.03.2022 10:18

            Нет, Вы написали что он есть с претензией на описанный в посте расширяющий его функционал. А мой вопрос был касательно дефолтных: что мешает Вам их использовать?


            1. Aidar87
              16.03.2022 10:29

              Зачем ставить лишний пакет когда есть тот самый автоматический гибкий нейминг из коробки?


              1. Helldar Автор
                16.03.2022 10:31

                Нет, не автоматический. Из коробки имена вручную прописываются, но к вопросу присоединюсь: зачем ставить лишний пакет? Не ставьте его)

                А если без ёрничества, то никто не заставляет его ставить. Пакет есть. Вот он. Это факт. А использовать или не использовать его личное дело каждого.


                1. Aidar87
                  16.03.2022 10:50

                  Почитайте внимательно мануал, видите список имен?


                  1. Helldar Автор
                    16.03.2022 10:53

                    А Вы взамен внимательнее прочтите здесь.

                    Видите коллизию?

                    Лучше с компа откройте ссылку, так весь текст будет правильно структурирован и сразу заметите различия между коробкой и пакетом.


                    1. Aidar87
                      16.03.2022 11:14

                      Я так понял, теперь вы согласны с тем, что Route::resource ставит Route Name.

                      У вас ссылка на свой же коммент. Про какую коллизию идет речь? изначально мой комментарий был, что есть Route::resource и он ставит имя из коробки и если что-то надо кастомизировать, это делается тоже все из коробки, поэтому мой вопрос актуален так как статья называется "Автоматические имена роутов Laravel", а в итоге про Route::resource даже не упоминалось


                      1. Helldar Автор
                        16.03.2022 12:07

                        В данной статье ресурсный роут и не должен упоминаться, так как это есть в официальной документации. В очередной раз говорю, что данное пакетное решение - это улучшение коробочного функционала, а не его добавление, как в комментариях пишут.

                        И да, ссылка на мой комментарий, так как именно в нём наглядно показана коллизия использования коробочных ресурсных роутов, а именно создание абсолютно одинаковых имён маршрутов при разных URL. Данное пакетное решение, в том числе, исправляет данную проблему. О чём тоже в статье написано.


                      1. Aidar87
                        16.03.2022 12:15
                        -1

                        Переименуйте статью


                      1. Helldar Автор
                        16.03.2022 12:38

                        С чего вдруг? Название как раз в полной мере описывает выполняемый пакетом функционал. а коли он не нравится Вам, то не пользуйтесь им и всё. Что Вы от меня-то хотите?