gigablah / durian
基于生成器风格中间件的辛辣PHP 5.5微型框架。
Requires
- php: >=5.5.0
- nikic/fast-route: dev-master
- pimple/pimple: ~1.0
- symfony/http-foundation: >=2.3,<2.5-dev
- symfony/http-kernel: >=2.3,<2.5-dev
Requires (Dev)
- filp/whoops: ~1.0
- phpunit/phpunit: ~3.7
- satooshi/php-coveralls: dev-master
Suggests
- filp/whoops: For pretty exception traces
This package is auto-updated.
Last update: 2024-08-24 04:16:16 UTC
README
Durian是一个PHP微型框架,利用PHP 5.5的最新特性以及轻量级的库组件,创建了一个易于使用、紧凑的框架,具有高性能的路由和灵活的生成器风格中间件。
为什么?
因为我可以。
安装
使用 Composer 通过将其添加到您的 composer.json
中来安装gigablah/durian库。
{ "require": { "gigablah/durian": "~0.1" } }
使用方法
$app = new Durian\Application(); $app->route('/hello/{name}', function () { return 'Hello '.$this->params('name'); }); $app->run()->send();
这里没有特别之处。Application容器基于Pimple,并继承了其定义懒加载服务的功能。我们还利用了Symfony2的Request
对象,因此您可以访问请求头、参数和cookie。但鉴于这是一个PHP 5.5微型框架,它已被量身定制以利用新的生成器函数。我们将从应用程序的核心:处理程序堆栈开始探讨这一点。
处理程序
当应用程序运行时,Request
对象被插入到Context
对象中,然后通过一系列处理程序函数传递。它们可以读取请求属性中的信息,将信息插入上下文,创建响应,抛出异常等。其中一些函数本身可能是一系列函数,它们以与主堆栈相同的方式执行。如果在路上遇到任何生成器函数,它们将在到达堆栈末尾后以相反的顺序重新访问。整个机制封装在Handler
类中。
所有处理程序归结为一个可调用的数组或生成器函数,可选的测试函数。它们可以使用Application::handler
定义。
$responseTimeHandler = $app->handler(function () { // this executes before the rest of the stack $time = microtime(true); yield; // this executes after the rest of the stack $time = microtime(true) - $time; $this->response()->headers->set('X-Response-Time', $time); }, function () use ($app) { // only execute for the master request in the debug environment return $this->master() && $app['debug']; });
这返回一个Handler
对象,现在可以将其添加到中间件堆栈的前面或后面。
$app->before($responseTimeHandler); $app->after($someOtherHandler);
您可以使用Application::handlers
修改整个堆栈。第二个参数确定是否替换整个堆栈(默认为true)。
$app->handlers([ $responseTimeHandler, new Durian\Middleware\RouterMiddleware($app) ], true);
每个处理程序本身也可以是一个处理程序堆栈!您可以将更多处理程序插入到特定处理程序中,就像您会对主应用程序做的那样。您可以将它们视为事件;每个包含堆栈的Handler
(而不是单个函数)独立迭代所有已注册的函数(监听器),最终将输出传递给主应用程序堆栈。主堆栈本身在容器中注册为$app['app.handler']
。
通常,堆栈会在上下文中找到响应时停止迭代。要更改此行为,您可以设置terminate_on_response
选项为false。
$app['app.handler']->options(['terminate_on_response' => false]);
上下文
请看上面示例中对$this
的广泛使用?这是通过将每个闭包或生成器自动绑定到Context
对象来实现的。每次应用程序处理请求或子请求时,都会在堆栈上推入一个新的上下文。处理程序和中间件接收一个ContextProxy
,这样就免去了在不同上下文对象之间切换的麻烦。
上下文是一个简单的容器,用于存储Request
和Response
对象。它还持有路由参数和每个处理程序的返回值。
$app->route('/hello/{name}', function () { return $this->params('name'); })->get(function () { $name = $this->last(); $request = $this->request(); if (!$this->response()) { $this->response("Hello $name"); } });
Context::last
保存了上一个处理程序返回(或生成)的值。除了使用应用容器或请求属性外,这是向下传递信息的一种方式。
中间件
中间件是定义为具体类的处理函数,通过扩展 Middleware
实现。逻辑在 Middleware::run
中。中间件可以访问所有上下文方法,因此语法基本上没有变化
class ResponseTimeMiddleware extends Durian\Middleware { public function run() { $time = microtime(true); yield; $time = microtime(true) - $time; $this->response()->headers->set('X-Response-Time', $time); } } // Middlewares accept the application container in the constructor $app->before(new ResponseTimeMiddleware($app));
处理程序注入
如果处理程序函数返回另一个 Handler
或生成器函数,它将被插入到执行堆栈的当前位置。
本质上,处理程序堆栈是作为多维数组递归迭代的。
路由
与其他一些微框架中的分层路由语法不同,我们使用方法链。yield
关键字允许我们将执行传递给下一个匹配的段。到达链的末尾时,执行流程按顺序返回到所有生成器。因此,在 yield
语句之前和之后的代码将“包装”后续的路由处理程序
$app['awesome_library'] = $app->share(function ($app) { return new MyAwesomeLibrary(); }); $app->route('/hello', function () use ($app) { $app['awesome_library']->performExpensiveOperation(); yield 'Hello'; $app['awesome_library']->performCleanUp(); })->route('/{name}', function () { return $this->last().' '.$this->params('name'); })->get(function () { return ['method' => 'GET', 'message' => $this->last()]; })->post(function () { return ['method' => 'POST', 'message' => $this->last()]; });
为什么使用方法链?简单的原因是,将下一个路由或方法段嵌入路由处理程序函数中,迫使我们首先执行处理程序,然后再继续执行,因此即使请求导致错误,也可能产生昂贵的初始化代码。在这里,我们按每个段匹配的方式堆叠处理程序函数,并且仅在路由和方法匹配成功时一次性执行所有处理程序。
(至少,这是原始的意图。目前框架使用 nikic/fast-route,它将所有路由编译成一个单个正则表达式,该正则表达式映射到处理程序堆栈组合。)
注意,Application::route
开始一个新段并返回一个新的 Route
对象。方法函数映射到所有常见的 HTTP 请求方法(get、post、put、delete、patch、options)并返回相同的 Route
。所有路由方法都接受任意数量的处理程序函数,因此您可以封装周围的操作(如上面的示例)到单独的生成器中
$expensiveOperation = function () use ($app) { $app['awesome_library']->performExpensiveOperation(); yield; $app['awesome_library']->performCleanUp(); }; $app->route('/hello', $expensiveOperation, function () { return 'Hello'; })->route(...);
您不必链接路由段,旧式定义整个路径的方法仍然可以正常工作
// Routes will support GET by default $app->route('/users'); // Methods can be declared without handlers $app->route('/users/{name}')->post(); // Declare multiple methods separated by pipe characters $app->route('/users/{name}/friends')->method('GET|POST');
响应中间件
ResponseMiddleware
负责将返回值转换为 Symfony2 的 Response
对象。数组将生成一个 JsonResponse
。您也可以手动创建响应
$app->route('/tea', function () use ($app) { $this->response('I\'m a teapot', 418); });
或者抛出异常
$app->route('/404', function () { // Alternatively pass in an exception object as the first parameter $this->error('Not Found', 404); });
返回 HTTP 状态代码也有效
$app->route('/fail', function () { return 500; });
子请求
通过调用 Application::run
执行子请求
$app->route('/song/{id:[0-9]+}', function () use ($app) { $id = $this->params('id'); return [ 'id' => $id, 'artist' => $app->run('GET', "/artists-by-song/$id")->getContent() ]; });
在子请求期间,$this->master()
将返回 false。
子请求抛出的异常不会转换为 Response
对象;它们应在主请求中处理。
异常处理
每当抛出异常时,应用程序都会将其通过堆栈中的所有生成器向上传递。
这意味着您可以通过将 yield
语句包裹在 try/catch 块中来拦截任何异常
$exceptionHandlerMiddleware = $app->handler(function () { try { yield; } catch (\Exception $exception) { if (!$this->master()) { throw $exception; } $this->response($exception->getMessage(), 500); } });
对于漂亮的异常跟踪,您可以使用 filp/whoops 库,通过将其包含在您的 composer.json 中来实现
{ "require": { "gigablah/durian": "~0.1", "filp/whoops": "~1.0" } }
然后,将 WhoopsMiddleware
注册为应用程序中的第一个处理程序
$app->before(new Durian\Middleware\WhoopsMiddleware($app));
您也可以只为特定的子堆栈注册它,如下面的示例所示
$app->handlers([ new Durian\Middleware\ResponseMiddleware($app), new Durian\Handler([ new Durian\Middleware\WhoopsMiddleware($app), new Durian\Middleware\RouterMiddleware($app) ]) ]);
只是为了强调,您也可以只为特定的路由注册它
$app->route('/foo', new Durian\Middleware\WhoopsMiddleware($app), function () { throw new \Exception('bar'); });
依赖注入
依赖注入?那是什么? :)
除了应用程序容器基于 Pimple
(一个轻量级的DIC或服务定位器,如果你愿意的话),当前在路由处理器上没有进行参数匹配。最终我希望将其实现为一个可选特性。请关注这个空间!
HttpKernelInterface
Application
容器实现了 Symfony2 的 HttpKernelInterface
,因此你可以通过 Stack 与其他兼容的应用程序进行组合。
方法列表
Application
$app->run($request); // handle a request or subrequest $app->run($method, $path); // handle a HTTP method for a request path $app->handler($callable, $condition); // wrap a callback as a Handler $app->handlers(); // get the handler stack $app->handlers(array $handlers, $replace); // replace or append to the stack $app->before($callable, $condition); // prepend a handler to the stack $app->after($callable, $condition); // append a handler to the stack $app->route($path, ...$handlers); // start a new route segment $app->handle($request, $type, $catch); // implement HttpKernelInterface::handle
处理器
$handler->run(); // iterate through the handler stack $handler->handler($callable, $condition); // wrap a callback as a Handler $handler->handlers(); // get the handler stack $handler->handlers(array $handlers, $replace); // replace or append to the stack $handler->context($context); // set the HTTP context $handler->context(); // get the HTTP context $handler->before($callable, $condition); // prepend a handler to the stack $handler->after($callable, $condition); // append a handler to the stack $handler->options(array $options); // set the handler options $handler->options(); // get the handler options
路由
$route->route($path, ...$handlers); // append a new route segment $route->method($methods, ...$handlers); // register method handler(s) $route->get(...$handlers); // register method handler(s) for GET $route->post(...$handlers); // register method handler(s) for POST $route->put(...$handlers); // register method handler(s) for PUT $route->delete(...$handlers); // register method handler(s) for DELETE $route->patch(...$handlers); // register method handler(s) for PATCH $route->options(...$handlers); // register method handler(s) for OPTIONS $route->head(...$handlers); // register method handler(s) for HEAD $route->dump(); // recursively dump all routes to an array
上下文
$context->request(); // get the Request $context->request($request, $type); // set the Request $context->response(); // get the Response $context->response($response); // set the Response $context->response($content, $status, array $headers); // create a new Response $context->error($exception); // throw an exception $context->error($message, $status, array $headers, $code); // throw an exception $context->master(); // check whether the current request is the master request $context->params($key); // get a route parameter $context->params($key, $default); // get a route parameter with fallback $context->params(array $params); // insert route parameters $context->params(); // get all route parameters $context->append($output); // append a handler return value $context->last(); // get the last handler return value $context->clear(); // clear the current context
许可协议
在 MIT 许可协议下发布。有关详细信息,请参阅 LICENSE 文件。
致谢
本项目受到了以下项目的启发: