durian: a php 5.5 microframework with generator-style middleware

32
DURIAN A PHP 5.5 microframework based on generator-style middleware http://durianphp.com Durian Building Singapore / Dave Cross / CC BY-NC-SA 2.0

Upload: kuan-yen-heng

Post on 10-May-2015

738 views

Category:

Technology


1 download

DESCRIPTION

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.

TRANSCRIPT

Page 1: Durian: a PHP 5.5 microframework with generator-style middleware

DURIANA PHP 5.5 microframework based on generator-style middleware

http://durianphp.com

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

Page 2: Durian: a PHP 5.5 microframework with generator-style middleware

BEFORE WE BEGIN…What the heck are generators ?

Page 3: Durian: a PHP 5.5 microframework with generator-style middleware

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

Page 4: Durian: a PHP 5.5 microframework with generator-style middleware

THE YIELD KEYWORDclass 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();}

Page 5: Durian: a PHP 5.5 microframework with generator-style middleware

PHP MICROFRAMEWORKS

Page 6: Durian: a PHP 5.5 microframework with generator-style middleware

How do they handle middleware and routing ?

Page 7: Durian: a PHP 5.5 microframework with generator-style middleware

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); });

Page 8: Durian: a PHP 5.5 microframework with generator-style middleware

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

Page 9: Durian: a PHP 5.5 microframework with generator-style middleware

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); });

Page 10: Durian: a PHP 5.5 microframework with generator-style middleware

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

Page 11: Durian: a PHP 5.5 microframework with generator-style middleware

“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) { // ... }); }); }); }); }); });

Page 12: Durian: a PHP 5.5 microframework with generator-style middleware

How about other languages ?

Page 13: Durian: a PHP 5.5 microframework with generator-style middleware

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);

Page 14: Durian: a PHP 5.5 microframework with generator-style middleware

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() }

Page 15: Durian: a PHP 5.5 microframework with generator-style middleware

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

Page 16: Durian: a PHP 5.5 microframework with generator-style middleware

COMPONENTS• Application container : Pimple by @fabpot

• Request/Response: Symfony2 HttpFoundation

• Routing: FastRoute by @nikic

• Symfony2 HttpKernelInterface (for stackphp compatibility)

Page 17: Durian: a PHP 5.5 microframework with generator-style middleware

A DURIAN APPLICATION $app = new Durian\Application();! $app->route('/hello/{name}', function () { return 'Hello '.$this->param('name'); });! $app->run();

• Nothing special there, basically the same syntax as every microframework ever

Page 18: Durian: a PHP 5.5 microframework with generator-style middleware

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

Page 19: Durian: a PHP 5.5 microframework with generator-style middleware

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

Page 20: Durian: a PHP 5.5 microframework with generator-style middleware

A D

B C

A B C D

function generator

Route dispatcher

Page 21: Durian: a PHP 5.5 microframework with generator-style middleware

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 Durian\Middleware\RouterMiddleware() ]);! $app->after(new Durian\Middleware\ResponseMiddleware());! $app->before(new Durian\Middleware\WhoopsMiddleware());

Page 22: Durian: a PHP 5.5 microframework with generator-style middleware

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!'); });

Page 23: Durian: a PHP 5.5 microframework with generator-style middleware

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()]; });

Page 24: Durian: a PHP 5.5 microframework with generator-style middleware

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 /albums => [A,B]" POST /albums => [A,C]" GET /albums/{aid} => [A,D,E,F]" PUT /albums/{aid} => [A,D,E,G,H]" DELETE /albums/{aid} => [A,D,E,I]

Page 25: Durian: a PHP 5.5 microframework with generator-style middleware

• 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');

Page 26: Durian: a PHP 5.5 microframework with generator-style middleware

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);

Page 27: Durian: a PHP 5.5 microframework with generator-style middleware

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); } });

Page 28: Durian: a PHP 5.5 microframework with generator-style middleware

AWESOME EXAMPLELet’s add two integers together !

Page 29: Durian: a PHP 5.5 microframework with generator-style middleware
Page 30: Durian: a PHP 5.5 microframework with generator-style middleware

$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('SimplePHPEasyPlus\Number\SimpleNumber'); $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();

Page 31: Durian: a PHP 5.5 microframework with generator-style middleware

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)

Page 32: Durian: a PHP 5.5 microframework with generator-style middleware

THANK [email protected]

https://github.com/gigablah http://durianphp.com