build a custom (micro)framework with zf2 components (as building blocks)

66
ZFDAY - 2014 BUILD A CUSTOM (MICRO)FRAMEWORK WITH ZF2 COMPONENTS AS BUILDING BLOCKS Author / Walter Dal Mut @walterdalmut

Upload: corley-srl

Post on 01-Dec-2014

694 views

Category:

Technology


0 download

DESCRIPTION

We will cover how to build a custom microframework using ZF2 components as building blocks.

TRANSCRIPT

Page 1: Build a custom (micro)framework with ZF2 Components (as building blocks)

ZFDAY - 2014BUILD A CUSTOM (MICRO)FRAMEWORKWITH ZF2 COMPONENTS AS BUILDING BLOCKS

Author / Walter Dal Mut @walterdalmut

Page 2: Build a custom (micro)framework with ZF2 Components (as building blocks)

WHO AM I?, I work at

and also at Walter Dal Mut Corley S.r.l.

UpCloo Ltd.

You can contact me at: [email protected]

On Twitter: @walterdalmut

On GitHub: wdalmut

Page 3: Build a custom (micro)framework with ZF2 Components (as building blocks)

WHY A CUSTOM FRAMEWORK?Tipically because we want to solve a particular problem.

ZendFramework is a general purpose Web Framework thatcan fit different problems easily.

Page 4: Build a custom (micro)framework with ZF2 Components (as building blocks)

IT SOUNDS LIKE REINVENTING THE WHEELNO, WE DON'T WANT!

We will use ZF2 components as building blocksWe will drive a personal behaviour

Page 5: Build a custom (micro)framework with ZF2 Components (as building blocks)

IT COULD WORK!!

Page 6: Build a custom (micro)framework with ZF2 Components (as building blocks)

REQUIREMENTSEvent Driven Design

Dependency Inversion Principle

TESTABLE/TDD

Page 7: Build a custom (micro)framework with ZF2 Components (as building blocks)

EVENT DRIVEN DESIGN

All framework operations are events (like ZF2)

Page 8: Build a custom (micro)framework with ZF2 Components (as building blocks)

DEPENDENCY INVERSIONWe will focus on Service Manager

Page 9: Build a custom (micro)framework with ZF2 Components (as building blocks)

[ WE WANT TO TEST OUR APPLICATIONS ]

Page 10: Build a custom (micro)framework with ZF2 Components (as building blocks)

COMPOSE IT!{ "require": { "zendframework/zend-eventmanager": "2.*", "zendframework/zend-servicemanager": "2.*", "zendframework/zend-mvc": "2.*", "zendframework/zend-http": "2.*" }, "require-dev": { "phpunit/phpunit": "3.7.*" },}

Page 11: Build a custom (micro)framework with ZF2 Components (as building blocks)

LET'S GO TO SCHOOLWe have to learn our building blocks!

Page 12: Build a custom (micro)framework with ZF2 Components (as building blocks)

THE EVENT MANAGER$eventManager = new Zend\EventManager\EventManager();

// Trigger an eventpublic function trigger($event, $target = null, $argv = null);

// Attach a listener to an eventpublic function attach($event, $callback, $priority);

There are many other methods (detach, triggerUntil, etc...)

See it on Github

Page 13: Build a custom (micro)framework with ZF2 Components (as building blocks)

HOW EVENT MANAGER WORKS?public function testEventManager(){ $ev = new EventManager();

$isCalled = false; $ev->attach('an-event', function($event) use (&$isCalled) { $isCalled = true; });

$ev->trigger("an-event");

$this->assertTrue($isCalled);}

Page 14: Build a custom (micro)framework with ZF2 Components (as building blocks)

THE SERVICE LOCATOR$serviceManager = new Zend\ServiceManager\ServiceManager();

"services" => [ "invokables" => [ "Your\\Name\\Comp" => "Your\\Name\\Comp", ], "factories" => [ "Your\\Name\\Service" => "Your\\Name\\ServiceFactory", ], "aliases" => [ "your.alias" => "Your\\Name\\Service", ]]

