此包的最新版本(1.6.1)没有可用的许可证信息。

快速的PHP路由器

维护者

详细信息

github.com/c9s/Pux

主页

源代码

问题

安装数: 47,729

依赖项: 17

建议者: 1

安全性: 0

星标: 1,274

关注者: 81

分支: 81

开放问题: 23

语言:C

1.6.1 2015-08-24 11:53 UTC

README

Pux是一个更快的PHP路由器,它还包含开箱即用的控制器助手。

Latest Stable Version Total Downloads Latest Unstable Version License Monthly Downloads Daily Downloads

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 模式匹配。

  1. 普通字符串比较。
  2. 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_findzend_hash_find 更快)。

  • 当匹配路由时,symfony 对每个路由使用很多函数调用。

    https://github.com/symfony/Routing/blob/master/Matcher/UrlMatcher.php#L124

    Pux 从索引数组中获取模式。

    https://github.com/c9s/Pux/blob/master/src/Pux/Mux.php#L189

  • 即使您启用了 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.distphpunit.xml,例如

  <php>
    <env name="XHPROF_ROOT" value="/Users/c9s/src/php/xhprof"/>
  </php>

修改Pux的C扩展

  1. 在GitHub问题页面上讨论您的主要想法。

  2. fork此项目并为您的大修创建一个分支。

  3. 开发周期

     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
    
  4. 提交!

  5. 发送pull请求并描述您所做的工作以及发生了什么变化。