PHRoute — это интересный пакет: это быстрый маршрутизатор на основе регулярных выражений, который вы можете легко реализовать в небольших и средних проектах. Однако это не просто очень быстро: есть фильтры, группы фильтров и именованные маршруты. Вы также можете использовать базовую систему контроллеров, если все становится больше.
Тем не менее, сегодня мы увидим, как его использовать и как реализовать его функции в примере проекта. Кроме того, мы посмотрим, что у вас под капотом: PHRoute — это результат многих экспериментов и испытаний, проведенных разными людьми.
Давайте начнем с его установки!
устанавливать
Вы можете добавить PHRoute в свой проект с помощью Composer за считанные секунды. Просто добавьте эту строку в ваш файл composer.json
:
{ "require": { "phroute/phroute": "1.*" } }
Введите команду composer install
и вы в работе. Теперь перейдем к нашему тестовому проекту.
Пример проекта и первый пример
Для лучшего понимания каждой концепции PHRoute рекомендуется создать пример проекта для работы. Сегодня мы собираемся сделать базовый API для службы базы данных книг.
Вот схема базы данных, которую мы собираемся использовать:
Если вы хотите выполнить некоторые тесты, то это дамп схемы SQL, который я использовал (с некоторыми дополнительными фиктивными данными).
CREATE TABLE IF NOT EXISTS authors (id int(10) unsigned NOT NULL AUTO_INCREMENT, name varchar(250) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3; INSERT INTO authors (id, name) VALUES (1, 'Dan Brown'), (2, 'Paulo Coelho'); CREATE TABLE IF NOT EXISTS categories (id int(10) unsigned NOT NULL AUTO_INCREMENT, name varchar(250) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3; INSERT INTO categories (id, name) VALUES (1, 'Thriller'), (2, 'Novel'); CREATE TABLE IF NOT EXISTS books (id int(10) unsigned NOT NULL AUTO_INCREMENT, title varchar(250) NOT NULL, isbn varchar(50) NOT NULL, year int(11) NOT NULL, pages int(11) NOT NULL, author_id int(10) unsigned NOT NULL, category_id int(10) unsigned NOT NULL, PRIMARY KEY (id), KEY author_id (author_id,category_id), KEY category_id (category_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=7; INSERT INTO books (id, title, isbn, year, pages, author_id, category_id) VALUES (1, 'The Zahir', '0-06-083281-9', 2005, 336, 2, 2), (2, 'The Devil and Miss Prym', '0-00-711605-5', 2000, 205, 2, 2), (3, 'The Alchemist', '0-06-250217-4', 1988, 163, 2, 2), (4, 'Inferno', '978-0-385-53785-8', 2013, 480, 1, 1), (5, 'The Da Vinci Code', '0-385-50420-9', 2003, 454, 1, 1), (6, 'Angels & Demons', '0-671-02735-2', 2000, 616, 1, 1);
Мы не собираемся писать что-то действительно сложное. На самом деле, написание некоторых маршрутов для эмуляции запроса API очень простым способом. Если вы хотите написать API реального мира, вам нужно знать много концепций , но сегодня мы просто взглянем на PHRoute.
Прежде чем мы начнем с конкретных маршрутов, давайте проанализируем основную структуру приложения. Это то, что мы собираемся поместить в наш файл index.php
.
<?php require 'vendor/autoload.php'; function processInput($uri){ $uri = implode('/', array_slice( explode('/', $_SERVER['REQUEST_URI']), 3)); return $uri; } function processOutput($response){ echo json_encode($response); } function getPDOInstance(){ return new PDO('mysql:host=localhost;dbname=booksapi;charset=utf8', 'root', ''); } $router = new Phroute\RouteCollector(new Phroute\RouteParser); $router->get('hello', function(){ return 'Hello, PHRoute!'; }); $dispatcher = new Phroute\Dispatcher(router); try { $response = $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], processInput($_SERVER['REQUEST_URI'])); } catch (Phroute\Exception\HttpRouteNotFoundException $e) { var_dump($e); die(); } catch (Phroute\Exception\HttpMethodNotAllowedException $e) { var_dump($e); die(); } processOutput($response);
У нас есть три служебных метода: processInput
, getPDOInstance
и getPDOInstance
. Мы будем использовать первые два, чтобы быть уверенными, что мы получаем правильный ввод и правильный вывод. Третий подготовит необходимый экземпляр PDO.
Примечание: второй параметр метода array_slice
— «3» из-за моей личной настройки конкретного проекта. Измените его по мере изменения вашего базового URL.
После этого мы объявляем наши маршруты, используя объект $router
, экземпляр класса RouteController
. Затем волшебство происходит в методе $dispatcher->dispatch()
, который принимает два параметра: метод запроса $_SERVER
(GET, POST и т. Д.) И конкретный uri запроса. Получив эту информацию, диспетчер вызывает правильный маршрут и выполняет код в замыкании. Возвращаемое значение сохраняется в переменной $response
, которая передается методу processOutput()
который отображает его в виде строки JSON.
Как видите, в этом конкретном примере мы объявили единственный маршрут: hello
.
Примечание: если вы хотите, однако, вы можете улучшить фактическую структуру. Создайте новый файл и назовите его routes.php
. Затем включите его из основного файла index.php
сразу после инициализации объекта $router
: все маршруты будут храниться в отдельном файле. Более элегантное решение, на мой взгляд.
Тем не менее, теперь вы знаете все, что вам нужно об основной структуре нашего примера.
Давайте сделаем наши первые маршруты!
Маршруты
Простой маршрут
Хорошо, давайте посмотрим, что мы можем сделать с маршрутами и сколько мы можем настроить их для наших нужд.
Мы начинаем с самого простого: списка авторов.
$router->get('authors', function(){ $db = getPDOInstance(); $sql = 'SELECT * FROM authors;'; $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $st->execute(); $result = $st->fetchAll(PDO::FETCH_CLASS); return $result; });
В первой строке мы объявляем название нашего маршрута, authors
.
Давайте проверим маршрут: это результат.
[{"id":"1","name":"Dan Brown"},{"id":"2","name":"Paulo Coelho"}]
Большой!
Добавление параметра
Теперь мы можем сделать шаг вперед: как насчет добавления параметра, чтобы получить сведения об одном авторе с учетом идентификатора?
Что-то такое:
$router->get('author/{id}', function($id){ $db = getPDOInstance(); $sql = 'SELECT * FROM `authors` WHERE `id` = :id'; $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $st->execute(array(':id' => $id)); $result = $st->fetchAll(PDO::FETCH_CLASS); return $result; });
Вы можете передать параметр, используя заполнитель {variable_name}
, с тем же выбранным именем, что и у параметра для замыкания. В этом примере у нас есть заполнитель {id}
соответствующий параметру $id
. Вы можете указать любой параметр, который вы хотите: без ограничений.
Иногда параметр может быть необязательным. Давайте создадим другой пример: если мы используем URL-адрес books
мы хотим получить список всех книг базы данных. Но, если мы укажем идентификатор типа books/1
мы получим список книг данной категории.
Вот так:
$router->get('books/{category_id}?', function($category_id = null){ $db = getPDOInstance(); if($category_id == null) { $sql = 'SELECT * FROM `books`;'; $params = array(); } else { $sql = 'SELECT * FROM `books` WHERE `category_id` = :category_id;'; $params = array(':category_id' => $category_id); } $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $st->execute($params); $result = $st->fetchAll(PDO::FETCH_CLASS); return $result; });
Добавление «?» После параметра-заполнителя означает, что он будет необязательным. Конечно, хорошей идеей будет указать значение по умолчанию в объявлении закрытия.
Использование разных глаголов
До сих пор мы создавали только маршруты GET. Как насчет других HTTP-глаголов?
Нет проблем. Посмотрите здесь:
$router->get($route, $handler); // used for GET-only requests $router->post($route, $handler); // used for POST-only requests $router->delete($route, $handler); // used for DELETE-only requests $router->any($route, $handler); // used for all verbs
Давайте сделаем пример POST-маршрута. Пришло время добавить новую книгу в нашу коллекцию!
$router->post('book', function(){ $db = getPDOInstance(); $bookData = $_POST; $sql = 'INSERT INTO table_name (id, title, isbn, year, pages, author_id, category_id) VALUES (NULL, :title, :isbn, :year, :pages, :author_id, :category_id);'; $params = array( ':title' => 'The Winner Stands Alone', ':isbn' => '978-88-452-6279-1', ':year' => 2009, ':pages' => 361, ':author_id' => 2, ':category_id' => 2 ); $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $result = $st->exec($params); if($result) { return $db->lastInsertId(); } else { return false; } });
Давайте представим, что у нас есть форма для заполнения данными книги: ее атрибут action
будет указывать на маршрут book
мы создали прямо сейчас!
Теперь мы собираемся сделать еще один шаг вперед: пришло время «защищать» наши маршруты!
фильтры
На самом деле, каждый, кто входит в book
маршрут POST, может вставить новую книгу в нашу коллекцию. Это круто, но обычно это не так. Что если мы хотим защитить наши маршруты? Фильтры — это то, что нам нужно.
Фильтры очень похожи на маршруты: у них есть имя и соответствующее закрытие, которые выполняются, когда фильтр вызывается где-то.
Так в чем же разница? Фильтр можно легко вызвать до (или после) маршрута.
Фильтр
Давайте сделаем пример:
$router->filter('logged_in', function(){ if(!$_SESSION['user_id']){ header('Location: /login'); return false; } }); $router->post('book', function(){ $db = getPDOInstance(); $bookData = $_POST; $sql = 'INSERT INTO table_name (id, title, isbn, year, pages, author_id, category_id) VALUES (NULL, :title, :isbn, :year, :pages, :author_id, :category_id);'; $params = array( ':title' => 'The Winner Stands Alone', ':isbn' => '978-88-452-6279-1', ':year' => 2009, ':pages' => 361, ':author_id' => 2, ':category_id' => 2 ); $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $result = $st->exec($params); if($result) { return $db->lastInsertId(); } else { return false; } }, array('before' => 'logged_in'));
Прежде всего, мы объявили фильтр с помощью метода filter()
объекта $router
. Синтаксис такой же, как и с маршрутом. Мы даем ему имя и закрытие, которое будет выполнено в нужное время.
Хорошо, но что такое «правильное время»?
Сейчас мы принимаем решение: мы просто добавили третий параметр в метод post()
. Этот третий параметр является массивом, в котором мы указываем ключ before
с именем фильтра ( logged_in
). С этого момента, перед каждым отдельным вызовом к маршруту logged_in
book
, также будет logged_in
фильтр logged_in
(и выполнялось его закрытие).
В этом конкретном случае мы проверяем переменную сеанса user_id
чтобы увидеть, вошел ли пользователь в систему.
Существует также ключ after
который используется для запуска фильтра сразу после вызова маршрута. Вот пример.
$router->filter('clean', function(){ // cleaning code after the route call... }); $router->post('book', function(){ $db = getPDOInstance(); $bookData = $_POST; $sql = 'INSERT INTO table_name (id, title, isbn, year, pages, author_id, category_id) VALUES (NULL, :title, :isbn, :year, :pages, :author_id, :category_id);'; $params = array( ':title' => 'The Winner Stands Alone', ':isbn' => '978-88-452-6279-1', ':year' => 2009, ':pages' => 361, ':author_id' => 2, ':category_id' => 2 ); $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $result = $st->exec($params); if($result) { return $db->lastInsertId(); } else { return false; } }, array('after' => 'clean'));
Если вам нужно, вы также можете указать более одного фильтра одновременно.
Все, что вам нужно сделать, это использовать массив строк вместо одной строки.
$router->filter('filter1', function(){ // filter 1 operations... }); $router->filter('filter2', function(){ // filter 2 operations... }); $router->post('book', function(){ $db = getPDOInstance(); $bookData = $_POST; $sql = 'INSERT INTO table_name (id, title, isbn, year, pages, author_id, category_id) VALUES (NULL, :title, :isbn, :year, :pages, :author_id, :category_id);'; $params = array( ':title' => 'The Winner Stands Alone', ':isbn' => '978-88-452-6279-1', ':year' => 2009, ':pages' => 361, ':author_id' => 2, ':category_id' => 2 ); $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $result = $st->exec($params); if($result) { return $db->lastInsertId(); } else { return false; } }, array('after' => array('filter1', 'filter2')));
Группы фильтров
Давайте представим себе реальный случай: допустим, у нас есть три почтовых маршрута, по одному для каждой сущности (автор, книга, категория). Было бы скучно добавлять фильтр logged_in
три раза.
Не волнуйтесь: группы фильтров здесь, чтобы помочь.
$router->filter('logged_in', function(){ if(!isset($_SESSION['user_id'])) { header('Location: /login'); return false; } }); $router->group(array('before' => 'logged_in'), function($router){ $router->post('book', function(){ // book insert code... }); $router->post('author', function(){ // author insert code... }); $router->post('category', function(){ // category insert code... }); });
С помощью этой единственной группы мы определили один и тот же фильтр для трех разных маршрутов.
Примечание. При необходимости вы также можете вкладывать группы в другие группы столько раз, сколько захотите.
Растущий проект? Время использовать контроллеры!
Наш проект развивается, и организация нашей кодовой базы в одном файле действительно тяжелая и небрежная. Как насчет использования контроллеров?
Да: PHRoute — это не только маршруты. Когда дела идут плохо, самое время их организовать.
Прежде всего, давайте посмотрим, на что похожа структура контроллера. Посмотрите на этот пример (мы можем поместить его в наш файл routes.php
):
<?php class Author { public function getIndex() { // get author list data here... return $result; } public function postAdd() { // add a new author to the database return $insertId; } } $router->controller('author', 'Author');
Мы создали класс Author
. В этом классе мы помещаем два метода: getIndex()
и postAdd()
.
Затем с помощью метода controller()
объекта $router
мы связываем url-адрес author
с классом Author
. Таким образом, если мы введем URL author
в нашем браузере, автоматически будет вызван метод getIndex()
. То же самое касается postAdd()
, который будет привязан к URL author/add
(POST).
Эта функция автоматического разрешения имен довольно интересна, но на самом деле ее недостаточно.
Контроллер находится на ранней стадии разработки и нуждается во многих улучшениях. Одним из них является возможность определения параметров для методов контроллера. Или, может быть, простой способ определить фильтры для некоторых методов контроллера (а не «все или ничего»).
Вывод
Много работы, особенно со стороны контроллеров. Как разработчик, я думаю, было бы замечательно иметь базовый базовый класс контроллера для обработки всей грязной работы (с фильтрами, параметрами методов и т. Д.). Там также не хватает документации.
С другой стороны, PHRoute поставляется с очень быстрым маршрутизатором. На странице проекта GitHub вы можете увидеть некоторые статистические данные о сравнении с основным маршрутизатором Laravel: результаты потрясающие. В худшем случае PHRoute примерно в сорок (да, в 40) раз быстрее.
Если вы хотите узнать конкретные подробности о «движке» этого маршрутизатора, вы можете посетить страницу ников на GitHub, где он объяснил все , с тестами, тестами и связанными результатами.
Собираетесь ли вы попробовать PHRoute? Дай мне знать, что ты думаешь об этом!