Symfony2, Backbone.js & socket.io - SfLive Paris 2k13 - Wisembly

17,558 views

Published on

Wisembly experience sharing on building one-page js app w/ Backbone.js over Symfony2 REST API and socket.io push server.

Symfony2, Backbone.js & socket.io - SfLive Paris 2k13 - Wisembly

  1. 1. Symfony2, Backbone.js & socket.io Symfony live Paris 2013 @guillaumepotier guillaume@wisembly.com
  2. 2. app.wisembly.com/sfliveapp.wisembly.com/sflive
  3. 3. 2011, Septemberapp.wisembly.com/sflive
  4. 4. MySQL PHPapp.wisembly.com/sflive
  5. 5. Twig Symfony 2 Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  6. 6. Twig js jQuery Assetic Twig Symfony 2 Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  7. 7. Backbone.js Underscore.js Twig js jQuery Assetic Twig Symfony 2 Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  8. 8. Backbone.js Underscore.js Twig js jQuery H ! UC Assetic O M Twig TO Symfony 2 Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  9. 9. Client Serverapp.wisembly.com/sflive
  10. 10. Client Twig REST Symfony 2 Server Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  11. 11. Backbone.js Underscore.js Client jQuery HTML Twig REST Symfony 2 Server Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  12. 12. Backbone.js Underscore.js Client jQuery ! HTML ING L L PO NG L O Twig REST Symfony 2 Server Doctrine 2 MySQL PHPapp.wisembly.com/sflive
  13. 13. Users want fast & smooth SaaS appsapp.wisembly.com/sflive
  14. 14. Users want fast & smooth SaaS apps Users want multiplateform SaaS appsapp.wisembly.com/sflive
  15. 15. Users want fast & smooth SaaS apps Users want multiplateform SaaS apps Users want dynamic & interactive SaaS appsapp.wisembly.com/sflive
  16. 16. Users want fast & smooth SaaS apps Users want multiplateform SaaS apps Users want dynamic & interactive SaaS apps You should do so!app.wisembly.com/sflive
  17. 17. Users want fast & smooth SaaS apps Users want multiplateform SaaS apps Users want dynamic & interactive SaaS apps W ? You shouldO so! do T H B Uapp.wisembly.com/sflive
  18. 18. app.wisembly.com/sflive
  19. 19. ?app.wisembly.com/sflive
  20. 20. E L L H ! N O ?app.wisembly.com/sflive
  21. 21. app.wisembly.com/sflive
  22. 22. Nowadaysapp.wisembly.com/sflive
  23. 23. app.wisembly.com/sflive
  24. 24. small / lightweight / stableapp.wisembly.com/sflive
  25. 25. small / lightweight / stable easy to learn, easy to extendapp.wisembly.com/sflive
  26. 26. small / lightweight / stable easy to learn, easy to extend great resources: layoutManager relationalapp.wisembly.com/sflive
  27. 27. Models Modelsapp.wisembly.com/sflive
  28. 28. Models Models Collections Repositoriesapp.wisembly.com/sflive
  29. 29. Models Models Collections Repositories Views Controllersapp.wisembly.com/sflive
  30. 30. Models Models Collections Repositories Views Controllers Templates Viewsapp.wisembly.com/sflive
  31. 31. Models Models Collections Repositories Views Controllers Templates Views Routing Routingapp.wisembly.com/sflive
  32. 32. Models Models Collections Repositories Views Controllers S H ViewsP U Templates + RE ST Routing Routingapp.wisembly.com/sflive
  33. 33. FOSJsRouting BazingaExposeTranslation JMSSerializer FOSRestBundleapp.wisembly.com/sflive
  34. 34. FOSJsRouting BazingaExposeTranslation JMSSerializer FOSRestBundleapp.wisembly.com/sflive
  35. 35. 1 - MAKE AN APIapp.wisembly.com/sflive
  36. 36. MUST READ http://fr.slideshare.net/nachomartin/symfony- javascript-combining-the-best-of-two-worldsapp.wisembly.com/sflive
  37. 37. ar tin nacm @ Books = new Backbone.collection(); Books.url = ‘/books’;app.wisembly.com/sflive
  38. 38. ar tin nacm @ Books = new Backbone.collection(); Books.url = ‘/books’; Books.fetch(); GET /booksapp.wisembly.com/sflive
  39. 39. ar tin nacm @ events: { ‘click .mybutton’:‘doStuffAndSave’ } doStuffAndSave: function() { var book = Books.get(3); book.stuff(); book.save(); }app.wisembly.com/sflive
  40. 40. ar tin nacm @ events: { ‘click .mybutton’:‘doStuffAndSave’ } doStuffAndSave: function() { var book = Books.get(3); book.stuff(); book.save(); } PUT /books/3app.wisembly.com/sflive
  41. 41. /** * @var integer $id * * @ORMColumn(name="id", type="integer") * @ORMId [ { * @ORMGeneratedValue(strategy="AUTO") “id”:1, */ “name”:”guillaume”, private $id; “phone”: “0611010011” /** ... * @var string $name * }, * @ORMColumn(name="name", type="string", {...}, length=50, nullable=true) ] */ private $name; /** * @var string $phone * * @ORMColumn(name="phone", type="string", length=20, nullable=true) */ private $phone; ...app.wisembly.com/sflive
  42. 42. II - MAKE A GOOD REST APIapp.wisembly.com/sflive
  43. 43. MUST READ 2 http://williamdurand.fr/2012/08/02/rest-apis- with-symfony2-the-right-wayapp.wisembly.com/sflive
  44. 44. JMSSerializer orapp.wisembly.com/sflive
  45. 45. JMSSerializer or class User implements UserInterface, EquatableInterface, ApiAbleInterface { }app.wisembly.com/sflive
  46. 46. JMSSerializer or class User implements UserInterface, EquatableInterface, ApiAbleInterface { public function toArray() { return [ id => $this->getId(), name => $this->getName(), email => $this->getEmail(), ]; } }app.wisembly.com/sflive
  47. 47. FOSRestBundle orapp.wisembly.com/sflive
  48. 48. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  49. 49. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  50. 50. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  51. 51. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  52. 52. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  53. 53. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  54. 54. FOSRestBundle or /** * @Route("{keyword}/quote/{id}", name="api3_quote_get", options={"expose"=true}) * @Method({"GET", "OPTIONS"}) */ public function getQuote(EventInterface $event, $id) { try { $quote = $this->get(api3.quote)->get($id); return $this->container->get(api3.response)->newSuccessResponse($quote- >toArray(), 200); } catch (NoResultException $e) { return $this->container->get(api3.response)->newErrorResponse(No quote found, ErrorCode::NO_QUOTE, 404); } }app.wisembly.com/sflive
  55. 55. Controller Entity Service API Service Entity EntityRepositoryapp.wisembly.com/sflive
  56. 56. Use Validator and Form ouac @c <?php // ... private function processForm(User $user) { $form = $this->createForm(new UserType(), $user); $form->bind($this->getRequest()); if ($form->isValid()) { $this->doYourStuff(); return $user; } // ... }app.wisembly.com/sflive
  57. 57. /** * @Route("event/{keyword}/poll/{id}", name="api3_poll_edit", requirements={"id" = "d+"}, options={"expose"=true}) * @Method({"POST", "PUT", "OPTIONS"}) */ public function editPollAction(EventInterface $event, Request $request, $id) { try { $poll = $this->get(api3.poll)->get($event, $id); $poll = $this->get(api3.poll)->edit($poll, $request); return $this->container->get(api3.response)->newSuccessResponse($poll- >toArray(), 201); } catch (NoResultException $e) { // ... } catch (AccessDeniedException $e) { // ... } catch (Exception $e) { // ... } }app.wisembly.com/sflive
  58. 58. /** * @Route("event/{keyword}/poll/{id}", name="api3_poll_edit", requirements={"id" = "d+"}, options={"expose"=true}) * @Method({"POST", "PUT", "OPTIONS"}) */ public function editPollAction(EventInterface $event, Request $request, $id) { try { $poll = $this->get(api3.poll)->get($event, $id); $poll = $this->get(api3.poll)->edit($poll, $request); return $this->container->get(api3.response)->newSuccessResponse($poll- >toArray(), 201); } catch (NoResultException $e) { // ... } catch (AccessDeniedException $e) { // ... } catch (Exception $e) { // ... } }app.wisembly.com/sflive
  59. 59. /** * @Route("event/{keyword}/poll/{id}", name="api3_poll_edit", requirements={"id" = "d+"}, options={"expose"=true}) * @Method({"POST", "PUT", "OPTIONS"}) */ public function editPollAction(EventInterface $event, Request $request, $id) { try { $poll = $this->get(api3.poll)->get($event, $id); $poll = $this->get(api3.poll)->edit($poll, $request); return $this->container->get(api3.response)->newSuccessResponse($poll- >toArray(), 201); } catch (NoResultException $e) { // ... } catch (AccessDeniedException $e) { // ... } catch (Exception $e) { // ... } }app.wisembly.com/sflive
  60. 60. /** * @Route("event/{keyword}/poll/{id}", name="api3_poll_edit", requirements={"id" = "d+"}, options={"expose"=true}) * @Method({"POST", "PUT", "OPTIONS"}) */ public function editPollAction(EventInterface $event, Request $request, $id) { try { $poll = $this->get(api3.poll)->get($event, $id); $poll = $this->get(api3.poll)->edit($poll, $request); return $this->container->get(api3.response)->newSuccessResponse($poll- >toArray(), 201); } catch (NoResultException $e) { // ... } catch (AccessDeniedException $e) { // ... } catch (Exception $e) { // ... } }app.wisembly.com/sflive
  61. 61. /** * @Route("event/{keyword}/poll/{id}", name="api3_poll_edit", requirements={"id" = "d+"}, options={"expose"=true}) * @Method({"POST", "PUT", "OPTIONS"}) */ public function editPollAction(EventInterface $event, Request $request, $id) { try { $poll = $this->get(api3.poll)->get($event, $id); $poll = $this->get(api3.poll)->edit($poll, $request); return $this->container->get(api3.response)->newSuccessResponse($poll- >toArray(), 201); } catch (NoResultException $e) { // ... } catch (AccessDeniedException $e) { // ... } catch (Exception $e) { // ... } }app.wisembly.com/sflive
  62. 62. app.wisembly.com/sflive
  63. 63. app.wisembly.com/sflive
  64. 64. FOSJsRouting BazingaExposeTranslation JMSSerializer FOSRestBundleapp.wisembly.com/sflive
  65. 65. ar tin nacm @ events: { ‘bookUpdated’:‘update’, ‘bookCreated’: ‘create’, ‘bookDeleted’:‘delete’, } update: function(websocketData) { doStuff(websocketData); }, create: function(websocketData) { doOtherStuff(websocketData); }, delete: function(websocketData) { stillDoOtherStuff(websocketData); }app.wisembly.com/sflive
  66. 66. ar tin nacm @ events: { ‘bookUpdated’:‘update’, ‘bookCreated’: ‘create’, ‘bookDeleted’:‘delete’, } update: function(websocketData) { doStuff(websocketData); }, create: function(websocketData) { doOtherStuff(websocketData); }, delete: function(websocketData) { stillDoOtherStuff(websocketData); } REAL TIME FULL EVENT BASEDapp.wisembly.com/sflive
  67. 67. websocketData? Who sends what? Which port, which protocol?app.wisembly.com/sflive
  68. 68. app.wisembly.com/sflive
  69. 69. FOSJsRouting BazingaExposeTranslation JMSSerializer FOSRestBundleapp.wisembly.com/sflive
  70. 70. Authenticate user against PUSH serverapp.wisembly.com/sflive
  71. 71. Authenticate user against PUSH server sessionToken domainapp.wisembly.com/sflive
  72. 72. Authenticate user against PUSH server sessionToken domain REST sessionToken domainapp.wisembly.com/sflive
  73. 73. Authenticate user against PUSH server sessionToken domain rights REST sessionToken domainapp.wisembly.com/sflive
  74. 74. Authenticate user against PUSH server Authenticated! rights REST sessionToken domainapp.wisembly.com/sflive
  75. 75. PUSH: The «Classic» way rightsapp.wisembly.com/sflive
  76. 76. PUSH: The «Classic» way REST sessionToken domain rightsapp.wisembly.com/sflive
  77. 77. PUSH: The «Classic» way REST sessionToken data domain rightsapp.wisembly.com/sflive
  78. 78. PUSH: The «Classic» way REST sessionToken data domain rightsapp.wisembly.com/sflive
  79. 79. PUSH: The «Classic» way websocketData REST sessionToken data domain rightsapp.wisembly.com/sflive
  80. 80. • Slow: HTTP ajax round-trip • !DRY: Double front processing (Ajax / Push) • Push server complexity: authorizationsapp.wisembly.com/sflive
  81. 81. PUSH: The «Wisembly» way websocketData REST sessionToken data domain rightsapp.wisembly.com/sflive
  82. 82. PUSH: The «Wisembly» way websocketData REST sessionToken domain websocketData secret rightsapp.wisembly.com/sflive
  83. 83. PUSH: The «Wisembly» way websocketData REST sessionToken domain websocketData websocketData secret rightsapp.wisembly.com/sflive
  84. 84. PUSH: The «Wisembly» way websocketData REST sessionToken data websocketData domain websocketData secret rightsapp.wisembly.com/sflive
  85. 85. Push «surprises»app.wisembly.com/sflive
  86. 86. Push «surprises» • Must find always opened portapp.wisembly.com/sflive
  87. 87. Push «surprises» • Must find always opened port • Websocket protocol must go through firewallsapp.wisembly.com/sflive
  88. 88. Push «surprises» • Must find always opened port • Websocket protocol must go through firewalls • Push may disconnect (very!) frequently and loose events (duh!)app.wisembly.com/sflive
  89. 89. app.wisembly.com/sflive
  90. 90. • 80 always opened, but websocket very often blocked -> FAIL -> goto 443 w/ httpsapp.wisembly.com/sflive
  91. 91. • 80 always opened, but websocket very often blocked -> FAIL -> goto 443 w/ https • Implement disconnection mechanism and lost events in case of socket.io «degraded» protocol (xhr polling, jsonp polling)app.wisembly.com/sflive
  92. 92. The «Wisembly» way hashN: { eventName, args }app.wisembly.com/sflive
  93. 93. The «Wisembly» way hashN: { eventName, args }app.wisembly.com/sflive
  94. 94. The «Wisembly» way hashN hashN hashN hash1: { eventName, args } hashN: { eventName, args } hash2: { eventName, args } ... hashN: { eventName, args } hashN: { eventName, args } hashN: { eventName, args }app.wisembly.com/sflive
  95. 95. The «Wisembly» way hashM hashM hashN hashN: { eventName, args } ...hashN+M: { eventName, args }app.wisembly.com/sflive
  96. 96. The «Wisembly» way hashM hashM hashN REST onReconect() since hashN hashN: { eventName, args } ...hashN+M: { eventName, args }app.wisembly.com/sflive
  97. 97. The «Wisembly» way hashM hashM hashN REST onReconect() since hashN hashN: { eventName, args } ...hashN+M: { eventName, args } hashN+1: { eventName, args } ... hashN+M: { eventName, args }app.wisembly.com/sflive
  98. 98. The «Wisembly» way hashM hashM hashM REST onReconect() since hashN hashN: { eventName, args } ...hashN+M: { eventName, args } hashN+1: { eventName, args } ... hashN+M: { eventName, args }app.wisembly.com/sflive
  99. 99. Great Ressources http://fr.slideshare.net/nachomartin/symfony-javascript- combining-the-best-of-two-worlds http://williamdurand.fr/2012/08/02/rest-apis-with- symfony2-the-right-wayapp.wisembly.com/sflive
  100. 100. @guillaumepotier http://wisembly.com/en/about#jobsapp.wisembly.com/sflive
  101. 101. Any Questions ?app.wisembly.com/sflive
  102. 102. app.wisembly.com/sflive

×