See it on Github

Page 15: Build a custom (micro)framework with ZF2 Components (as building blocks)

CONFIGURE S.L. FROM ARRAY$serviceManager = new Zend\ServiceManager\ServiceManager();

$config = new Zend\ServiceManager\Config([ "invokables" => [...], "factories" => [...], "abstract_factories" => [...], "aliases" => [...]])$config->configureServiceManager($serviceManager);

Page 16: Build a custom (micro)framework with ZF2 Components (as building blocks)

HOW SERVICE MANAGER WORKS?public function testServiceManager(){ $sm = new ServiceManager(); $this->config->configureServiceManager($sm);

$this->assertTrue($sm->has("example"));

$this->assertSame( $sm->get("example"), $sm->get("example") );}

Page 17: Build a custom (micro)framework with ZF2 Components (as building blocks)

THE ROUTER$rtr = Zend\Mvc\Router\Http\TreeRouteStack::factory(array $config);

"routes" => [ "routeName" => [ "type" => "Literal", "options" => [ "route" => "/path", "defaults" => [ "controller" => "ZF\\Name", "action" = > "actionName", ] ], "may_terminate" => true, ]]

$routeMatch = $rtr->match(Request $request);

Page 18: Build a custom (micro)framework with ZF2 Components (as building blocks)

HOW ROUTER WORKS?public function testRouteMatch(){ $router = TreeRouteStack::factory($this->config);

$request = new Request("/path");

$routeMatch = $router->match($request);

$controllerName = $routerMatch->getParam("controller"); $actionName = $routeMatch->getParam("action");

$this->assertEquals("ZF\\Name", $controllerName)) $this->assertEquals("actionName", $actionName))}

Page 19: Build a custom (micro)framework with ZF2 Components (as building blocks)

REQUEST//$request = new \Zend\Http\PhpEnvironment\Request();

Page 20: Build a custom (micro)framework with ZF2 Components (as building blocks)

RESPONSE$response = new \Zend\Http\PhpEnvironment\Response();

$response->addHeaders([...]);$response->setContent("...");$response->send();

Page 21: Build a custom (micro)framework with ZF2 Components (as building blocks)

HYDRATORSpublic function extract($object);public function hydrate(array $data, $object);

$classMethods = new \Zend\Stdlib\Hydrator\ClassMethods();$objectProp = new \Zend\Stdlib\Hydratror\ObjectProperty();$array = new \Zend\Stdlib\Hydratror\ArraySerializable();

Page 22: Build a custom (micro)framework with ZF2 Components (as building blocks)

LET'S START!

Page 23: Build a custom (micro)framework with ZF2 Components (as building blocks)

EVENTS!

Page 24: Build a custom (micro)framework with ZF2 Components (as building blocks)

THE FRAMEWORK EVENTS LISTBEGIN

The loop begins

ROUTEParse the actual route in order to found a dispatchable action

PRE.FETCHBefore dispatch the action

EXECUTEDispatch all actions

RENDERRender your data

FINISHThe loop ends

Page 25: Build a custom (micro)framework with ZF2 Components (as building blocks)

ON ERRORS?BEGIN

ROUTE

PRE.FETCH

404/500/HALT

RENDER

FINISH

Page 26: Build a custom (micro)framework with ZF2 Components (as building blocks)

TEST IT!public function testBaseAppFlow(){ $request = RequestFactory::createRequest("/valid-url", "GET", []);

$app = new App();

$app->setRequest($request); $app->setServiceManager(new Zend\ServiceManager\ServiceManager()); $app->setEventManager(new Zend\EventManager\EventManager());

$response = $app->run()->response();

$this->assertEquals(404, $response->getStatusCode());}

F

FAIL (1 tests, 1 assertions)

Page 27: Build a custom (micro)framework with ZF2 Components (as building blocks)

REQUESTpublic function setRequest(Request $request){ $this->request = $request;}

public function request(){ if (!$this->request) { $this->request = new Request(); }

return $this->request;}

