Skip to content
Permalink
Browse files

Updated README

  • Loading branch information...
gigablah committed Mar 10, 2014
1 parent b81a3ea commit 513345deec83dd827674b19343f7eefd36c6ec3b
Showing with 171 additions and 104 deletions.
  1. +167 −100 README.md
  2. +1 −1 composer.json
  3. +3 −3 src/Durian/Handler.php
267 README.md
@@ -3,7 +3,7 @@ Durian

[![Build Status](https://travis-ci.org/gigablah/durian.png?branch=master)](https://travis-ci.org/gigablah/durian) [![Coverage Status](https://coveralls.io/repos/gigablah/durian/badge.png)](https://coveralls.io/r/gigablah/durian)

Durian is a PHP microframework that utilizes the newest features of PHP 5.4, 5.5 together with lightweight library components to create an accessible, compact framework with performant routing and flexible generator-style middleware.
Durian is a PHP microframework that utilizes the newest features of PHP 5.5 together with lightweight library components to create an accessible, compact framework with performant routing and flexible generator-style middleware.

Why?
----
@@ -18,7 +18,7 @@ Use [Composer][1] to install the gigablah/durian library by adding it to your `c
```json
{
"require": {
"gigablah/durian": "~0.0.3"
"gigablah/durian": "~0.1"
}
}
```
@@ -29,16 +29,109 @@ Usage
```php
$app = new Durian\Application();
$app->route('/hello/{name}', function () {
return 'Hello '.$this->param('name');
return 'Hello '.$this->params('name');
});
$app->run();
$app->run()->send();
```

Nothing special there. The `Application` container is based on [Pimple][2] and inherits its functions for defining lazy loading services. We also make use of the Symfony2 `Request` object so you have access to request headers, parameters and cookies. But since this is a PHP 5.5 microframework, it has been tailored to take advantage of shiny new [generator functions][3]. We'll explore that starting with `Application::route`.
Nothing special there. The `Application` container is based on [Pimple][2] and inherits its functions for defining lazy loading services. We also make use of the Symfony2 `Request` object so you have access to request headers, parameters and cookies. But since this is a PHP 5.5 microframework, it has been tailored to take advantage of shiny new [generator functions][3]. We'll explore that starting with the core of our application: the handler stack.

Handlers
--------

When the application is run, the `Request` object is inserted into a `Context` object which is passed through a series of handler functions. They may read information from the request attributes, insert information into the context, create a `Response`, throw an exception and so on. Some of these functions may themselves comprise a series of functions, which are executed in the same manner as the main stack. If there are any generator functions encountered along the way, they are revisited in reverse order after the end of the stack is reached. This entire mechanism is encapsulated in the `Handler` class.

All handlers boil down to an array of callables or generator functions with an optional test function. They can be defined using `Application::handler`:

```php
$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'];
});
```

This returns a `Handler` object which can now be added to the front or back of the middleware stack:

```php
$app->before($responseTimeHandler);
$app->after($someOtherHandler);
```

You can modify the entire stack with `Application::handlers`. The second parameter determines whether to replace the whole stack (true by default).

```php
$app->handlers([
$responseTimeHandler,
new Durian\Middleware\RouterMiddleware($app)
], true);
```

Each handler can itself be a stack of handlers! You can insert more handlers into a particular handler like you would with the main application. You may think of them as events; each `Handler` that contains a stack (as opposed to a single function) iterates through all its registered functions (listeners) independently, eventually passing the output to the main application stack. The main stack itself is registered in the container as `$app['app.handler']`.

Normally, a stack will stop iterating once a response is found in the context. To change this behaviour, you can set the `terminate_on_response` option to false:

```php
$app['app.handler']->options(['terminate_on_response' => false]);
```

### Context

See the lavish use of `$this` in the examples above? That's made possible by the automatic binding of each closure or generator to the `Context` object. Each time the application handles a request or subrequest, a new context is pushed onto the stack. Handlers and middlewares receive a `ContextProxy` which saves them the trouble of juggling between different context objects.

The context is a simple container for the `Request` and `Response` objects. It also holds the route parameters and the return values from each handler.

```php
$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` holds the return (or yielded) value from the previous handler. This is one way to pass information downstream other than using the application container or request attributes.

### Middleware

Middlewares are simply handler functions defined as concrete classes by extending `Middleware`. The logic goes in `Middleware::run`. Middlewares have access to all context methods, so the syntax is essentially unchanged:

```php
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 Injection

