Durian Building Singapore / Dave Cross / CC BY-NC-SA 2.0

DURIAN
A PHP 5.5 microframework based on generator-style middlew...
BEFORE WE BEGIN…
What the heck are generators ?
GENERATORS
•

Introduced in PHP 5.5 (although HHVM had them earlier)	


•

Generators are basically iterators with a simpl...
THE YIELD KEYWORD
class MyIterator implements Iterator	
{	
private $values;	

!

public function __construct(array $values...
PHP MICROFRAMEWORKS
How do they handle middleware and routing ?
EVENT LISTENERS
$app->before(function (Request $request) use ($app) {	
$app['response_time'] = microtime(true);	
});	
 	
$...
THE DOWNSIDE
•

A decorator has to be split into two separate
functions to wrap the main application	


•

Data has to be ...
HIERARCHICAL ROUTING
$app->path('blog', function ($request) use ($app) {	
$time = microtime(true);	
$blog = BlogService::c...
THE DOWNSIDE
•

Subsequent route and method declarations are now
embedded inside a closure	


•

Closure needs to be execu...
“CALLBACK HELL”
$app->path('a', function () use ($app) {	
$app->param('b', function ($b) use ($app) {	
$app->path('c', fun...
How about other languages ?
KOA (NODEJS)
var koa = require('koa');	
var app = koa();	

!
app.use(function *(next){	
var start = new Date;	
yield next;...
MARTINI (GOLANG)
package main	
import "github.com/codegangsta/martini"	

!
func main() {	
m := martini.Classic()	

!
m.Use...
INTRODUCING DURIAN
•

Take advantage of PHP 5.4, 5.5 features	


•

Unify interface across controllers and middleware	


•...
COMPONENTS
•

Application container: Pimple by @fabpot	


•

Request/Response: Symfony2 HttpFoundation	


•

Routing: Fast...
A DURIAN APPLICATION
$app = new DurianApplication();	
!

$app->route('/hello/{name}', function () {	
return 'Hello '.$this...
HANDLERS
•

Simple wrapper around closures and generators	


•

Handlers consist of the primary callback and an optional g...
THE HANDLER STACK
•

Application::handle() iterates through a generator that
produces Handlers to be invoked	


•

Generat...
function

generator

Route dispatcher
A

D

B

A

C

B

C

D
MODIFYING THE STACK
$app['middleware.response_time'] = $app->handler(function () {	
$time = microtime(true);	
yield;	
$tim...
ROUTE HANDLER
•

Apply the handler concept to route matching	



$app->handler(function () {	
$this->response('Hello World...
ROUTE CHAINING
$app['awesome_library'] = $app->share(function ($app) {	
return new MyAwesomeLibrary();	
});	

!
$app->rout...
ROUTE DISPATCHING
•

This route definition:	



$albums = $app->route('/albums', A)->get(B)->post(C);	
$albums->route('/{ai...
•

Route chaining isn’t mandatory !	


•

You can still use the regular syntax

// Routes will support GET by default	
$ap...
CONTEXT
•

Every handler is bound to the Context object using Closure::bind	


•

A new context is created for every reque...
EXCEPTION HANDLING
•

Exceptions are caught and bubbled back up through all registered
generators	


•

Intercept them by ...
AWESOME EXAMPLE
Let’s add two integers together !
$app->route('/add', function () use ($app) {



$app['number_collection'] = $app->share(function ($app) {	
return new Numb...
COMING SOON
•

Proper tests and coverage (!!!)	


•

Handlers for format negotiation, session, locale, etc	


•

Dependenc...
THANK YOU
bigblah@gmail.com	

https://github.com/gigablah	

http://durianphp.com
Durian: a PHP 5.5 microframework with generator-style middleware
Upcoming SlideShare
Loading in …5
×

Durian: a PHP 5.5 microframework with generator-style middleware

1,339 views

Published on

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.

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,339
On SlideShare
0
From Embeds
0
Number of Embeds
7
Actions
Shares
0
Downloads
7
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Durian: a PHP 5.5 microframework with generator-style middleware

  1. 1. Durian Building Singapore / Dave Cross / CC BY-NC-SA 2.0 DURIAN A PHP 5.5 microframework based on generator-style middleware http://durianphp.com
  2. 2. BEFORE WE BEGIN… What the heck are generators ?
  3. 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. 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(); }
  5. 5. PHP MICROFRAMEWORKS
  6. 6. How do they handle middleware and routing ?
  7. 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. 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. 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. 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. 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) { // ... }); }); }); }); }); });
  12. 12. How about other languages ?
  13. 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. 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. 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
  16. 16. COMPONENTS • Application container: Pimple by @fabpot • Request/Response: Symfony2 HttpFoundation • Routing: FastRoute by @nikic • Symfony2 HttpKernelInterface (for stackphp compatibility)
  17. 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. 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. 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
  20. 20. function generator Route dispatcher A D B A C B C D
  21. 21. MODIFYING THE STACK $app['middleware.response_time'] = $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->handlers([ 'middleware.response_time', new DurianMiddlewareRouterMiddleware() ]); ! $app->after(new DurianMiddlewareResponseMiddleware()); ! $app->before(new DurianMiddlewareWhoopsMiddleware());
  22. 22. ROUTE HANDLER • Apply the handler concept to route matching 
 $app->handler(function () { $this->response('Hello World!'); }, function () { $matcher = new RequestMatcher('^/$'); return $matcher->matches($this->request()); }); • Compare to 
 $app->route('/', function () { $this->response('Hello World!'); });
  23. 23. ROUTE CHAINING $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->param('name'); })->get(function () { return ['method' => 'GET', 'message' => $this->last()]; })->post(function () { return ['method' => 'POST', 'message' => $this->last()]; });
  24. 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. 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. 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. 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); } });
  28. 28. AWESOME EXAMPLE Let’s add two integers together !
  29. 29. $app->route('/add', function () use ($app) {
 
 $app['number_collection'] = $app->share(function ($app) { return new NumberCollection(); }); $app['number_parser'] = $app->share(function ($app) { return new SimpleNumberStringParser(); });" yield; $addition = new AdditionOperator('SimplePHPEasyPlusNumberSimpleNumber'); $operation = new ArithmeticOperation($addition); $engine = new Engine($operation); $calcul = new Calcul($engine, $app['number_collection']); $runner = new CalculRunner(); $runner->run($calcul); $result = $calcul->getResult(); $numericResult = $result->getValue(); $this->response('The answer is: ' . $numericResult);
 })->route('/{first:[0-9]+}', function () use ($app) {
 $firstParsedNumber = $app['number_parser']->parse($this->param('first')); $firstNumber = new SimpleNumber($firstParsedNumber); $firstNumberProxy = new CollectionItemNumberProxy($firstNumber); $app['number_collection']->add($firstNumberProxy);
 })->route('/{second:[0-9]+}', function () use ($app) {
 $secondParsedNumber = $app['number_parser']->parse($this->param('second')); $secondNumber = new SimpleNumber($secondParsedNumber); $secondNumberProxy = new CollectionItemNumberProxy($secondNumber); $app['number_collection']->add($secondNumberProxy);
 })->get();
  30. 30. COMING SOON • Proper tests and coverage (!!!) • Handlers for format negotiation, session, locale, etc • Dependency injection through reflection (via trait) • Framework/engine-agnostic view composition and template rendering (separate project)
  31. 31. THANK YOU bigblah@gmail.com https://github.com/gigablah http://durianphp.com

×