Быстрый роутинг на PHP

image
Уходя от использования роутинга в .htaccess файле, в первую очередь пришёл к стандартному направлению на index.php: разбирал там URL и вызывал соответствующие контроллеры — долгое время был доволен такой техникой. Однако совсем недавно осознал, что что-то делаю не так, что можно сделать эффективнее и лучше.
Далее я расскажу о своём роутинге, использующем XML для хранения правил и в последующем использующем его сериализованный вид.

Уходя от проверки в индексном файле, хотел в первую очередь избавиться от подобной конструкции
if ($url[1] == 'news'){}


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


$route->addRule(
        '/news/{id}/',
        array(
                'controler' = 'news',
                'action'    = 'showOne'
                )
);


Изначально хотелось хранить правила роутинга в JSON или XML.
Однако парсить каждый раз файл не очень хорошая идея, и такой тип более пригоден для статической навигации или навигации вида /controller/action/.
Мне же хотелось большей гибкости в настройке роутинга и в конечном итоге решил использовать XML для хранения правил, а после парсинга файла и создания массива правил сериализовывать его в файл (в дальнейшем используя его для получения настроек)
XML-файл правил роутинга выглядит примерно так:

<root>
	<routes>
		<route match="exit" controller="user" action="exit" />
		<route match="secret" controller="error" action="404">
			<route match="love" controller="secret" action="love" />
		</route>
		<route match="user" controller="user" action="list">
			<route controller="user" action="user" match="{login}">
				<route match="comments" controller="user" action="comments" />
				<route match="wall" controller="user" action="wall" />
			</route>
		</route>
	</routes>
</root>


Структура правила представляет собой следующее
XML-элемент правил содержится в элементе /root/routes, элемент правил должен содержать в себе следующие атрибуты:
match — Используется для поиска по URL
controller — Вызываемый контроллер
action — Вызываемый метод

match может содержать как статические данные, например «secret», так и динамические «page-{page|num}», динамические отличаются от статических наличием фигурных скобок и названием переменной в ней (название переменной и её значение будут получены в случае совпадения)
В переменной можно указать её тип:
{param1|num} — выдаст совпадение только в случае, если param1 является числовым значением
{param2|str} — выдаст совпадение только в случае, если param2 содержит в себе только буквы и цифры

На основе XML формируется массив, который разделает статические и динамические правила.
Все потомки так же разделяются на статических и динамических.
Так же в элементе /root/system
хранятся следующие данные:
	<route match="index" controller="index" action="index" />
	<route match="not_found" controller="error" action="404" />

Соответственно в случае, если совпадения по правилам роутинга найдены не будут, вернётся 404 ошибка, в случае пустого урл — его index значение

Использование выглядит так:


$result = $router->get('/secret/love/');

Результат будет таким:
Array
(
    [controller] => secret
    [action] => love
    [values] => Array
        (
        )

)



$result = $router->get('/user/Testik/wall/');


Array
(
    [controller] => user
    [action] => wall
    [values] => Array
        (
            [login] => Testik
        )

)