If a handler function returns another `Handler` or generator function, it will be inserted into the current position of the execution stack.

In essence, the handler stack is recursively iterated over as a multidimensional array.

Routing
-------

Instead of the hierarchical routing syntax found in some other microframeworks, we use method chaining. The `yield` keyword allows us to pass execution to the next matching segment. Upon reaching the end of the chain, the execution flow is passed back to all generators in reverse order. Therefore, code before and after a `yield` statement will "wrap" subsequent route handlers:

```php
$app['awesome_library'] = $app->share(function ($app) {
return new MyAwesomeLibrary();
@@ -49,16 +142,14 @@ $app->route('/hello', function () use ($app) {
yield 'Hello';
$app['awesome_library']->performCleanUp();
})->route('/{name}', function () {
return $this->last().' '.$this->param('name');
return $this->last().' '.$this->params('name');
})->get(function () {
return ['method' => 'GET', 'message' => $this->last()];
})->post(function () {
return ['method' => 'POST', 'message' => $this->last()];
});
```

Instead of the hierarchical routing syntax found in some other microframeworks, we use method chaining. The `yield` keyword allows us to pass execution to the next matching segment. Upon reaching the end of the chain, the execution flow is passed back to all generators in reverse order. Therefore, code before and after a `yield` statement will essentially "wrap" subsequent route handlers.

Why method chaining? The simple reason is that embedding the next route or method segment inside the route handler function forces us to execute the handler first before proceeding, thus potentially incurring expensive initialization code even if the request results in an error. Here, we stack the handler functions as each segment matches, and execute all of them in one go only if the route and method match is successful.

(At least, that was the original intention. Currently the framework utilizes [nikic/fast-route][4], which compiles all the routes into a single regex that maps to handler stack combinations.)
@@ -90,7 +181,9 @@ $app->route('/users/{name}')->post();
$app->route('/users/{name}/friends')->method('GET|POST');
```

Return values are automatically converted to Symfony2 `Response` objects. Arrays will result in a `JsonResponse`. You may also manually craft a response:
### ResponseMiddleware

`ResponseMiddleware` takes care of converting return values to Symfony2 `Response` objects. Arrays will result in a `JsonResponse`. You may also manually craft a response:

```php
$app->route('/tea', function () use ($app) {
@@ -107,81 +200,31 @@ $app->route('/404', function () {
});
```

Subrequests are performed by calling `Application::run`:
Returning a HTTP status code also works:

```php
$app->route('/song/{id:[0-9]+}', function () use ($app) {
$id = $this->param('id');
return [
'id' => $id,
'artist' => $app->run('GET', "/artists-by-song/$id")->getContent()
];
});
```

Context
-------

See the lavish use of `$this` in the examples above? That's made possible by the automatic binding of each closure or generator to the `Context` object. Each time the application handles a request or subrequest, a new context is pushed onto the stack.

The context is a simple container for the `Request` and `Response` objects. It also holds the route parameters and the return values from each handler.

```php
$app->route('/hello/{name}', function () {
return $this->param('name');
})->get(function () {
$name = $this->last();
$request = $this->request();
if (!$this->response()) {
$this->response("Hello $name");
}
$app->route('/fail', function () {
return 500;
});
```

`Context::last` holds the return (or yielded) value from the previous handler. This is a way to pass information downstream other than using the application container or request attributes.

Middleware
----------
### Subrequests

The pattern of generator stacking applies to the entire application flow, not just routing. All middlewares boil down to a callable or generator function with an optional test function. They can be defined using `Application::handler`:
Subrequests are performed by calling `Application::run`:

```php
$responseTimeMiddleware = $app->handler(function () {
$time = microtime(true);
yield;
$time = microtime(true) - $time;
$this->response()->headers->set('X-Response-Time', $time);
}, function () use ($app) {
return $this->master() && $app['debug'];
$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 returns a `Handler` object which can now be added to the front or back of the middleware stack:

```php
$app->before($responseTimeMiddleware);
$app->after($someOtherMiddleware);
```

You can modify the entire stack with `Application::handlers`. The second parameter determines whether to replace the whole stack (true by default).

```php
$app->handlers([
$responseTimeMiddleware,
new Durian\Middleware\RouterMiddleware()
]);
```

Middleware can also be defined as concrete classes by extending `AbstractMiddleware`.

Handler Injection
-----------------

If a handler function returns another `Handler`, it will be inserted into the current position of the execution stack.
During a subrequest, `$this->master()` will return false.

Similarly, if a handler function produces a generator that yields handlers, the whole collection will be inserted into the stack. This is exactly how the router middleware works.

In essence, the handler stack is recursively iterated over as a multidimensional array.
Thrown exceptions from subrequests are not converted to `Response` objects; they should be handled in the master request.

Exception Handling
------------------
@@ -195,15 +238,10 @@ $exceptionHandlerMiddleware = $app->handler(function () {
try {
yield;
} catch (\Exception $exception) {
if ($exception instanceof HttpException) {
$this->response(
$exception->getMessage(),
$exception->getStatusCode(),
$exception->getHeaders()
);
} else {
$this->response($exception->getMessage(), 500);
if (!$this->master()) {
throw $exception;
}
$this->response($exception->getMessage(), 500);
}
});
```
@@ -213,21 +251,38 @@ For pretty exception traces, you can make use of the [filp/whoops][5] library by
```json
{
"require": {
"gigablah/durian": "~0.0.3",
"gigablah/durian": "~0.1",
"filp/whoops": "~1.0"
}
}
```

Then, register `WhoopsMiddleware` as the first handler in your application:

```php
$app->before(new Durian\Middleware\WhoopsMiddleware($app));
```

You may also register it for only a specific sub-stack, like the example below:

```php
$app->handlers([
new Durian\Middleware\WhoopsMiddleware($app),
new Durian\Middleware\RouterMiddleware()
new Durian\Middleware\ResponseMiddleware($app),
new Durian\Handler([
new Durian\Middleware\WhoopsMiddleware($app),
new Durian\Middleware\RouterMiddleware($app)
])
]);
```

Just to drive home the point, you may also register it only for a specific route:

```php
$app->route('/foo', new Durian\Middleware\WhoopsMiddleware($app), function () {
throw new \Exception('bar');
});
```

Dependency Injection
--------------------

@@ -248,19 +303,30 @@ Method List
```php
$app->run($request); // handle a request or subrequest
$app->run($method, $path); // handle a HTTP method for a request path
$app->handler($callable, $optional_callable); // wrap a callback as a Handler
$app->handler($callable, $condition); // wrap a callback as a Handler
$app->handlers(); // get the handler stack
$app->handlers($handlers, $replace); // replace or append to the handler stack
$app->before($callable, $optional_callable); // prepend a handler to the stack
$app->after($callable, $optional_callable); // append a handler to the 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->routes(); // get the route collection
$app->routes($routes, $replace); // replace or append to the route collection
$app->context(); // get the current context
$app->context($context); // append a new context
$app->handle($request, $type, $catch); // implement HttpKernelInterface::handle
```

### Handler

```php
$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

```php
@@ -279,20 +345,21 @@ $route->dump(); // recursively dump all routes to an array
### Context

```php
$context->request(); // get the Request object
$context->request($request); // set the Request object
$context->response(); // get the Response object
$context->response($response); // set the Response object
$context->response($content, $status, $headers); // create a new Response object
$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, $headers, $code); // create and 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->param($key); // get a route parameter
$context->param($key, $default); // get a route parameter with fallback
$context->params($params); // insert route parameters
$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
```

License
@@ -1,6 +1,6 @@
{
"name": "gigablah/durian",
"version": "0.0.3",
"version": "0.1.0",
"type": "library",
"description": "A pungent PHP 5.5 microframework based on generator-style middleware.",
"keywords": ["framework", "micro", "microframework"],
Oops, something went wrong.

0 comments on commit 513345d

Please sign in to comment.
You can’t perform that action at this time.