Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Silex: An implementation detail (tnphp)

Silex: An implementation detail (tnphp)

Igor Wiedler

November 08, 2013
Tweet

More Decks by Igor Wiedler

Other Decks in Programming

Transcript

  1. Scenario: Place bid on a running auction Given there is

    an auction for some "Glasses" And I am a registered user And I am on "/" When I follow "Login" And I fill in "email" with "[email protected]" And I fill in "password" with "password" And I press "Login" And I follow "Glasses" And I fill in "amount" with "10.00" And I select "USD" from "currency" And I press "Place Bid" Then I should see "Bid Accepted"
  2. Scenario: Place bid on a running auction Given there is

    a running auction And I am viewing the auction When I place a bid on the auction Then I should see my bid is accepted
  3. class BidRequest { public $auctionId; public $userId; public $amount; !

    public function __construct($auctionId, $userId, Money $amount) { $this->auctionId = $auctionId; $this->userId = $userId; $this->amount = $amount; } }
  4. $interactor = new BidInteractor(); ! $request = new BidRequest( $auctionId,

    $userId, new Money($amount) ); ! $response = $interactor($request);
  5. namespace Douche\Interactor; ! class Bid { public function __invoke(BidRequest $request)

    { $auction = $this->auctionRepo->find($request->auctionId); $user = $this->userRepo->find($request->userId); ! $converted = $this->converter->convert( $request->amount, $auction->getCurrency() ); ! $bid = new BidValue($converted, $request->amount); $auction->bid($user, $bid); return new BidResponse($bid); } }
  6. class Auction { public function getId(); public function getName(); public

    function getCurrency(); public function getEndingAt(); public function getHighestBid(); public function getHighestBidder(); public function isRunning(DateTime $now = null); ! public function bid(User $bidder, Bid $bid, DateTime $now = null); }
  7. public function bid(User $bidder, Bid $bid, DateTime $now = null)

    { if (!$this->isRunning($now)) { throw new AuctionClosedException(); } ! $highestBid = $this->getHighestBid(); ! if ($highestBid && $bid->getAmount() <= $highestBid->getAmount()) { throw new BidTooLowException(); } ! $this->bids[] = [$bidder, $bid]; }
  8. $app['resolver'] = $app->share( $app->extend('resolver', function ($resolver, $app) { $resolver =

    new ControllerResolver( $resolver, $app ); ! return $resolver; }) );
  9. $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { $view = $event->getControllerResult(); $request

    = $event->getRequest(); ! $controller = $request->attributes->get('controller'); $template = "$controller.html"; ! $body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); });
  10. {{> _header.html }} ! <h1>{{ auction.name }}</h1> ! <p>Ends: {{

    auction.endingAt | format_date }}</p> ! {{# auction.highestBid }} <p>Highest bid: {{ getAmount | format_money }}</p> {{/ auction.highestBid }} ! {{# auction.highestBidder }} <p>Highest bidder: {{ . }}</p> {{/ auction.highestBidder }}
  11. if ($request->attributes->has('success_handler')) { $fn = $request->attributes->get('success_handler'); ! $view = $fn($view,

    $request); if ($view instanceof Response) { $event->setResponse($view); return; } }
  12. $app->error(function (DoucheException $e, $code) use ($app) { $handlers = $app['request']

    ->attributes ->get('error_handlers', []); ! foreach ($handlers as $type => $handler) { if ($e instanceof $type) { return $handler( $e, $code, $app['request'] ); } } });
  13. namespace DoucheWeb; ! use Douche\Interactor\AuctionListResponse; use Douche\Interactor\AuctionViewRequest; use Douche\Interactor\UserLoginRequest; use

    Douche\Interactor\UserLoginResponse; use Douche\Interactor\AuctionViewResponse; use Douche\Interactor\BidRequest; use Douche\Exception\Exception as DoucheException; ! use Mustache\Silex\Provider\MustacheServiceProvider; ! use Silex\Application; use Silex\Provider\DoctrineServiceProvider; use Silex\Provider\MonologServiceProvider; use Silex\Provider\ServiceControllerServiceProvider; use Silex\Provider\SessionServiceProvider; use Silex\ExceptionListenerWrapper; ! use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\KernelEvents; ! use Money\Money; use Money\Currency; ! $app = new Application(); ! $app->register(new MonologServiceProvider()); $app->register(new DoctrineServiceProvider()); $app->register(new MustacheServiceProvider(), [ 'mustache.options' => [ 'helpers' => [ 'format_money' => function ($money) { return $money->getCurrency().' '.($money->getAmount() / 100); }, 'format_date' => function (\DateTime $date) { return $date->format("Y-m-d H:i:s"); }, ], ], ]); $app->register(new ServiceControllerServiceProvider()); $app->register(new SessionServiceProvider()); $app->register(new ServiceProvider()); ! $app->get('/', 'interactor.auction_list') ->value('controller', 'auction_list'); ! $app->get('/auction/{id}', 'interactor.auction_view') ->value('controller', 'auction_view') ->convert('request', function ($_, Request $request) { return new AuctionViewRequest($request->attributes->get('id')); }); ! $app->post('/auction/{id}/bids', 'interactor.bid') ->before(function (Request $request, Application $app) { if (!$request->getSession()->has('current_user')) { return $app->abort(401, 'Authenitcation Required'); } }) ->value('controller', 'bid') ->value('success_handler', function ($view, $request) { return new RedirectResponse("/auction/" . $request->attributes->get('id')); }) ->value('error_handlers', [ "Douche\Exception\BidTooLowException" => function ($e, $code, $request) { $request->getSession()->getFlashBag()->set('errors', [ 'The provided bid was too low.', ]); return new RedirectResponse("/auction/" . $request->attributes->get('id')); }, ]) ->convert('request', function ($_, Request $request) { return new BidRequest( $request->attributes->get('id'), $request->getSession()->get('current_user')->id, new Money((int) $request->request->get('amount') * 100, new Currency($request->request->get('currency'))) ); }); ! $app->post('/login', 'interactor.user_login') ->value('controller', 'login') ->value('success_handler', function ($view, $request) { $request->getSession()->set('current_user', $view->user); return new RedirectResponse("/"); }) ->value('error_handlers', [ "Douche\Exception\UserNotFoundException" => function ($e) { return [ 'errors' => ['Incorrect email provided.'], 'email' => $e->email, ]; }, "Douche\Exception\IncorrectPasswordException" => function ($e) { return [ 'errors' => ['Invalid credentials provided.'], 'email' => $e->email, ]; }, ]) ->convert('request', function ($_, Request $request) { return new UserLoginRequest($request->request->all()); }); ! $app->get('/login', function(Request $request, Application $app) { $view = [ 'errors' => [], ]; ! return $app['mustache']->render('login.html.mustache', $view); }); ! $app->get('/logout', function(Request $request, Application $app) { $request->getSession()->start(); $request->getSession()->invalidate(); return $app->redirect("/"); }); ! $app['resolver'] = $app->share($app->extend('resolver', function ($resolver, $app) { $resolver = new ControllerResolver($resolver, $app); ! return $resolver; })); ! // TODO change to ->error once fabpot/silex#705 is merged $app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::EXCEPTION, new ExceptionListenerWrapper($app, function (DoucheException $e, $code) use ($app) { $app['request']->attributes->set('failed', true); ! $errorHandlers = $app['request']->attributes->get('error_handlers', []); ! foreach ($errorHandlers as $type => $handler) { if ($e instanceof $type) { return $handler($e, $code, $app['request']); } } ! }), -8); ! return $dispatcher; })); ! // TODO change to ->on once fabpot/silex#705 is merged $app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { $view = $event->getControllerResult(); ! if (is_null($view) || is_string($view)) { return; } ! $request = $event->getRequest(); ! if (!$request->attributes->get('failed') && $request->attributes->has('success_handler')) { $handler = $request->attributes->get('success_handler'); ! $view = $handler($view, $request); if ($view instanceof Response) { $event->setResponse($view); return; } } ! $controller = $request->attributes->get('controller'); $template = "$controller.html"; ! $view = (object) $view; $view->current_user = $request->getSession()->get('current_user'); $view->form_errors = $request->getSession()->getFlashBag()->get('errors'); ! $body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); }); ! return $dispatcher; })); ! // TODO change to ->after once fabpot/silex#705 is merged $app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::RESPONSE, function () use ($app) { $app['douche.auction_repo']->save(); }); ! return $dispatcher; })); ! return $app; service providers routes listeners
  14. /** * @When /^I place a bid on the auction$/

    */ public function iPlaceABidOnTheAuction() { $this->auctionHelper->placeBid(1.0); }
  15. public function placeBid($amount) { $interactor = new BidInteractor( $this->getAuctionRepository() );

    ! $request = new BidRequest( $this->auction->getId(), $this->getCurrentUserId(), $amount ); ! $this->response = $interactor($request); }