Durian utilizes the newest features of PHP 5.4 and 5.5 as well as lightweight library components to create an accessible, compact framework with performant routing and flexible generator-style middleware.
3. GENERATORS
•
Introduced in PHP 5.5 (although HHVM had them earlier)
•
Generators are basically iterators with a simpler syntax
•
The mere presence of the yield keyword turns a closure into a
generator constructor
•
Generators are forward-only (cannot be rewound)
•
You can send() values into generators
•
You can throw() exceptions into generators
4. THE YIELD KEYWORD
class MyIterator implements Iterator
{
private $values;
!
public function __construct(array $values) {
$this->values = $values;
}
public function current() {
return current($this->values);
}
public function key() {
return key($this->values);
}
public function next() {
return next($this->values);
}
public function rewind() {}
public function valid() {
return null !== key($this->values);
}
}
$iterator = new MyIterator([1,2,3,4,5]);
while ($iterator->valid()) {
echo $iterator->current();
$iterator->next();
}
$callback = function (array $values) {
foreach ($values as $value) {
yield $value;
}
};
$generator = $callback([1,2,3,4,5]);
while ($generator->valid()) {
echo $generator->current();
$generator->next();
}
7. EVENT LISTENERS
$app->before(function (Request $request) use ($app) {
$app['response_time'] = microtime(true);
});
$app->get('/blog', function () use ($app) {
return $app['blog_service']->getPosts()->toJson();
});
$app->after(function (Request $request, Response $response) use ($app) {
$time = microtime(true) - $app['response_time'];
$response->headers->set('X-Response-Time', $time);
});
8. THE DOWNSIDE
•
A decorator has to be split into two separate
functions to wrap the main application
•
Data has to be passed between functions
•
Can be confusing to maintain
9. HIERARCHICAL ROUTING
$app->path('blog', function ($request) use ($app) {
$time = microtime(true);
$blog = BlogService::create()->initialise();
$app->path('posts', function () use ($app, $blog) {
$posts = $blog->getAllPosts();
$app->get(function () use ($app, $posts) {
return $app->template('posts/index', $posts->toJson());
});
});
$time = microtime(true) - $time;
$this->response()->header('X-Response-Time', $time);
});
10. THE DOWNSIDE
•
Subsequent route and method declarations are now
embedded inside a closure
•
Closure needs to be executed to proceed
•
Potentially incurring expensive initialisation or
computations only to be discarded
•
Middleware code is still split across two locations
11. “CALLBACK HELL”
$app->path('a', function () use ($app) {
$app->param('b', function ($b) use ($app) {
$app->path('c', function () use ($b, $app) {
$app->param('d', function ($d) use ($app) {
$app->get(function () use ($d, $app) {
$app->json(function () use ($app) {
// ...
});
});
});
});
});
});
13. KOA (NODEJS)
var koa = require('koa');
var app = koa();
!
app.use(function *(next){
var start = new Date;
yield next;
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
});
!
app.use(function *(){
this.body = 'Hello World';
});
!
app.listen(3000);
14. MARTINI (GOLANG)
package main
import "github.com/codegangsta/martini"
!
func main() {
m := martini.Classic()
!
m.Use(func(c martini.Context, log *log.Logger) {
log.Println("before a request")
c.Next()
log.Println("after a request")
})
!
m.Get("/", func() string {
return "Hello world!"
})
!
m.Run()
}
15. INTRODUCING DURIAN
•
Take advantage of PHP 5.4, 5.5 features
•
Unify interface across controllers and middleware
•
Avoid excessive nesting / callback hell
•
Use existing library components
•
None of this has anything to do with durians
17. A DURIAN APPLICATION
$app = new DurianApplication();
!
$app->route('/hello/{name}', function () {
return 'Hello '.$this->param('name');
});
!
$app->run();
•
Nothing special there, basically the same syntax
as every microframework ever
18. HANDLERS
•
Simple wrapper around closures and generators
•
Handlers consist of the primary callback and an optional guard
callback
$responseHandler = $app->handler(function () {
$time = microtime(true);
yield;
$time = microtime(true) - $time;
$this->response()->headers->set('X-Response-Time', $time);
}, function () use ($app) {
return $app['debug'];
});
19. THE HANDLER STACK
•
Application::handle() iterates through a generator that
produces Handlers to be invoked
•
Generators produced from handlers are placed into
another stack to be revisited in reverse order
•
A Handler may produce a generator that produces more
Handlers, which are fed back to the main generator
•
The route dispatcher is one such handler
24. ROUTE DISPATCHING
•
This route definition:
$albums = $app->route('/albums', A)->get(B)->post(C);
$albums->route('/{aid:[0-9]+}', D, E)->get(F)->put(G, H)->delete(I);
•
Gets turned into:
GET
POST
GET
PUT
DELETE
/albums
/albums
/albums/{aid}
/albums/{aid}
/albums/{aid}
=>
=>
=>
=>
=>
[A,B]"
[A,C]"
[A,D,E,F]"
[A,D,E,G,H]"
[A,D,E,I]
25. •
Route chaining isn’t mandatory !
•
You can still use the regular syntax
// 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');
26. CONTEXT
•
Every handler is bound to the Context object using Closure::bind
•
A new context is created for every request or sub request
Get the Request object
$request = $this->request();
Get the Response
$response = $this->response();
Set the Response
$this->response("I'm a teapot", 418);
Get the last handler output
$last = $this->last();
Get a route parameter
$id = $this->param('id');
Throw an error
$this->error('Forbidden', 403);
27. EXCEPTION HANDLING
•
Exceptions are caught and bubbled back up through all registered
generators
•
Intercept them by wrapping the yield statement with a try/catch block
$exceptionHandlerMiddleware = $app->handler(function () {
try {
yield;
} catch (Exception $exception) {
$this->response($exception->getMessage(), 500);
}
});