Все заинтересованы в 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). Вы можете установить его разными способами:
- Скачать в виде tar-шара или почтового индекса с GitHub.
- Если вы используете 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
вы передали? Это для некоторой гибкости.
- Метод
match()
не анализирует URI и не использует$_SERVER
внутреннего использования. Это связано с тем, что разные системы могут по-разному представлять эту информацию (например, через объект URI или объект контекста). До тех пор, пока вы можете передавать путь строки и массив серверов, вы можете использовать Aura.Router в своей основе или каркасе приложения. - Иногда URI может выглядеть как
http://example.com/base/path/controller/action/param
, и здесь у нас есть базовый путь. Поэтому, прежде чем искать метод match, нам нужно удалить базовый путь. - В некоторых случаях вы можете получить
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_REFERER
— example.com
мы не сможем загрузить контент. Вы можете передать свои собственные вызываемые функции, как указано выше. Это делает Aura.Router более гибким.
Вывод
В этой статье мы рассмотрели некоторые основные и дополнительные возможности использования Aura.Router для веб-маршрутизации. Почему бы не попробовать Aura.Router? Я уверен, что это сделает вашу жизнь с веб-маршрутизацией проще, чем раньше. Взгляните на http://auraphp.github.com/Aura.Router, так как он может сказать больше, чем в текущей статье.
Изображение через Fotolia