Page 28: Build a custom (micro)framework with ZF2 Components (as building blocks)

RESPONSEpublic function setResponse(Response $response){ $this->response = $response;}

public function response(){ if (!$this->response) { $this->response = new Response(); }

return $this->response;}

Page 29: Build a custom (micro)framework with ZF2 Components (as building blocks)

EVENT MANAGERpublic function setEventManager($eventManager){ $this->eventManager = $eventManager;}

public function events(){ return $this->eventManager;}

Page 30: Build a custom (micro)framework with ZF2 Components (as building blocks)

EVENT TRIGGER HELPERpublic function trigger($name, array $params = []){ $event = new Event();

$event->setTarget($this); $event->setParams($params);

return $this->events()->trigger($name, $event);}

Page 31: Build a custom (micro)framework with ZF2 Components (as building blocks)

SERVICE MANAGERpublic function setServiceManager($serviceManager){ $this->serviceManager = $serviceManager;}

public function services(){ return $this->serviceManager;}

Page 32: Build a custom (micro)framework with ZF2 Components (as building blocks)

ENTRY POINT!public function run(){ $this->trigger("begin");

$eventsList = $this->dispatchUserActionRelatedEvents();

$this->trigger("render", ["data" => $eventsList]);

$this->trigger("finish");

return $this;}

Page 33: Build a custom (micro)framework with ZF2 Components (as building blocks)

HEY, CAN YOU DO IT?protected function dispatchUserActionRelatedEvents(){ try { $eventsList = $this->dispatchUserRequest(); } catch (HaltException $e) { $eventsList = $this->trigger("halt"); } catch (PageNotFoundException $e) { $this->response()->setStatusCode(404); $eventsList = $this->trigger("404"); } catch (\Exception $e) { $this->response()->setStatusCode(500); $eventsList = $this->trigger("500"); }

return $eventsList;}

Page 34: Build a custom (micro)framework with ZF2 Components (as building blocks)

JUST TRY IT!protected function dispatchUserRequest(){ $evList = $this->trigger("route", ["request" => $this->request()]); $routeMatch = $evList->last();

if (null === $routeMatch) { throw new PageNotFoundException("Page not found!"); }

$this->response()->setStatusCode(200);

$this->trigger("pre.fetch", ["routeMatch" => $routeMatch]); $controllers = $this->events()->trigger("execute", $routeMatch);

return $controllers;}

Why the "execute" trigger is different?

Page 35: Build a custom (micro)framework with ZF2 Components (as building blocks)

NOW IT WORKS!public function testBaseAppFlow(){ $request = RequestFactory::createRequest("/a-page", "GET");

$app = new App();

$app->setRequest($request); $app->setServiceManager(new Zend\ServiceManager\ServiceManager()); $app->setEventManager(new Zend\EventManager\EventManager());

$response = $app->run()->response();

$this->assertEquals(404, $response->getStatusCode());}

.

OK (1 tests, 1 assertions)

See it on Github Gist

Page 36: Build a custom (micro)framework with ZF2 Components (as building blocks)

EASY TO MOCK!public function testOkResponseOnRouteMatch(){ $app = new App(); $eventManager = new Zend\EventManager\EventManager(); $eventmanager->attach("route", function() { return new RouteMatch([]) });

$app->setEventManager($eventManager); $app->setServiceManager(new Zend\ServiceManager\ServiceManager());

$response = $app->run()->response();

$this->assertEquals(200, $response->getStatusCode());}

..

OK (2 tests, 2 assertions)

Page 37: Build a custom (micro)framework with ZF2 Components (as building blocks)

WE NEED A BOOTSTRAPPER!Or better, something that can prepare the event manager and

the service manager from a default configuration!

Page 38: Build a custom (micro)framework with ZF2 Components (as building blocks)

LINK DEFAULT LISTENERS"listeners" => [ "route" => [ ["route.listener", "onRouteEvent"], ], "renderer" => [ ["renderer.listener", "render"], ], "finish" => [ ["response.listener", "sendResponse"], ],]

