gigablah/durian

基于生成器风格中间件的辛辣PHP 5.5微型框架。

0.1.0 2014-03-10 11:02 UTC

This package is auto-updated.

Last update: 2024-08-24 04:16:16 UTC


README

Build Status Coverage Status

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,这样就免去了在不同上下文对象之间切换的麻烦。

上下文是一个简单的容器,用于存储RequestResponse对象。它还持有路由参数和每个处理程序的返回值。

$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 文件。

致谢

本项目受到了以下项目的启发: