Статьи

Веб-маршрутизация в PHP с Aura.Router

Все заинтересованы в SEO-дружественных URL-адресах в стиле REST. Apache может выполнять маршрутизацию URL через правила mod_rewrite, но это сложно и подвержено ошибкам. Почему бы не использовать сам PHP для обработки маршрутизации?

Aura — это независимая коллекция библиотек для PHP 5.4, представленная вам Полом Джонсом . Здесь мы собираемся представить вам Aura.Router. Aura.Router — это простая и легкая библиотека веб-маршрутизации для PHP. В этой статье вы узнаете, как с помощью PHP создавать маршруты, которые оптимизированы для SEO, URL-адреса в стиле REST.

Требования и установка

Мы рассматриваем Aura.Router версии 1, для которой требуется PHP 5.4+ (последняя версия 2 использует PHP 5.3). Вы можете установить его разными способами:

  1. Скачать в виде tar-шара или почтового индекса с GitHub.
  2. Если вы используете git, загрузите его через командную строку:
  git clone https://github.com/auraphp/Aura.Router.git 

Как только вы загрузите Aura.Router, вы увидите каталог с файловой структурой ниже:

Все исходные файлы находятся в каталоге src , и оттуда следует стандарт PSR-0 для автозагрузчиков. Все модульные тесты находятся в каталоге tests ; Вы можете запустить тесты, вызвав phpunit внутри каталога tests (просто убедитесь, что у вас установлен PHPUnit).

Работа с Aura.Router

Минимальный набор правил mod_rewrite должен быть написан в .htaccess чтобы направлять входящие запросы на одну точку входа.

  RewriteEngine On
 RewriteRule ^ $ index.php [QSA]
 RewriteCond% {REQUEST_FILENAME}! -D
 RewriteCond% {REQUEST_FILENAME}! -F
 RewriteRule ^ (. *) $ Index.php / $ 1 [QSA, L] 

Приведенные выше правила проверяют существующие файлы и каталоги и указывают все остальные запросы на index.php .

Или, если вы используете встроенный сервер php, вы можете запустить что-то вроде следующего:

  php -S localhost: 8080 index.php 

Маршрутизатор Aura.Router содержит четыре файла в папке src/Aura/Router : Map.php , Route.php , RouteFactory.php и Exception.php . RouteFactory — это фабричный класс для создания новых объектов Route . RouteFactory содержит метод newInstance() , который принимает ассоциативный массив. Значения передаются в конструктор класса Route .

Объект Route представляет отдельный маршрут с именем, путем, параметрами, значениями и т. Д. Вам никогда не нужно создавать экземпляр Route напрямую; Вы должны использовать RouteFactory или Map вместо этого.

Route и RouteFactory принимают ассоциативный массив со следующими ключами:

  • name — строка, которая является названием для маршрута.
  • path — строка, которая является путем для этого Маршрута с заполнителями токена параметра.
  • params — массив параметров, которые отображают токены в регулярные выражения.
  • values — массив значений по умолчанию для параметров, если они не найдены.
  • method — строка или массив методов HTTP; сервер REQUEST_METHOD должен соответствовать одному из этих значений.
  • secure — должен ли сервер использовать HTTPS-запрос.
  • routable — если true, этот маршрут может быть сопоставлен; если нет, его можно использовать только для генерации пути.
  • is_match — вызываемая функция для оценки маршрута.
  • generate — вызываемая функция для генерации пути.
  • name_prefix — строковый префикс для имени.
  • path_prefix — строковый префикс для пути.

Не беспокойтесь о параметрах метода. Я скоро покажу вам несколько примеров.

Класс Map является точкой сбора маршрутов URI. Конструктор Map принимает RouteFactory и ваши маршруты в одном массиве групп вложений. Это позволяет разделить конфигурацию и построение маршрутов.

Вы можете добавить отдельные маршруты к объекту Map помощью метода add() или присоединить серию маршрутов с помощью метода attach() . Каким бы способом вы ни добавляли маршрут, все спецификации маршрута сохраняются в свойстве definitions объекта Map , которое является массивом.

Объекты Route создаются только при вызове методов generate() и match() . Порядок, в котором создаются объекты, — это порядок, в котором они определены. Он не будет создавать все объекты Route для всех определенных маршрутов. Если имя маршрута совпадает, оно сгенерирует маршрут с помощью метода generate() объекта Route . Метод match() также применяется аналогичным образом. Метод match() внутренне вызывает метод isMatch() объекта Route , чтобы узнать, совпадают ли маршруты. Если были созданы предыдущие маршруты, они будут сначала проходить их в этом порядке. Если он не найден в созданных маршрутах, он создаст объекты Route для остальных маршрутов и выполнит поиск. Класс Map очень прост, поскольку в нем всего 400 строк закомментированного кода. Не стесняйтесь взглянуть на это для получения дополнительной информации.

Основное использование

Самый простой способ создать экземпляр объекта Map — это запрос файла instance.php расположенного в каталоге scripts.

 <?php $map = require "/path/to/Aura.Router/scripts/instance.php"; 

Кроме того, вы можете создать объект вручную, что в любом случае делает скрипт instance.php .

 <?php use AuraRouterMap; use AuraRouterDefinitionFactory; use AuraRouterRouteFactory; $map = new Map(new DefinitionFactory(), new RouteFactory()); 

Затем вы хотите добавить маршруты к объекту, используя метод add ().

 <?php // add a simple named route without params $map->add("home", "/"); // add a simple unnamed route with params $map->add(null, "/{:controller}/{:action}/{:id:(d+)}"); // add a complex named route $map->add("read", "/blog/read/{:id}{:format}", [ "params" => [ "id" => "(d+)", "format" => "(..+)?"], "values" => [ "controller" => "blog", "action" => "read", "format" => "html" ] ]); 

Метод add() принимает имя маршрута, путь и ассоциативный массив. Как я упоминал ранее, значения содержат значения по умолчанию массива params. Таким образом, в примере для маршрута «чтение» вы можете видеть формат по умолчанию всегда «html», если ни один не указан.

Вам интересно, зачем нам нужен формат по умолчанию? Для REST-подобного приложения контроллер и действие будут одинаковыми. Рендеринг данных отличается в принимаемом формате. Таким образом, нам не нужно повторять один и тот же код от одного действия к другому. Например, рассмотрим URI:

  example.com/blog/read/42.html 
 example.com/blog/read/42.json
 example.com/blog/read/42.atom 

Данные, которые нам нужно вывести, одинаковы для каждого, но в разных форматах, таких как json, html и atom. Так что, если ни один из форматов не появляется, например:

  example.com/blog/read/42 

Тогда он будет считать, что запрос для HTML.

Для реального REST API расширения файлов не должны использоваться для указания желаемого формата. Клиентов следует поощрять к использованию HTTP-заголовка запроса Accept. Чтобы узнать больше о REST, вы можете прочитать REST — Можете ли вы сделать больше, чем просто записать? серию здесь, в SitePoint, или книгу REST API Design Rulebook Mark Massé.

Соответствие маршруту

После добавления маршрутов вы захотите узнать, какой маршрут был запрошен пользователем. Это возможно с помощью метода match() объекта Map . Внутренне объект Map вызывает метод isMatch() объекта Route . Для метода match вам нужно передать путь и значение $_SERVER как показано ниже:

 <?php // get the route $path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); $route = $map->match($path, $_SERVER); 

Вы, возможно, задались вопросом, почему мы должны передавать путь, а также значения сервера. Почему Aura.Router не может получить сам путь из массива $_SERVER вы передали? Это для некоторой гибкости.

  1. Метод match() не анализирует URI и не использует $_SERVER внутреннего использования. Это связано с тем, что разные системы могут по-разному представлять эту информацию (например, через объект URI или объект контекста). До тех пор, пока вы можете передавать путь строки и массив серверов, вы можете использовать Aura.Router в своей основе или каркасе приложения.
  2. Иногда URI может выглядеть как http://example.com/base/path/controller/action/param , и здесь у нас есть базовый путь. Поэтому, прежде чем искать метод match, нам нужно удалить базовый путь.
  3. В некоторых случаях вы можете получить PATH_INFO от REDIRECT_PATH_INFO когда ваш сервер может понимать только /index.php/home/index , а не /home/index .

Если совпадение найдено, метод возвращает экземпляр объекта Route , в противном случае он возвращает false. Получив объект, вы можете получить доступ к его значениям как таковым:

 <?php $route->values["controller"]; $route->values["action"]; $route->values["id"]; 

