PHRoute是一个有趣的包:它是一个基于正则表达式的快速路由器,您可以在中小型项目中轻松实现。然而,它不仅仅是非常快:还有过滤器、过滤器组和命名路由。如果事情变得更大,您还可以使用基本的控制器系统。
也就是说,今天我们将看到如何使用它以及如何在示例项目中实现它的功能。此外,我们将了解幕后情况:PHRoute 是不同人进行许多实验和测试的结果。
让我们从安装它开始吧!
安装
您可以在几秒钟内使用 Composer 将 PHRoute 添加到您的项目中。只需将此行添加到您的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 get PDO Instance(){
return new PDO('mysql:host=localhost;dbname=booksapi;charset=utf8', ' root ', '');
}
$router = new Phroute\ Route Collector(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:processOutput和getPDOInstance。我们将使用前两个来确保我们得到正确的输入和正确的输出。第三个将准备必要的 PDO 实例。
注意: 方法的第二个参数array_slice是“3”,因为我个人具体的项目设置。随着您的基本网址更改,请更改它。
之后,我们使用类的$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"}]
伟大的!
添加参数
现在我们可以向前迈出一步:在给定 id 的情况下,添加一个参数以获取单个作者的详细信息怎么样?
像这样的东西:
$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你可以指定任何你想要的参数:没有限制。
有时参数可以是可选的。让我们再举一个例子:如果我们使用booksURL,我们想要检索所有数据库书籍的列表。但是,如果我们指定一个像这样的 id,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我们现在创建的路线!
现在我们要向前迈出一步:是时候“保护”我们的路线了!
过滤器
实际上,每个进入bookPOST 路径的人都可以在我们的收藏中插入一本新书。这很酷,但这与通常情况不同。如果我们想保护我们的路线怎么办?过滤器是我们需要的。
过滤器与路由非常相似:它们有一个名称和一个关联的闭包,在某个地方调用过滤器时执行。
那么,有什么区别呢?可以在路由之前(或之后)轻松调用过滤器。
筛选
让我们举个例子:
$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)。从这一刻起,在每次调用bookpost 路由之前,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,我们将authorurl 链接到Author类。因此,如果我们author在浏览器中输入 URL,该getIndex()方法将被自动调用。该postAdd()方法也是如此,它将绑定到author/add(POST) url。
这个自动解析名称功能很有趣,但实际上还不够。
控制器部分处于开发的早期阶段,需要许多改进。其中之一是可以为控制器方法定义参数。或者,也许,一种为控制器的某些方法定义过滤器的简单方法(而不是“全有或全无”)。
结论
有很多工作要做,尤其是在控制器方面。作为一名开发人员,我认为拥有一个通用的基本控制器类来处理所有脏工作(使用过滤器、方法参数等)会很棒。也缺乏文档。
另一方面,PHRoute 带有一个非常快的路由器。在项目的 GitHub 页面上,你可以看到一些与 Laravel 核心路由器进行比较的统计数据:结果令人惊叹。在最坏的情况下,PHRoute 大约快四十(是的,四十)倍。