fruit / routekit
fruit框架的快速路由器
Requires
- php: ^7
- fruit/compilekit: >=0.2.12
Requires (Dev)
- corneltek/pux: ~1
- fruit/benchkit: ~1
- lox/xhprof: @dev
- pdepend/pdepend: *
- phploc/phploc: *
- phpmd/phpmd: *
- phpunit/phpunit: ~6
- sebastian/phpcpd: *
- squizlabs/php_codesniffer: *
README
此包是Fruit框架的一部分,需要PHP 7。
RouteKit是一个快速路由器实现。它将您的路由规则存储在树结构中,您可以通过生成内置类生成器来创建路由器类来使其更快。
概要
$mux = new Fruit\RouteKit\Mux; // Create routing rules $mux->get('/', array('MyClass', 'myMethod')); // static method $mux->get('/', 'Foo::Bar'); // function $mux->get('/', 'foobar'); $mux->get('/', foobar); // create handler/controller instance with constructor arguments. $mux->get('/foo', array('MyClass', 'myMethod'), array('args', 'of', 'constructor')); class FOO { public function BAR($arg1, $arg2){} public function BAZ($arg1, $arg2){} } // uri variables are unnamed, ":x" is identical to ":" $mux->get('/foo/:/:x/bar', array('FOO', 'BAR')); $mux->get('/foo/:1/baz/:2', array('FOO', 'BAZ')); // you cannot use closure as handler // $mux->get('/', function(){}); // wrong! // but you can use object if it can be serialized with var_export() $mux->get('/', array(new FOO, 'BAR')); // dispatch request $mux->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['PATH_INFO']); // generate class file_put_content('router.php', $mux->compileFile()); // use generated router class $mux = require('router.php'); $mux->dispatch('GET', '/foo/123/abc/bar'); // (new Foo())->BAR('123', 'abc'); $mux->dispatch('GET', '/foo'); // (new MyClass('args', 'of', 'constructor'))->myMethod(); // use injector to do DI tasks function di($url, $obj, $method) { $obj->setDB($db); } $mux->setInjector('di'); $mux->get('/', array(new FOO, 'BAR')); $mux->dispatch('GET', '/'); // $obj = new FOO; di('/', $obj, 'BAR'); $obj->BAR(); // auth middleware, implements by input filter function checkAuth($method, $url, $cb, $params) { if (!authed()) { header('Location: /login'); return 'login first'; } } $mux->setFilters(['checkAuth'], []); $mux->get('/', array(new FOO, 'BAR')); $mux->dispatch('GET', '/'); // redirect to /login if not logged in // CORS middleware, implements by output filter function addCORS($result) { // few lines of code adding CORS headers here return $result; } $mux->setFilters([], ['addCORS']); $mux->get('/', array(new FOO, 'BAR')); $mux->dispatch('GET', '/'); // client will receive CORS header! // Templating middleware function handler() { // real codes return ['view' => 'index.tmpl', 'data' => $result]; } function forgeView($result) { return Template::load($result['view'])->render($result['data']); } $mux->setFilters([], ['addCORS']); $mux->get('/', 'handler'); $mux->dispatch('GET', '/');
为什么快速
RouteKit通过两种方式提高性能:更好的规则匹配数据结构和将动态调用转换为静态调用的能力。
匹配过程
RouteKit将路由规则存储在树结构中,因此匹配速度不会受到您有多少条规则的影响。换句话说,匹配过程具有常数时间复杂度。
更进一步,在生成的路由器中,我们使用有限状态机进行匹配过程,消除了所有可能的函数调用。与opcode操作(如if、switch、赋值、算术运算等)和散列表操作相比,函数调用要慢得多。在实践中,FSM方法可以比数组实现快20多倍。
静态调用
大多数路由器实现将分发工作分为两部分:匹配规则以获取正确的处理程序,然后使用cufa执行它;而cufa以其出色的性能而闻名。
通过生成自定义路由器,我们根据您在路由规则中提供的信息生成代码。不再需要cufa,不再需要反射,因此没有性能损失。
由于代码主要使用var_export
生成,因此某些情况不支持。
- 无法使用
__set_state
恢复类。 - 闭包和匿名函数。
- 不可访问的方法。(非public方法)
- 特殊变量,如资源。
生成的类应该是线程安全的,因为所有属性和方法都是静态的。
类型转换
您可以为处理程序添加类型提示,RouteKit将自动为您进行类型检查和转换。目前我们只支持int
、float
、bool
和string
。如果没有类型提示,我们将不会检查/转换参数。
在非编译版本中,这是通过ReflectionParameter::getType()
完成的,因此获取反射数据会花费一些性能。
在编译版本中,我们在编译时获取类型信息,因此除了类型检查和转换工作外,没有性能损失。
function handlerOK(int $i) {} function handlerFAIL(array $i) {} $mux->get('/ok/:', 'handlerOK'); $mux->get('/fail/:', 'handlerFAIL'); $mux->dispatch('GET', '/ok/1'); // works $mux->dispatch('GET', '/ok/hello'); // throw a Fruit\RouteKit\TypeMismatchException $mux->dispatch('GET', '/fail/1'); // throw an Exception with messages telling you array type is not supported $mux->compile(); // throw an Exception with messages telling you array type is not supported
注入器和过滤器
注入器
注入器旨在解决“全局变量”问题:您可以通过您定义的接口将全局数据注入到您的处理程序对象中。
输入过滤器
输入过滤器旨在过滤控制流。应该在此处实现权限验证层。
输入过滤器接受正好4个参数
- HTTP方法
- URL
- 表示处理程序的调用
- 传递给处理程序的参数数组。
返回任何非NULL值都会中断分发过程,并立即返回过滤结果。
虽然PHP语法允许您修改这4个参数,但您不应该修改它们。这是因为输入过滤器不是为了过滤输入数据。
输出过滤器
输出过滤器旨在过滤结果。如果您想细化结果或HTTP头,您就到了正确的位置。
输出过滤器只接受1个参数,即处理程序返回的数据:您可以修改它,发送或删除一些头部,然后将结果返回。最常见的例子是添加CORS头部或通过模板系统伪造真实输出。
分发流程
- 在路由树中搜索当前处理程序并收集URL参数。
- 执行输入过滤器,决定是否执行下一步。
- 如果任何输入过滤器阻止执行,则立即返回。
- 使用注入器修改处理程序的内部状态。
- 执行处理程序。
- 通过执行输出过滤器来操作结果。
许可证
任何版本的MIT、GPL或LGPL。