Page 39: Build a custom (micro)framework with ZF2 Components (as building blocks)

FRAMEWORK BASE SERVICES CONFIG"aliases" => [ "route.listener" => "Listener\\RouteListener", "renderer.listener" => "Listener\\Renderer\\Json", "response.listener" => "Listener\\SendResponseListener",],

"factories" => [ "Listener\\RouteListener" => "Service\\RouteListenerFactory",],"invokables" => [ "Listener\\Renderer\\Json" => "Listener\\Renderer\\Json", "Listener\\SendResponseListener" => "Listener\\SendResponseListener",],

Page 40: Build a custom (micro)framework with ZF2 Components (as building blocks)

THE ROUTER!

Page 41: Build a custom (micro)framework with ZF2 Components (as building blocks)

FOLLOW ZF2 IDEAS!return [ "router" => [ "routes" => [ "home" => [ "type" => "Literal", "options" => [ "route" => "/", "defaults" => [ "controller" => "ZF\\Day\\Italy", "action" => "get2014Day" ] ], 'may_terminate' => true, ] ] ]];

Page 42: Build a custom (micro)framework with ZF2 Components (as building blocks)

ROUTE LISTENERclass RouteListener{ public function __construct($router) { $this->router = $router; }

public function onRouteEvent($event) { ... }}

//RouteListenerFactory$router = TreeRouteStack::factory($config["router"]);$routeListener = new RouteListener($router);

$this->attach("route", [$routeListener, "onRouteEvent"]);

Page 43: Build a custom (micro)framework with ZF2 Components (as building blocks)

ON ROUTE EVENTpublic function onRouteEvent($event){ $target = $event->getTarget(); $request = $event->getParam("request");

$match = $this->getRouter()->match($request); if ($match) { $act = $match->getParam("action"); $ctrl = $match->getParam("controller"); if ($target->services()->has($ctlr)) { $ctrl = $target->services()->get($ctlr); }

$target->events()->attach("execute", [$ctrl, $act]); } return $match;}

Page 44: Build a custom (micro)framework with ZF2 Components (as building blocks)

CONTROLLERS [ACTION LISTENERS]Controllers are POPO objects that returns serializable datanamespace ZF\Day;

class Italy{ public function get2014Day() { return [ "talks" => [ "first" => [ "title" => "Just a title..." ] ], ... ]; }}

Page 45: Build a custom (micro)framework with ZF2 Components (as building blocks)

BUT, POPOS ARE TOO SIMPLEWe want to use other services into actions!

