corneltek / pux
快速的PHP路由器
Requires
- corneltek/cliframework: ^2
- corneltek/codegen: ^2.1
Requires (Dev)
- corneltek/phpunit-testmore: dev-master
- corneltek/universal: dev-master
README
Pux是一个更快的PHP路由器,它还包含开箱即用的控制器助手。
2.0.x 分支构建状态 (此分支正在开发中)
基准测试
功能
- 低内存占用(仅6KB,带有简单路由和扩展)。
- 低开销。
- 支持PCRE模式路径。 (Sinatra样式语法)
- 控制器自动挂载 - 您可以自动挂载控制器而无需为每个动作指定路径。
- 支持控制器注解 - 您可以通过注解覆盖控制器中的默认路径。
- 带有可选模式的路由。
- 请求约束
- 支持请求方法条件。
- 支持域名条件。
- 支持HTTPS条件。
需求
- PHP 5.4+
安装
composer require corneltek/pux "2.0.x-dev"
概要
路由使用非常简单
require 'vendor/autoload.php'; // use PCRE patterns you need Pux\PatternCompiler class. use Pux\RouteExecutor; class ProductController { public function listAction() { return 'product list'; } public function itemAction($id) { return "product $id"; } } $mux = new Pux\Mux; $mux->any('/product', ['ProductController','listAction']); $mux->get('/product/:id', ['ProductController','itemAction'] , [ 'require' => [ 'id' => '\d+', ], 'default' => [ 'id' => '1', ] ]); $mux->post('/product/:id', ['ProductController','updateAction'] , [ 'require' => [ 'id' => '\d+', ], 'default' => [ 'id' => '1', ] ]); $mux->delete('/product/:id', ['ProductController','deleteAction'] , [ 'require' => [ 'id' => '\d+', ], 'default' => [ 'id' => '1', ] ]); // If you use ExpandableController, it will automatically expands your controller actions into a sub-mux $mux->mount('/page', new PageController); $submux = new Pux\Mux; $submux->any('/bar'); $mux->mount('/foo',$submux); // mount as /foo/bar // RESTful Mux Builder $builder = new RESTfulMuxBuilder($mux, [ 'prefix' => '/=' ]); $builder->addResource('product', new ProductResourceController); // expand RESTful resource point at /=/product $mux = $builder->build(); if ($route = $mux->dispatch('/product/1')) { $response = RouteExecutor::execute($route); $responder = new Pux\Responder\SAPIResponder(); // $responder->respond([ 200, [ 'Content-Type: text/plain' ], 'Hello World' ]); $responder->respond($response); }
Mux
Mux是您定义路由的地方,您可以将多个Mux挂载到父Mux上。
$mainMux = new Mux; $pageMux = new Mux; $pageMux->any('/page1', [ 'PageController', 'page1' ]); $pageMux->any('/page2', [ 'PageController', 'page2' ]); // short-hand syntax $pageMux->any('/page2', 'PageController:page2' ); $mainMux->mount('/sub', $pageMux); foreach( ['/sub/page1', '/sub/page2'] as $p ) { $route = $mainMux->dispatch($p); // The $route contains [ pcre (boolean), path (string), callback (callable), options (array) ] list($pcre, $path, $callback, $options) = $route; }
方法
Mux->add( {path}, {callback array or callable object}, { route options })
Mux->post( {path}, {callback array or callable object}, { route options })
Mux->get( {path}, {callback array or callable object}, { route options })
Mux->put( {path}, {callback array or callable object}, { route options })
Mux->any( {path}, {callback array or callable object}, { route options })
Mux->delete( {path}, {callback array or callable object}, { route options })
Mux->mount( {path}, {mux object}, { route options })
Mux->length()
返回路由长度。Mux->export()
通过PHP代码中的 __set_state 静态方法返回 Mux 构造函数。Mux->dispatch({path})
分发路径并返回匹配的路由。Mux->getRoutes()
返回路由数组。Mux::__set_state({object member array})
构建并返回一个 Mux 对象。
排序路由
当不使用编译路由时,需要排序路由,因为pux将较长的路径放在前面
$pageMux = new Mux; $pageMux->add('/', [ 'PageController', 'page1' ]); $pageMux->add('/pa', [ 'PageController', 'page1' ]); $pageMux->add('/page', [ 'PageController', 'page1' ]); $pageMux->sort();
这将按以下顺序排序路由
/page
/pa
/
因此,pux首先比较 /page
,然后是 /pa
,最后是 /
。
不同的字符串比较策略
当启用扩展时,字符串模式比较策略将匹配整个字符串。
当禁用扩展时,字符串模式比较策略将匹配前缀。
RouteRequest
RouteRequest维护当前请求环境的信息,它还提供一些约束检查方法,帮助您识别请求,例如。
if ($request->queryStringMatch(...)) { } if ($request->hostEqual('some.dev')) { } if ($request->pathEqual('/foo/bar')) { }
use Pux\Environment; $env = Environment::createFromGlobals(); $request = RouteRequest::createFromEnv($env); if ($route = $mux->dispatchRequest($request)) { }
APCDispatcher
尽管Pux\Mux已经很快,但您仍然可以添加APCDispatcher来提高性能,即避免重新查找路由。
当您有很多PCRE路由时,这非常有用。
use Pux\Dispatcher\APCDispatcher;
$dispatcher = new APCDispatcher($mux, array(
'namespace' => 'app_',
'expiry' => ...,
));
$route = $dispatcher->dispatch('/request/uri');
var_dump($route);
控制器
Pux提供了将您的控制器方法映射到路径的自动映射能力,这是通过C扩展中的简单、快速的控制器或其纯PHP对应版本实现的。
class ProductController extends \Pux\Controller { // translate to path "" public function indexAction() { } // translate to path "/add" public function addAction() { } // translate to path "/del" public function delAction() { } } $mux = new Pux\Mux; $submux = $controller->expand(); $mux->mount( '/product' , $submux ); // or even simpler $mux->mount( '/product' , $controller); $mux->dispatch('/product'); // ProductController->indexAction $mux->dispatch('/product/add'); // ProductController->addAction $mux->dispatch('/product/del'); // ProductController->delAction
您还可以使用 @Route
和 @Method
注解来覆盖默认的 \Pux\Controller::expand()
功能。
class ProductController extends \Pux\Controller { /** * @Route("/all") * @Method("GET") */ public function indexAction() { // now available via GET /all only } /** * @Route("/create") * @Method("POST") */ public function addAction() { // now available via POST /create only } /** * @Route("/destroy") * @Method("DELETE") */ public function delAction() { // now available via DELETE /destroy only } }
当您想提供更具体或语义化的(例如,特定 HTTP 方法的)操作时,这特别有用。请注意,默认情况下,展开的控制器路由将通过任何 HTTP 方法可用 - 指定 @Method
将将其限制为提供的方法。
Pux\Controller::expand()
返回一个包含控制器方法到 URI 映射的\Pux\Mux
实例,旨在作为另一个\Pux\Mux
实例的子多路复用器挂载。
路由执行器
Pux\RouteExecutor
通过创建控制器对象并调用控制器操作方法来执行您的路由。
路由执行器接受返回的路由作为其参数,您只需将路由传递给执行器,然后获取执行结果即可。
这里是一个使用示例
use Pux\RouteExecutor; $mux = new Pux\Mux; $mux->any('/product/:id', ['ProductController','itemAction']); $route = $mux->dispatch('/product/1'); $result = RouteExecutor::execute($route);
您还可以定义控制器构造函数的参数
class ProductController extends Pux\Controller { public function __construct($param1, $param2) { // do something you want } public function itemAction($id) { return "Product $id"; } } use Pux\RouteExecutor; $mux = new Pux\Mux; $mux->any('/product/:id', ['ProductController','itemAction'], [ 'constructor_args' => [ 'param1', 'param2' ], ]); $route = $mux->dispatch('/product/1'); $result = RouteExecutor::execute($route); // returns "Product 1"
分发策略
Pux 中有两种路由分发策略,而 Symfony/Routing 只提供 PCRE 模式匹配。
- 普通字符串比较。
- PCRE 模式比较。
尽管 PHP PCRE 缓存了编译的模式,但您已经知道 PCRE 模式匹配比普通字符串比较慢。
普通字符串比较是为静态路由路径设计的,当您有大量简单路由时,它可以提高性能。
PCRE 模式比较用于您有某些动态路由路径时,例如,您可以在您的路由路径中放置一些占位符,然后稍后传递这些路径参数到您的控制器。
Pux 对您的路由进行排序和编译到单个缓存文件中,它还使用最长匹配,因此在编译路由到缓存之前,它按模式长度降序排序模式。
Pux 使用索引数组作为存储路由信息的数据结构,因此它更快。
路由路径格式
静态路由
/post
PCRE 路由
/post/:id => matches /post/33
带可选模式的 PCRE 路由
/post/:id(/:title) => matches /post/33, /post/33/post%20title
/post/:id(\.:format) => matches /post/33, /post/33.json .. /post/33.xml
问答
为什么它更快
-
Pux 使用更简单的数据结构(索引数组)来存储模式和标志。(在 PHP 内部,
zend_hash_index_find
比zend_hash_find
更快)。 -
当匹配路由时,symfony 对每个路由使用很多函数调用。
https://github.com/symfony/Routing/blob/master/Matcher/UrlMatcher.php#L124
Pux 从索引数组中获取模式。
-
即使您启用了 APC 或其他字节码缓存扩展,您仍然会在运行时调用方法和函数。Pux 将路由构建减少到一次静态方法调用
__set_state
。 -
Pux 自动区分静态路由和动态路由,Pux 使用哈希表查找静态路由而无需遍历整个路由数组。
-
Pux\Mux
是用 C 扩展编写的,方法调用更快! -
使用 C 扩展,没有类加载开销。
-
Pux 将您的路由编译成普通 PHP 数组,编译后的路由可以非常快地加载。您在使用它之前不需要调用函数来注册路由。
为什么它在这里
我们大多数人使用很多机器来运行我们的应用程序,然而,它消耗了太多的能量和资源。
有些人认为路由不是瓶颈,事实上,这个项目并不声称路由是瓶颈。
实际上,“瓶颈”总是在不同的应用程序中不同,如果您有很多重的数据库请求,那么您的瓶颈就是您的数据库;如果您有很多复杂的计算,那么瓶颈应该是您的算法。
您可能会想,既然瓶颈不是路由,为什么我们还在C扩展中实现路由调度器?答案很简单,如果您将一个纯PHP路由组件和一些空的回调函数放入其中,并使用Apache基准测试工具来查看每秒可以处理多少个请求,您会发现路由组件消耗了大量的计算时间,请求数量会大幅减少。(它什么都不做,只是路由)
Pux试图减少加载PHP类和运行时方法/函数调用的开销,您可以在没有这些开销的情况下更快地运行您的应用程序。
分组模式匹配策略的优缺点
匹配路由的一个想法是将所有模式合并为一个模式,并使用一次pcre_match
将给定的路径与之比较。
但是,如果您有可选组或命名捕获组,这种方法就不适用了,如果使用它们之一,pcre_match
无法返回有关匹配了什么模式的详细信息。
而且,由于您将所有模式编译为一个,您无法将它们与具有不同条件的其他相同模式进行比较,例如
/users # GET
/users # POST
/users # with HTTP_HOST=somedomain
Pux中的权衡是按顺序比较路由,因为相同的模式可能位于不同的HTTP方法或不同的主机名中。
最佳方法是合并并编译正则表达式模式为FSM(有限状态机),复杂条件也可以合并到这个FSM中,并让这个FSM来调度路由。这就是Pux的长期目标。
贡献
测试XHProf中间件
在您的phpunit.xml
中定义XHPROF_ROOT
,您可以复制phpunit.xml.dist
到phpunit.xml
,例如
<php> <env name="XHPROF_ROOT" value="/Users/c9s/src/php/xhprof"/> </php>
修改Pux的C扩展
-
在GitHub问题页面上讨论您的主要想法。
-
fork此项目并为您的大修创建一个分支。
-
开发周期
cd ext ./compile ... hack hack hack ... # compile and run phpunit test ./compile && ./test -- --debug tests/Pux/MuxTest.php # use lldb to debug extension code ./compile && ./test -l -- tests/Pux/MuxTest.php # use gdb to debug extension code ./compile && ./test -g -- tests/Pux/MuxTest.php
-
提交!
-
发送pull请求并描述您所做的工作以及发生了什么变化。