Исходники на BitBucket
Скачать zip gz bz2
Поделиться публикацией

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 48

  • НЛО прилетело и опубликовало эту надпись здесь
      0
      В решениях от фреймворках массив правил создаётся динамически, в моём решении массив правил создаётся лишь 1 раз при парсинге, после чего используется сериализованные данные.
        +5
        Вроде везде можно закешировать ;)
          0
          В моё решение тоже несложно будет встроить кэш, что не уступит в производительности решений от фреймворков.
          В первую очередь писал данный класс, для варианта без использования кэша памяти.
            0
            На сколько я вижу, у вас и так используется кеш — файл с сериализованными данными. Да и не важно где его хранить — в файле или памяти, с точки зрения бизнес логики. Хоть делать var_export в файл. Вот мы и говорим, что закешировать вроде то все можно (помимо Вашего примера).

            Не кажется ли Вам, что на производительность роутера нужно смотреть не в самом начале? А если и смотрите, то нужен ли он вообще? Считаю, что сам контроллер будет требовать гораздо больше ресурсов, чем роутер. Так что роутер было бы хорошо написать что бы было быстро, понятно и приятно его использовать.
              0
              Разумеется контроллер отъест побольше памяти и будет выполняться дольше, никто не спорит.
              >Так что роутер было бы хорошо написать что бы было быстро, понятно и приятно его использовать.
              Я вижу для себя эти плюсы, если вы видите минусы, я не заставляю пересаживаться на него.
          0
          в symfony2 нет.
        +3
        А в чем преимущество? Как по мне, так удобнее и читабельнее править именно что-то вида:
        $route->addRule(
        '/news/{id}/',
        array(
        'controler' = 'news',
        'action' = 'showOne'
        )
        );
          +4
          Простите, повздорил с парсером:
          $route->addRule(
                  '/news/{id}/',
                  array(
                          'controler' = 'news',
                          'action'    = 'showOne'
                          )
          );
            0
            Именно на такой вариант я хотел перейти изначально, мне же он не подошёл по следующим причинам:
            1. Хотелось хранить правила роутинга в 1 файле и чтобы вид был стандартизирован, лично по-моим критерями php-код выглядит для настроек неудачно
            2. Людям которые будут работать с кодом в дальнейшем (в том числе не программистам), будет проще просмотреть файл конфигов в xml
            3. Гораздо лучше видно дерево правил, пример:
            <route match="user" controller="user" action="list">
            	<route controller="user" action="user" match="id{user_id|num}">
            		<route match="comments" controller="user" action="comments" />
            		<route match="wall" controller="user" action="wall" />
            	</route>
            	<route controller="user" action="user" match="{login}">
            		<route match="comments" controller="user" action="comments" />
            		<route match="wall" controller="user" action="wall" />
            	</route>
            </route>
            
              +3
              Не программистам может потребоваться влезать в настройки правил роутинга? :)
                0
                Вы рассматриваете крупные компании, иногда сайт делаю всего пару человек и не всегда оба — программисты.
                  +8
                  Нечего не программистам делать сайты. Непонятно каким образм не-программисты будут лучше читать XML, нежели правила прописанные в исходнике.

                  > 1. Хотелось хранить правила роутинга в 1 файле и чтобы вид был стандартизирован
                  Вы можете хранить правила роутинга в одном php-файле, а без стандартизации не обойдется в принципе.
                  > 2. Людям которые будут работать с кодом в дальнейшем (в том числе не программистам), будет проще просмотреть файл конфигов в xml
                  Ваш XML-конфиг в 3 раза более нагроможденный, чем правила роутинга в PHP, не думаю, что не-программистам в нем легче будет разобраться.
                  > 3. Гораздо лучше видно дерево правил
                  В XML видно дерево правил? Кому? Операторам матрицы? Уверяю вас, человек не-программист (а вы под этих людей затачиваете свой файл-конфиг роутинга), никакого дерева там не видит.

                  Если вы ищите решение для не-программистов, сделайте админку с удобным редактированием правил роутинга.
                +4
                Не убедили, вижу только лишние заморочки. Хотя это дело вкуса — кому xml, кому массивы)
                  –1
                  Хотелось хранить правила роутинга в 1 файле и чтобы вид был стандартизирован

                  Вы наверно шутите? Храниние всех ройтингов в одном файле приведет к нечитаемому монолиту.

                  Мне же хотелось большей гибкости в настройке роутинга и в конечном итоге решил использовать XML для хранения правил, а после парсинга файла и создания массива правил сериализовывать его в файл (в дальнейшем используя его для получения настроек)


                  Подскажите, в дальнейшем — значит на протяжении одного запроса, либо это кэш для последующих запросов? Если это кэш, то как вы его обновляйте?

                  Гораздо лучше видно дерево правил

                  Объясните про дерево правил, не очень понял.
                    0
                    >Объясните про дерево правил, не очень понял.
                    <page>
                         <subpage>
                    </page>
                    

                      0
                      Так это роутинг или навигация? Зачем дерево?
                        0
                        Это роутинг,
                        а дерево затем, что subpage может вызывать совершенно другой контроллер, нежели page/
                          0
                          Соответственно в случае, если совпадения по правилам роутинга найдены не будут, вернётся 404 ошибка, в случае пустого урл — его index значение


                          Я правильно понимаю, что дерево нужно ТОЛЬКО для этого?
                            0
                            нет, боюсь вы не видите плюсов деревьев.
                              0
                              Объясните пожалуйста
                                0
                                На основе примера:
                                <root>
                                	<routes>
                                		<route match="user" controller="user" action="list">
                                			<route controller="user" action="user" match="{login}">
                                				<route match="comments" controller="user" action="comments" />
                                				<route match="wall" controller="user" action="wall" />
                                			</route>
                                		</route>
                                	</routes>
                                </root>
                                


                                Если перейти по урлу /user/ — отобразится список пользователей
                                /user/testik/ — отобразится информация об этом пользователе
                                /user/testik/comments/ — отобразятся его комментарии.

                                А в XML видно, кто чьим потомком является , лично мне удобно использовать этот плюс, но я никого не заставляю переходить на это решение.
                                  0
                                  Ясно вы вкладывайте роуты друго в друга, тем самым сокращая запись правила матчинга.

                                  Ответьте, пожалуйста, также на 2 вопроса из моего первого комментария
                                    0
                                    интересует как будет выглядеть правило в указанном выше примере, если у меня в контроллере юзера добавляется экшен profile котороый должен отобразить инфу залогиненого пользователя и следовательно адрес получается вида

                                    /user/profile
                        0
                        >Вы наверно шутите? Храниние всех ройтингов в одном файле приведет к нечитаемому монолиту.
                        Если хранить роутинг в разных страницах, при больших объёмах можно просто забыть где и что лежит, свой вариант считаю вполне удобным.

                        >Подскажите, в дальнейшем — значит на протяжении одного запроса, либо это кэш для последующих запросов? Если это кэш, то как вы его обновляйте?

                        В дальнейшем, при последующих запросах разумеется, обновляется путём удаление файла *.dat
                        Роутинг обновляется очень редко, не хотел каждый раз проверять время изменения файла.
                          0
                          Если хранить роутинг в разных страницах, при больших объёмах можно просто забыть где и что лежит, свой вариант считаю вполне удобным.

                          Когда кол-во роутов возрастет до 500, вам по-прежнему будет удобно?

                          В дальнейшем, при последующих запросах разумеется, обновляется путём удаление файла *.dat
                          Роутинг обновляется очень редко, не хотел каждый раз проверять время изменения файла.

                          Вы моежете быть уверены, что ни один программист из команды, работающий над проектом никогда не забудет обновить файл *.dat?
                            0
                            >Когда кол-во роутов возрастет до 500, вам по-прежнему будет удобно?
                            Да.
                            >Вы моежете быть уверены, что ни один программист из команды, работающий над проектом никогда не забудет обновить файл *.dat?
                            Тоже да.
                        0
                        В тему «не программистам будет проще» — ооочень спорно.
                        Одно время работал как раз в такой ситуации, и моим «не программистам» XML было очень сложно понять (по сравнению, с тем же json и даже рнр).
                    +1
                    Еще один, достаточно типичный, зато родной, роутинг на PHP с использованием XML как хранилища и сериализованного массива в файле в качестве «кэша».
                    Не в обиду, но я не нашел ничего выдающегося.
                    Кстати, есть предположение, что буферизация в .php файле через var_export() окажется более по вкусу оп-кэшеру.
                      0
                      >Не в обиду, но я не нашел ничего выдающегося.
                      «Дьявол в мелочах»
                      >Кстати, есть предположение, что буферизация в .php файле через var_export() окажется более по вкусу оп-кэшеру
                      Есть такая вероятность, чуть позже проверю
                        0
                        Тоже так подумал, и да, можно задавать роутинг вообще из пхп-массива и не придется ничего парсить, ну и во всех вменямых фреймворках сей файл все равно кешится в пхп-массив, да и более интересгая частт это матч роутинга, а не то, в каком виде его хранить.
                          0
                          Вы можете изучить исходники, если вам интересна match часть роутинга.
                            +1
                            Навеяло: «учите match-часть» :)
                        0
                        В Symfony 2 то же самое, или я что-то путаю?
                          +5
                          «Изобрел» роутинг, напиши об это на хабре.
                          По итогу ничего нового, нет никаких преимуществ вашего роутинга перед другими. Да и кеширование довольно странное, наряду с использованием xml.
                            0
                            Быстрый? Вы бы тогда сравнили скорость работы вашего варианта и хотя бы $route->addRule(), чтобы не о сферической быстроте говорить. Мне кажется конкретные результаты показали бы, что отличия в скорости не очень существенны.

                            Можно ещё понять танцы с xml, если по какой-то причине у вас все конфиги в xml, в противном случае просто непонятно зачем это нужно.
                              0
                              Это решение будет быстрее, потому как все данные будут уже закэшированы, $route->addRule() не может себе такого позволить.
                                0
                                Вы внимательно читали?

                                > Мне кажется конкретные результаты показали бы, что отличия в скорости не очень существенны.

                                Реальных цифр у вас нет, читать же про «будет» быстрее не очень интересно.
                              0
                              TROODON вариант хранения маршрутов в БД не рассматривался? А что если, например, необходимо будет разграничить права доступа пользователей к ресурсу в плоскости маршрутов (страниц с точки зрения пользователя)? Мне кажется, такие связи будет проще реализовать если хранить все в БД, или я ошибаюсь?
                                +1
                                Разграничением прав должен заниматься контроллер, имхо.
                                  0
                                  С одной стороны да, с другой стороны — какой именно контроллер должен заниматься разграничением прав? :)

                                  Если разграничения по страницам (маршрутам) достаточно, то на мой взгляд оптимальным будет разместить всю систему в одном контроллере, ослабив тем самым связи с другими контроллерами. В таком варианте это имеет непосредственное отношение к роутингу.
                                –1
                                В случае ini-файлов (формата .htaccess — LIKE) роутер может выглядеть так:

                                /users/ users#index
                                ; роутинг сразу на класс, метод определится из url
                                /users/ users#
                                /pages/ pages#index
                                /pages/about pages#about
                                /users/:id/comments commets#list_by_user
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                    +1
                                    А чем зендовский не устроил? Там и различные форматы (php, ini, xml, yaml), и свою стратегию добавить можно, и кеширование…
                                      –1
                                      Всегда было интересно: чем не устраивает роутинг средствами .htaccess на dev, и nginx на продакшене?
                                        0
                                        Как со стороны php тогда строить ссылки по роутингам?
                                        // nginx тоже на dev можно использовать чтобы не дублировать rewrite в RewriteRule
                                        0
                                        Один файл это очень хорошо, но что будет в ситуации если этот, с вашего позволения, движок надо будет скопировать другому клиенту. А потом появится новый модуль в сайте третьего клиента и будет идея всем десяти его добавить? Роутинг для одного модуля прописывать всем? Не с проста спрашиваю, сталкивался.

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

                                        Самое читаемое