public function theAction(){ //I need a service here! $myService = $this->services()->get("my.service");

//use it...}

But how to do that?

Page 46: Build a custom (micro)framework with ZF2 Components (as building blocks)

CONTROLLERS USESclass TheController{ use ServiceManager;

public function theAction() { $service = $this->services()->get("my.service");

... }}

Page 47: Build a custom (micro)framework with ZF2 Components (as building blocks)

TRAITStrait ServiceManager(){ private $serviceManager;

public function setServiceManager($serviceManager) { $this->serviceManager = $serviceManager; }

public function services() { return $this->serviceManager; }}

Page 48: Build a custom (micro)framework with ZF2 Components (as building blocks)

ZEND\STDLIB\HYDRATOR$data = [ "serviceManager" => $target->services(), "eventManager" => $target->events(), "response" => $target->response(), "request" => $target->request(),];

$hydrator = new \Zend\Stdlib\Hydrator\ClassMethods();$hydrator->hydrate($data, $controller);

Page 49: Build a custom (micro)framework with ZF2 Components (as building blocks)

AND ALSO CONTROLLERS AS SERVICES"services" => [ "invokables" => [ "ZF\\Day\\Italy" => "ZF\\Day\\Italy", ]]

"factories" => [ "ZF\\Day\\Italy" => function(ServiceLocatorInterface $sl) { $controller = new \ZF\Day\Italy(); $controller->setMyService($sl->get("my.service"));

return $controller; }, ...]

And all others ZF2 services opportunities

Page 50: Build a custom (micro)framework with ZF2 Components (as building blocks)

RENDERERS!Serialize your data

Page 51: Build a custom (micro)framework with ZF2 Components (as building blocks)

JSON RENDERERclass Json{ public function render(Event $event) { $target = $event->getTarget(); $dataPack = $event->getParam("data")->last()

$response = $target->response();

$response->addHeaders([ "Content-Type" => "application/json", ]);

$response->setContent(json_encode($dataPack)); }}

// App$this->events()->attach("render", [$renderer, "render"]);

Page 52: Build a custom (micro)framework with ZF2 Components (as building blocks)

SEND RESPONSE TO THE CLIENTIt's an event of course!

Page 53: Build a custom (micro)framework with ZF2 Components (as building blocks)

EMIT!class Emit{ public function send($event) { $target = $event->getTarget();

$target->response()->send(); }}

//App$this->events()->attach("finish", [$emitter, "send"]);

We can remove it or mock it out during testing!

Page 55: Build a custom (micro)framework with ZF2 Components (as building blocks)

AT LEAST 3 RESPONSIBILITIESBUT EFFECTIVELY MORE...

Configure the applicationPrepare the applicationRun the application

Single Responsibility Principle

Page 57: Build a custom (micro)framework with ZF2 Components (as building blocks)

OK, RECAP!

Page 58: Build a custom (micro)framework with ZF2 Components (as building blocks)

EVENTSANY IDEAS?

Page 59: Build a custom (micro)framework with ZF2 Components (as building blocks)

PERFORMANCE INSPECTION & TRACKINGSymfony2 Stopwatch component

class StopwatchListener public function start($event) { $executionName = get_class($event->getTarget()); $this->getStopwatch()->start($executionName); }

public function lap($event) { ... }

public function stop($event) { $executionName = get_class($event->getTarget()); $execution = $this->getStopwatch()->stop($executionName);

//send to DATADOG SERVICE, LOGGERS... }}

Symfony\Component\Stopwatch\Stopwatch

Page 60: Build a custom (micro)framework with ZF2 Components (as building blocks)

PERFORMANCE INSPECTION & TRACKING"listeners" => [ "begin" => [ ["listener.stopwatch", "start"], ], "pre.fetch" => [ ["listener.stopwatch", "lap"], ], "finish" => [ ["listener.stopwatch", "stop"], ],]

"services" => [ "aliases" => [ "listener.stopwatch" => "StopwatchListenerFactory" ]]

Page 61: Build a custom (micro)framework with ZF2 Components (as building blocks)

PERFORMANCE INSPECTION & TRACKING

Page 62: Build a custom (micro)framework with ZF2 Components (as building blocks)

PERFORMANCE INSPECTION & TRACKING

Page 63: Build a custom (micro)framework with ZF2 Components (as building blocks)

PERSONAL EVENTSpublic function confirmSubscriptionAction($routeMatchEvent){ $user = new User(); $user->setName(...); ... $entityManager->persist($user); $entityManager->flush();

$this->events()->trigger("subscriber.new", $user);}

"listeners" => [ "subscriber.new" => [ ["subscriber.handler", "notifyViaEmail"] ["stats.counter", "newSubscriber"] ]]

Page 64: Build a custom (micro)framework with ZF2 Components (as building blocks)

MAINTENANCE PAGES"listeners" => [ "route" => [ ["maintenance.listeners", "maintenancePage"] ], "execute" => [ ["maintenance.controller", "maintenanceAction"] ],]

class MaintenanceRouteListener{ public function maintenancePage(Event $event) { return new RouteMatch([]); }}

Don't use this, there are better ways

Page 65: Build a custom (micro)framework with ZF2 Components (as building blocks)

TRY IT!Fork it on Github

https://github.com/wdalmut/upcloo-web-framework

Page 66: Build a custom (micro)framework with ZF2 Components (as building blocks)

THANKS FOR LISTENINGALWAYS EVENTS?Author / Walter Dal Mut @walterdalmut