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

Durian: a PHP 5.5 microframework with generator-style middleware

Embed Size (px)

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.

Citation preview

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