Symfony2, Backbone.js & socket.io - SfLive Paris 2k13 - Wisembly
- 4. MySQL
PHP
app.wisembly.com/sflive
- 5. Twig
Symfony 2
Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 6. Twig js
jQuery
Assetic
Twig
Symfony 2
Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 7. Backbone.js
Underscore.js
Twig js
jQuery
Assetic
Twig
Symfony 2
Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 8. Backbone.js
Underscore.js
Twig js
jQuery
H !
UC
Assetic
O M Twig
TO Symfony 2
Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 10. Client
Twig REST
Symfony 2
Server Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 11. Backbone.js
Underscore.js
Client jQuery
HTML
Twig REST
Symfony 2
Server Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 12. Backbone.js
Underscore.js
Client jQuery
!
HTML
ING
L L
PO
NG
L O Twig REST
Symfony 2
Server Doctrine 2
MySQL
PHP
app.wisembly.com/sflive
- 14. Users want fast & smooth SaaS apps
Users want multiplateform SaaS apps
app.wisembly.com/sflive
- 15. Users want fast & smooth SaaS apps
Users want multiplateform SaaS apps
Users want dynamic & interactive SaaS apps
app.wisembly.com/sflive
- 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. 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 U
app.wisembly.com/sflive
- 20. E L L
H !
N O
?
app.wisembly.com/sflive
- 26. small / lightweight / stable
easy to learn, easy to extend
great resources:
layoutManager
relational
app.wisembly.com/sflive
- 27. Models Models
app.wisembly.com/sflive
- 28. Models Models
Collections Repositories
app.wisembly.com/sflive
- 29. Models Models
Collections Repositories
Views Controllers
app.wisembly.com/sflive
- 30. Models Models
Collections Repositories
Views Controllers
Templates Views
app.wisembly.com/sflive
- 31. Models Models
Collections Repositories
Views Controllers
Templates Views
Routing Routing
app.wisembly.com/sflive
- 32. Models Models
Collections Repositories
Views Controllers
S H
ViewsP
U
Templates +
RE ST
Routing Routing
app.wisembly.com/sflive
- 35. 1 - MAKE AN API
app.wisembly.com/sflive
- 36. MUST READ
http://fr.slideshare.net/nachomartin/symfony-
javascript-combining-the-best-of-two-worlds
app.wisembly.com/sflive
- 37. ar tin
nacm
@
Books = new
Backbone.collection();
Books.url = ‘/books’;
app.wisembly.com/sflive
- 38. ar tin
nacm
@
Books = new
Backbone.collection();
Books.url = ‘/books’;
Books.fetch();
GET /books
app.wisembly.com/sflive
- 39. ar tin
nacm
@
events:
{ ‘click
.mybutton’:‘doStuffAndSave’ }
doStuffAndSave: function() {
var book = Books.get(3);
book.stuff();
book.save();
}
app.wisembly.com/sflive
- 40. ar tin
nacm
@
events:
{ ‘click
.mybutton’:‘doStuffAndSave’ }
doStuffAndSave: function() {
var book = Books.get(3);
book.stuff();
book.save();
}
PUT /books/3
app.wisembly.com/sflive
- 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. II - MAKE A GOOD
REST API
app.wisembly.com/sflive
- 43. MUST READ 2
http://williamdurand.fr/2012/08/02/rest-apis-
with-symfony2-the-right-way
app.wisembly.com/sflive
- 45. JMSSerializer
or
class User implements UserInterface, EquatableInterface, ApiAbleInterface
{
}
app.wisembly.com/sflive
- 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
- 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. 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. 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. 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. 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. 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. 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. Controller
Entity Service API Service
Entity EntityRepository
app.wisembly.com/sflive
- 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. /**
* @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. /**
* @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. /**
* @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. /**
* @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. /**
* @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
- 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. 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 BASED
app.wisembly.com/sflive
- 67. websocketData?
Who sends what?
Which port, which protocol?
app.wisembly.com/sflive
- 79. PUSH: The «Classic» way
websocketData
REST
sessionToken
data
domain
rights
app.wisembly.com/sflive
- 80. • Slow: HTTP ajax round-trip
• !DRY: Double front processing (Ajax / Push)
• Push server complexity: authorizations
app.wisembly.com/sflive
- 81. PUSH: The «Wisembly» way
websocketData
REST
sessionToken
data
domain
rights
app.wisembly.com/sflive
- 82. PUSH: The «Wisembly» way
websocketData
REST
sessionToken
domain
websocketData
secret
rights
app.wisembly.com/sflive
- 83. PUSH: The «Wisembly» way
websocketData
REST
sessionToken
domain websocketData
websocketData
secret
rights
app.wisembly.com/sflive
- 84. PUSH: The «Wisembly» way
websocketData
REST
sessionToken
data websocketData
domain
websocketData
secret
rights
app.wisembly.com/sflive
- 87. Push «surprises»
• Must find always opened port
• Websocket protocol must go through firewalls
app.wisembly.com/sflive
- 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
- 90. • 80 always opened, but websocket very
often blocked -> FAIL -> goto 443 w/ https
app.wisembly.com/sflive
- 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
- 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. The «Wisembly» way
hashM hashM
hashN
hashN: { eventName, args }
...
hashN+M: { eventName, args }
app.wisembly.com/sflive
- 96. The «Wisembly» way
hashM hashM
hashN
REST
onReconect()
since hashN
hashN: { eventName, args }
...
hashN+M: { eventName, args }
app.wisembly.com/sflive
- 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. 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. 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-way
app.wisembly.com/sflive