Из $route->values мы знаем, какой тип рендеринга, какой класс, какой метод мы хотим вызвать для диспетчеризации.

Отправка маршрута

Если маршрут найден, вам легко создать объект контроллера и соответствующий метод. Этот простой пример адаптирован из документов Aura:

 <?php if (!$route) { // no route object was returned, You can also set error controller depending on your logic echo "No application route was found for that URI path."; exit(); } // does the route indicate a controller? if (isset($route->values["controller"])) { // take the controller class directly from the route $controller = $route->values["controller"]; } else { // use a default controller $controller = "Index"; } // does the route indicate an action? if (isset($route->values["action"])) { // take the action method directly from the route $action = $route->values["action"]; } else { // use a default action $action = "index"; } // instantiate the controller class $page = new $controller(); // invoke the action method with the route values echo $page->$action($route->values); 

Micro-Framework Routing

Иногда вы можете использовать Aura в качестве микро-фреймворка Также возможно назначить анонимную функцию контроллеру:

 <?php $map->add("read", "/blog/read/{:id}{:format}", [ "params" => [ "id" => "(d+)", "format" => "(..+)?", ], "values" => [ "controller" => function ($args) { $id = (int) $args["id"]; return "Reading blog ID {$id}"; }, "format" => ".html", ], )); 

Когда вы используете Aura.Router в качестве микро-фреймворка, диспетчер будет выглядеть примерно так, как показано ниже:

 <?php $params = $route->values; $controller = $params["controller"]; unset($params["controller"]); echo $controller($params); с <?php $params = $route->values; $controller = $params["controller"]; unset($params["controller"]); echo $controller($params); 

Генерация маршрута

Вы можете создать маршрут по вашему мнению. Это возможно с помощью метода generate() карты. Метод generate() карты внутренне вызывает метод generate() объекта Route .

 <?php // $path => "/blog/read/42.atom" $path = $map->generate("read", [ "id" => 42, "format" => ".atom" ]); $href = htmlspecialchars($path, ENT_QUOTES, "UTF-8"); echo '<a href="' . $href . '">Atom feed for this blog entry</a>'; 

Конечно, ввод «blog / read / 42.atom» короче, но жестко закодированные пути менее гибки и затрудняют изменение маршрута. Рассмотрим следующий пример:

 <?php $map->add("read", "/blog/read/{:id}{:format}", [ "params" => [ "id" => "(d+)", "format" => "(..+)?"], "values" => [ "controller" => "blog", "action" => "read", "format" => "html" ] ]); 

Что произойдет, если вы захотите изменить /blog/read/42.atom на /article/view/42.atom ? Или что произойдет, когда ваш клиент сообщит, что хочет перейти на многоязычный веб-сайт? Если у вас есть жестко заданные пути, вам, вероятно, придется их менять во многих местах. Метод generate() всегда полезен.

Второй аргумент метода add() является ассоциированным массивом. Вы можете передавать вызываемые функции с помощью ключей is_match, которые могут возвращать true или false. И в зависимости от значения он будет возвращать маршруты при вызове метода match() . Например:

 <?php $map->add("read", "/blog/read/{:id}{:format}", [ "params" => [ "id" => "(d+)", "format" => "(..+)?", ], "values" => [ "controller" => "blog", "action" => "read", "id" => 1, "format" => ".html", ], "secure" => false, "method" => ["GET"], "routable" => true, "is_match" => function(array $server, ArrayObject $matches) { // disallow matching if referred from example.com if ($server["HTTP_REFERER"] == "http://example.com") { return false; } // add the referer from $server to the match values $matches["referer"] = $server["HTTP_REFERER"]; return true; }, "generate" => function(AuraRouterRoute $route, array $data) { $data["foo"] = "bar"; return $data; } ]); 

Здесь, если HTTP_REFERERexample.com мы не сможем загрузить контент. Вы можете передать свои собственные вызываемые функции, как указано выше. Это делает Aura.Router более гибким.

Вывод

В этой статье мы рассмотрели некоторые основные и дополнительные возможности использования Aura.Router для веб-маршрутизации. Почему бы не попробовать Aura.Router? Я уверен, что это сделает вашу жизнь с веб-маршрутизацией проще, чем раньше. Взгляните на http://auraphp.github.com/Aura.Router, так как он может сказать больше, чем в текущей статье.

Изображение через Fotolia