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

Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Moving a high traffic ZF1 Enterprise Application to SF2 - Lessons learned

Hautelook is a large ecommerce application that is currently running a Zend Framework 1 backend. The next iteration of its API (used by desktop, mobile, as well as iPhone and Android native applications) is done with Symfony 2. This API is following the principles for hypermedia APIs. To that end, Hal+Json is the media-type we chose, and we implemented most of it using the FSC HateoasBundle. Another critical piece of Hal+Json APIs is documentation. To this end we have used NelmioApiDocBundle to automatically generate documentation for the API endpoints. The other critical piece of any application is performance for which we use XHProf with XHGui. In my talk I want to touch on all those aspects, show some of the lessons learned, how we solved some of the problems, and what is still unsolved.

Baldur Rensch

May 22, 2013
Tweet

More Decks by Baldur Rensch

Other Decks in Programming

Transcript

  1. What is ? Member only shopping site Private, limited- time

    sale events daily email invitation at 8am
  2. Some stats Alexa traffic rank for US: 847 More than

    12 million members (on average 20k new members per day) Up to 200 orders per minute Massive traffic spikes (remember, 8am) [1]
  3. [1] Some stats Alexa traffic rank for US: 847 More

    than 12 million members (on average 20k new members per day) Up to 200 orders per minute Massive traffic spikes (remember, 8am) daily
  4. class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() { $service

    = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } View Controller Service Model
  5. View Controller Service Model Call the service class V4_Controller_Cart extends

    Halo_Rest_ViewController { public function get() { $service = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }
  6. View Controller Service Model Prepare for response class V4_Controller_Cart extends

    Halo_Rest_ViewController { public function get() { $service = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; }
  7. class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() { $service

    = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); } View Controller Service Model
  8. class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() { $service

    = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } View Controller Service Model Input Validation public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); }
  9. class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() { $service

    = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } View Controller Service Model Call model public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); }
  10. class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() { $service

    = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } View Controller Service Model Prepare for response public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); }
  11. class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() { $service

    = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); } <?php class V4_Model_CartItems { public function itemsForMember($member_id) { $db = Zend_Registry::get('db'); $q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...) EOT; $result = $db->fetchAll($q, $member_id); return $result; } } View Controller Service Model Run some SQL
  12. View class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() {

    $service = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); } <?php class V4_Model_CartItems { public function itemsForMember($member_id) { $db = Zend_Registry::get('db'); $q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...) EOT; $result = $db->fetchAll($q, $member_id); return $result; } } protected function modifyData(Halo_Response $service_response) { $member_id = $this->member_id; $data = $service_response->getData(); $items = $data['items']; unset($data['items']); $cart = new Hal_Resource("/v4/members/{$member_id}/cart", $data); $cart->setLink(new Hal_Link("/v4/members/{$member_id}/checkout", 'http://hautelook.com/rels/checkout')); foreach ($items as $item) { $event_id = $item['sku']['event_id']; $color = $item['sku']['color']; $expires = new DateTime($item['expires_at']); $cart_data = array( 'quantity' => (int) $item['quantity'], (...) ); $sku_data = array( 'event_id' => $event_id, (...) ); if (isset($item['style']['images'][strtolower($color)])) { $images = $item['style']['images'][strtolower($color)]['angles']; } $image_link = $images[0]['zoom']; $style = new Hal_Resource("/v4/events/{$event_id}/styles/{$item['style_num']}", $style_data); $r = new Hal_Resource("/v4/members/{$member_id}/cart/{$item['cart_id']}", $cart_data); $style->setEmbedded('http://hautelook.com/rels/sku', $sku); $style->setLink(new Hal_Link($image_link, 'http://hautelook.com/rels/images/style/zoom')); $r->setEmbedded('http://hautelook.com/rels/styles', $style); $cart->setEmbedded('http://hautelook.com/rels/cart-items', $r); } $service_response->success($cart->toArray()); } Controller Service Model
  13. View class V4_Controller_Cart extends Halo_Rest_ViewController { public function get() {

    $service = new V4_Service_Cart; ! ! ! ! $params = $this->getRequest()->getParams(); $response = $service->resource($params); ! ! ! ! if ($response->getSuccess()) { $this->getResponse()->setHttpResponseCode(200); } else { $this->getResponse()->setHttpResponseCode(400); } $this->view->member_id = (int) $params['member_id']; $this->service_response = $response; } public function resource(array $data) { $this->checkMemberId($data['member_id']); $input = new Zend_Filter_Input( array( '*' => 'StringTrim' ), array( 'member_id' => array( 'Int', 'presence' => 'required' ), ), $data ); if (!$input->isValid()) { return $this->response(false, $input); } $cart_items_model = $this->getComponent('V4_Model_CartItems'); $items = $cart_items_model->itemsForMember($input->member_id); foreach ($items as $k => $item) { $items[$k]['status'] = $this->statusMap($item['cart_item_status']); $styles_service = $this->getComponent('V4_Service_Styles'); $style = $styles_service->resource(array( 'event_id' => $item['event_id'], 'style_num' => $item['style_num'], )); $items[$k]['style'] = $style->getData(); } $result = array( 'gift_checkout' => $gift_checkout, 'items' => $items, ); return $this->response(true, $result); } <?php class V4_Model_CartItems { public function itemsForMember($member_id) { $db = Zend_Registry::get('db'); $q = <<<EOT SELECT cart_id, cart_items.event_id, cart_items.sku, quantity, cart_item_status, expires_at, (...) EOT; $result = $db->fetchAll($q, $member_id); return $result; } } protected function modifyData(Halo_Response $service_response) { $member_id = $this->member_id; $data = $service_response->getData(); $items = $data['items']; unset($data['items']); $cart = new Hal_Resource("/v4/members/{$member_id}/cart", $data); $cart->setLink(new Hal_Link("/v4/members/{$member_id}/checkout", 'http://hautelook.com/rels/checkout')); foreach ($items as $item) { $event_id = $item['sku']['event_id']; $color = $item['sku']['color']; $expires = new DateTime($item['expires_at']); $cart_data = array( 'quantity' => (int) $item['quantity'], (...) ); $sku_data = array( 'event_id' => $event_id, (...) ); if (isset($item['style']['images'][strtolower($color)])) { $images = $item['style']['images'][strtolower($color)]['angles']; } $image_link = $images[0]['zoom']; $style = new Hal_Resource("/v4/events/{$event_id}/styles/{$item['style_num']}", $style_data); $r = new Hal_Resource("/v4/members/{$member_id}/cart/{$item['cart_id']}", $cart_data); $style->setEmbedded('http://hautelook.com/rels/sku', $sku); $style->setLink(new Hal_Link($image_link, 'http://hautelook.com/rels/images/style/zoom')); $r->setEmbedded('http://hautelook.com/rels/styles', $style); $cart->setEmbedded('http://hautelook.com/rels/cart-items', $r); } $service_response->success($cart->toArray()); } Controller Service Model Convert array results to HAL+Json, yuck!
  14. Issues This is fine when you have 5 end points

    and simple responses. Lots of boiler plate code Zend Framework 1 did not scale very well. We constantly had to overwrite parts of the framework.
  15. Moving from Imperative to Declarative Programming [4,5] Imperative Declarative “In

    computer science, imperative programming is a programming paradigm that describes computation in terms of statements that change a program state.” “In computer science, declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow.”
  16. [4,5] Imperative Declarative “In computer science, imperative programming is a

    programming paradigm that describes computation in terms of statements that change a program state.” “In computer science, declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow.” Moving from Imperative to Declarative Programming
  17. Advantages: Symfony allows for way more declarative programming which allows

    us to write less code. Allows us to extend way easier. And it’s actually fun. Community is great.
  18. Bundles we use Friends of Symphony: RestBundle Nelmio: ApiDocBundle, SolariumBundle

    JMS: SerializerBundle Football Social Club: HateoasBundle Hautelook: GearmanBundle
  19. /** * This function returns a member's cart * *

    @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */ public function getCartAction($memberId) { $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member); $response = $this->get('serializer')->serialize($cart, 'json'); $response = new Response($response); $response->headers->set('Content-Type', 'application/json'); $response->setETag(md5($response->getContent())); return $response; } Controller Service Model/ View
  20. Controller Service Model/ View Routing, Input Validation /** * This

    function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */ public function getCartAction($memberId) { $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member); $response = $this->get('serializer')->serialize($cart, 'json'); $response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent())); return $response; }
  21. Controller Service Model/ View Documentation /** * This function returns

    a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */ public function getCartAction($memberId) { $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member); $response = $this->get('serializer')->serialize($cart, 'json'); $response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent())); return $response; }
  22. Controller Service Model/ View Call Service to get data /**

    * This function returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */ public function getCartAction($memberId) { $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member); $response = $this->get('serializer')->serialize($cart, 'json'); $response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent())); return $response; }
  23. Controller Service Model/ View Create response /** * This function

    returns a member's cart * * @Route("/members/{memberId}/cart", requirements = { "memberId" = "\d+" } ) * @Method({"GET"}) * * @ApiDoc( * resource=true, * description="Retrieve a member's cart", * statusCodes={ * 200="Returned when successful", * 400="Returned when the request is not well-formed", * 403="Returned when the user is not authorized or not owner or have no admin access", * 404="Returned when the member is not found" * } * ) * * @param int $memberId * * @return Response */ public function getCartAction($memberId) { $member = $this->get('hautelook.model.member')->getMemberAndValidate($memberId); $cart = $this->get('hautelook.model.cart')->getCart($member); $response = $this->get('serializer')->serialize($cart, 'json'); $response = new Response($response); $response->headers->set('Content-Type', 'application/hal+json'); $response->setETag(md5($response->getContent())); return $response; }
  24. public function getCart(Members $member) { $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId()); $cartItemArray

    = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray); return $cart; } Controller Service Model/ View
  25. Controller Service Model/ View Get entities from database public function

    getCart(Members $member) { $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId()); $cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray); return $cart; }
  26. Controller Service Model/ View Convert to view model Get entities

    from database public function getCart(Members $member) { $cartItems = $this->doctrine->getRepository("HautelookApiBundle:CartItems") ->getCartItems($member->getMemberId()); $cartItemArray = array(); foreach ($cartItems as $cartItem) { $cartItemArray []= new CartItem($cartItem); } $cart = new CartModel($member, $cartItemArray); return $cart; }
  27. use JMS\Serializer\Annotation as JMS; use FSC\HateoasBundle\Annotation as Rest; /** *

    @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */ class Cart Controller Service Model/ View
  28. use JMS\Serializer\Annotation as JMS; use FSC\HateoasBundle\Annotation as Rest; /** *

    @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */ class Cart Link Controller Service Model/ View
  29. use JMS\Serializer\Annotation as JMS; use FSC\HateoasBundle\Annotation as Rest; /** *

    @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */ class Cart Embedded Controller Service Model/ View
  30. URI Templates are sexy There is a RFC for it:

    RFC-6570 [6, 7, 12] /demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*} 1 And are the magic that make Hateoas possible 1
  31. URI Templates are sexy /demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*} There is a RFC for

    it: RFC-6570 There is a bundle for it™: TemplatedURIBundle [6, 7, 12] 1 And are the magic that make Hateoas possible 1
  32. URI Templates are sexy /demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*} $templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page'

    => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ) ); There is a RFC for it: RFC-6570 There is a bundle for it™: TemplatedURIBundle [6, 7, 12] 1 And are the magic that make Hateoas possible 1
  33. URI Templates are sexy /demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*} $templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page'

    => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ) ); There is a RFC for it: RFC-6570 There is a bundle for it™: TemplatedURIBundle It even integrates with the HateoasBundle: [6, 7, 12] 1 And are the magic that make Hateoas possible 1 hautelook_style_image_resizable: pattern: /resizer/{width}x{height}/products/{styleNum}/{size}/{imageId}.jpg defaults: width: "{width}" height: "{height}"
  34. URI Templates are sexy /demo?{&page}{&sort%5B%5D*}{&filter%5B%5D*} /** * @Rest\Relation("http://hautelook.com/rels/image/resizable", * href

    = @Rest\Route("hautelook_style_image_resizable", * parameters = { "styleNum": ".solrDocument.styleNum", "imageId": ".firstImageId" }, * options = { "router": "templated" } * ), * excludeIf = { ".firstImageId": null }, * attributes = { "templated": true } * ) */ $templateLink = $this->get('hautelook.router.template')->generate('hautelook_demo_route', array( 'page' => '{page}', 'sort' => array('{sort}'), 'filter' => array('{filter}'), ) ); There is a RFC for it: RFC-6570 There is a bundle for it™: TemplatedURIBundle It even integrates with the HateoasBundle: [6, 7, 12] 1 And are the magic that make Hateoas possible 1
  35. Measuring performance with declarative programming use JMS\Serializer\Annotation as JMS; use

    FSC\HateoasBundle\Annotation as Rest; /** * @author Baldur Rensch <[email protected]> * * @Rest\Relation("self", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ) * ) * @Rest\Relation("http://hautelook.com/rels/cart/item", * href = @Rest\Route("hautelook_api_cart_getcart", * parameters = { "memberId": ".member.memberId" } * ), * embed = @Rest\Content( * property = ".cartItems" * ) * ) */ class Cart Why its difficult [8, 9, 10]
  36. Measuring performance with declarative programming Why its difficult XHProf to

    the rescue Hierarchical function-level profiler [8, 9, 10]
  37. Measuring performance with declarative programming Why its difficult XHProf to

    the rescue Hierarchical function-level profiler PHP Extension [8, 9, 10]
  38. Measuring performance with declarative programming Why its difficult XHProf to

    the rescue Hierarchical function-level profiler PHP Extension Written by Facebook [8, 9, 10]
  39. Measuring performance with declarative programming Why its difficult XHProf to

    the rescue Hierarchical function-level profiler PHP Extension Written by Facebook XHGui on top of XHProf [8, 9, 10]
  40. Measuring performance with declarative programming Why its difficult XHProf to

    the rescue Hierarchical function-level profiler PHP Extension Written by Facebook XHGui on top of XHProf Uses a shared database backend [8, 9, 10]
  41. Measuring performance with declarative programming Why its difficult XHProf to

    the rescue Hierarchical function-level profiler PHP Extension Written by Facebook XHGui on top of XHProf Uses a shared database backend There is a bundle for that™ as well! [8, 9, 10]
  42. Lessons learned Large scale Symfony deployments are not that common

    A lot of modules that larger applications need don’t exist
  43. Lessons learned Large scale Symfony deployments are not that common

    A lot of modules that larger applications need don’t exist Example: Session storage in multiple storage layers such as: Memcached and Database
  44. Lessons learned Large scale Symfony deployments are not that common

    A lot of modules that larger applications need don’t exist Example: Session storage in multiple storage layers such as: Memcached and Database There is a bundle for that™ now as well: SessionStorageHandlerChainBundle [11]
  45. Lessons learned Large scale Symfony deployments are not that common

    A lot of modules that larger applications need don’t exist Need more documentation / community around enterprise level Symfony development
  46. Lessons learned Large scale Symfony deployments are not that common

    A lot of modules that larger applications need don’t exist Need more documentation / community around enterprise level Symfony development Our Developers love developing in Symfony
  47. Questions? Let’s get in touch for feedback, questions, discussion Leave

    feedback at: https://joind.in/8676 Connect on Twitter or Github.
  48. Sources [1] http://www.alexa.com/siteinfo/hautelook.com [2] http://fc08.deviantart.net/fs50/f/2009/280/3/c/And_Then_There_Was_Light_by_GTwerks.jpg [3] http://stateless.co/hal_specification.html [4] https://en.wikipedia.org/wiki/Declarative_programming [5]

    https://en.wikipedia.org/wiki/Imperative_programming [6] https://tools.ietf.org/html/rfc6570 [7] https://github.com/hautelook/TemplatedUriBundle [8] https://github.com/facebook/xhprof [9] https://github.com/preinheimer/xhprof [10] https://github.com/jonaswouters/XhprofBundle [11] https://github.com/hautelook/SessionStorageHandlerChainBundle [12] https://github.com/fxa/uritemplate-js [13] https://play.google.com/store/apps/details?id=com.hautelook.mcom&hl=en [14] https://itunes.apple.com/us/app/hautelook/id390783984?mt=8