Architecting Ajax Applications with  Zend Framework Matthew Weier O'Phinney Project Lead Zend Framework
Who I am <ul><li>ZF Contributor since January 2006
Assigned to the ZF team in July 2007
Promoted to Software Architect in April 2008
Project Lead since April 2009 </li></ul>Photo  ©  2009, Chris Shiflett
What we  won't  cover <ul><li>Actual client-side code </li><ul><li>Well, mostly none. </li></ul></ul>
Why not the client side? <ul><li>XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it </li></ul>var   ...
Why not the client side? <ul><li>XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it </li></ul>$.get(...
Why not the client side? <ul><li>XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it </li></ul>dojo.x...
Why not the client side? <ul><li>The real question is: how do you  detect  and  respond  to XHR requests in your Zend Fram...
Topics we  will  cover <ul><li>Write re-usable models that perform your application logic
Create standards-based services
Use HTTP wisely
Perform Context-Switching based on the requested Content-Type
Putting it all together: Tips and techniques </li></ul>
Writing Re-usable Models <ul><li>Write your application logic once, but use it in many different ways </li></ul>
Why? <ul><li>MVC is only one consumer
Can be called by job scripts, message systems, queues, etc.
Service endpoints can act as consumers (Hint: JSON-RPC is a good way to feed your Ajax applications) </li></ul>
What? <ul><li>Domain Models </li><ul><li>The individual resources and units of work in your application
E.g. user, team, backlog, sprint, etc.
E.g., object relations, transactions, queries </li></ul><li>Service Layers </li><ul><li>Public API of the application; i.e...
Logic for injecting dependencies </li></ul></ul>
Service Layers <ul><li>The guts of your application </li></ul>
Applications are like onions … …  they have layers Photo  ©  2008, Mike Chaput-Branson
Data Access Objects and Data store(s) Data Mappers Domain Models Service Layer Service Layer in Perspective
Benefits to Service Layers <ul><li>Allows easy consumption of the application via your MVC layer
Allows easy re-use of your application via services
Write CLI scripts that consume the Service Layer </li></ul>
What kind of application logic? <ul><li>Validation and filtering
Authentication and Authorization
Transactions and interactions between model entities </li></ul>
class  PersonService { public   function   create( array   $data ) { $person   =   new   Person(); if   (! $data   =   $th...
Techniques
Return objects implementing __toString() and/or toArray() <ul><li>Easily converted to a variety of formats for your XHR cl...
class   Foo { public   function   __toString() { return   'foo' ; } public   function   toArray() { $array   =   array ();...
if   (! $data   =   $cache ->load( 'foo_collection' )) { $data   =   $this ->fooObjects->toArray(); $cache ->save( $data ,...
Return collections as Paginators <ul><li>Consumers do not need to be aware of data format
Consumers can then provide offset and limit
Consumers can decide how to cast </li><ul><li>Paginator implements IteratorAggregate, toJson() </li></ul><li>Most Paginato...
Return paginators from Domain Objects </li></ul>
class   Foo_Model_User { public   function   fetchAll() { $select   =   $this ->getDbTable()-> select () ->where( 'disable...
$this ->users->setItemCountPerPage( $this ->numItems) ->setCurrentPageNumber( $this ->page); echo   $this ->users->toJson();
Caching <ul><li>Write-through caches are trivial to implement in service layers
Caching of result sets is trivial in service layers </li><ul><li>Or, built-in with Zend_Paginator! </li></ul></ul>
class   Foo_Model_User { public   function   update( array   $data ) { // ... update ... $cache   =   $this ->getCache(); ...
Zend_Paginator::setCache( $cache ); $users ->setCacheEnabled(true);
Implement ACLs in Service Layers <ul><li>Each service object is a resource
Once passed an ACL object, the service object defines the ACLs
Pass in role to the service object so it may do ACL checks
Throw a unique exception type for ACL failures; check for it in error handlers
Great way to make your RPC endpoints, such as JSON-RPC, ACL-aware </li></ul>
class   Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public   function   getResourceId() { return   't...
class   Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public   function   setAcl(Zend_Acl   $acl ) { $a...
class   Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public   function   setRole(   Zend_Role_Interfac...
class   Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public   function   fetchAll() { if   (! $this ->...
Writing Re-usable Models: Synopsis <ul><li>MVC may be only one consumer of your application; you may want to create JSON-R...
Use return values that are not data-source dependent
Use Service Layers as your application's public API
Implement strategies such as caching, validation, and authorization in service layers </li></ul>
Create Standards-based Services <ul><li>Standards are easier to consume </li></ul>
RPC and REST <ul><li>RPC: Remote Procedure Call </li><ul><li>Typical when wanting to invoke methods outside of standard CRUD
Often namespaced: “resource.method”
Typically, request includes a method name and typed arguments; response will be typed based on the method signature
Usually uses HTTP POST for all operations </li></ul></ul>
RPC and REST (cont.) <ul><li>REST: Representational State Transfer </li><ul><li>Typically manipulating resources; e.g. CRUD
Payloads vary based on requested Content-Type; XML and JSON are common
Uses HTTP verbs to indicate operations: </li><ul><li>GET: list all or retrieve individual resources
POST: create a new resource
PUT: update an existing resource
DELETE: delete a resource </li></ul></ul></ul>
RPC Services <ul><li>Define application behaviors and expose them </li></ul>
RPC Services in Zend Framework <ul><li>JSON-RPC: Zend_Json_Server
XML-RPC: Zend_XmlRpc_Server
SOAP: Zend_Soap_Server
AMF: Zend_Amf_Server </li></ul>
Common functionality <ul><li>Mimic SoapServer API from PHP </li><ul><li>Instantiate server
Attach one or more objects/classes, optionally with a namespace
Handle the request
Return the response </li></ul></ul>
JSON-RPC server $server   =   new   Zend_Json_Server(); $server ->setClass( 'App_Service_Team' ,   'team' ) ->setClass( 'S...
$server   =   new   Zend_XmlRpc_Server(); $server ->setClass( 'App_Service_Team' ,   'team' ) ->setClass( 'Scrum_Service_B...
SOAP server if   ( 'GET'   ==   $_SERVER [ 'REQUEST_METHOD' ]) { $server   =   new   Zend_Soap_AutoDiscover(); }   else   ...
Common RPC questions <ul><li>What about authentication? </li><ul><li>Determine method and authentication token from reques...
Check for authentication prior to handling request </li></ul></ul>
Authentication with RPC $request   =   $server ->getRequest(); if   (! in_array ( $method ,   array ( 'user.login' ))) { $...
Common RPC questions <ul><li>What about ACLs? </li><ul><li>Implement them in your service layer
Inject authentication identity when handling
Alternately, define ACLs based on request methods, and bail early </li></ul></ul>
Injecting the auth identity $server ->setClass( 'Scrum_Service_Sprint' ,   // class 'sprint' ,   // namespace $auth ->getI...
Checking ACLs based on method list ( $resource ,   $action ) =   explode ( '.' ,   $method ,   2); if   (! $acl ->isAllowe...
Common RPC questions <ul><li>My Service Layer objects have getters and setters I don't want to expose. </li><ul><li>Create...
Pass in dependencies via constructor arguments, use a service locator, or use lazy-loading with sane defaults. </li></ul><...
Creating a proxy object class   Foo_Service_Bar { public   function   setBar(   Foo_Model_Bar   $bar   ) { } public   func...
Injecting a service locator class   Foo_Service_BarProxy { public   function   __construct(   $serviceLocator   =   null  ...
Common RPC questions <ul><li>How do I expose services via the MVC </li><ul><li>Don't.
Seriously, you want your services snappy; bypass the MVC when exposing RPC services.
Re-use Zend_Application to bootstrap dependencies
Alternately, extend your application Bootstrap, and override the run() method </li></ul></ul>
Selectively bootstrapping resources $application ->bootstrap( 'auth' ); $application ->bootstrap( 'db' ); $server ->handle...
Overriding the application bootstrap class   XmlRpcService_Bootstrap   extends   Bootstrap { public   function   run() { $...
Common RPC questions <ul><li>How do I use methods other than POST? </li><ul><li>You don't.
Using the raw POST body is built into most RPC protocols
JSON Schema allows you to define a Service Mapping Description, which  can  allow you to selectively map HTTP methods to R...
RESTful Resources <ul><li>Define resources and expose them </li></ul>
Upcoming SlideShare
Loading in...5
×

Architecting Ajax Applications with Zend Framework

36,576

Published on

Published in: Technology
1 Comment
68 Likes
Statistics
Notes
No Downloads
Views
Total Views
36,576
On Slideshare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
1,339
Comments
1
Likes
68
Embeds 0
No embeds

No notes for slide

Architecting Ajax Applications with Zend Framework

  1. 1. Architecting Ajax Applications with Zend Framework Matthew Weier O'Phinney Project Lead Zend Framework
  2. 2. Who I am <ul><li>ZF Contributor since January 2006
  3. 3. Assigned to the ZF team in July 2007
  4. 4. Promoted to Software Architect in April 2008
  5. 5. Project Lead since April 2009 </li></ul>Photo © 2009, Chris Shiflett
  6. 6. What we won't cover <ul><li>Actual client-side code </li><ul><li>Well, mostly none. </li></ul></ul>
  7. 7. Why not the client side? <ul><li>XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it </li></ul>var req = new XMLHttpRequest(); req. open ( 'GET' , '/foo' , false ); req.send( null ); if (req.status == 200) { dump(req.responseText); }
  8. 8. Why not the client side? <ul><li>XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it </li></ul>$.get( &quot;/foo&quot; , function (data) { dump(data); });
  9. 9. Why not the client side? <ul><li>XmlHttpRequest is easy, and most good JS toolkits/frameworks abstract it </li></ul>dojo.xhrGet({ url: &quot;/foo&quot; , load: function (data) { dump(data); } });
  10. 10. Why not the client side? <ul><li>The real question is: how do you detect and respond to XHR requests in your Zend Framework application? </li></ul>
  11. 11. Topics we will cover <ul><li>Write re-usable models that perform your application logic
  12. 12. Create standards-based services
  13. 13. Use HTTP wisely
  14. 14. Perform Context-Switching based on the requested Content-Type
  15. 15. Putting it all together: Tips and techniques </li></ul>
  16. 16. Writing Re-usable Models <ul><li>Write your application logic once, but use it in many different ways </li></ul>
  17. 17. Why? <ul><li>MVC is only one consumer
  18. 18. Can be called by job scripts, message systems, queues, etc.
  19. 19. Service endpoints can act as consumers (Hint: JSON-RPC is a good way to feed your Ajax applications) </li></ul>
  20. 20. What? <ul><li>Domain Models </li><ul><li>The individual resources and units of work in your application
  21. 21. E.g. user, team, backlog, sprint, etc.
  22. 22. E.g., object relations, transactions, queries </li></ul><li>Service Layers </li><ul><li>Public API of the application; i.e., the behaviors you wish to expose
  23. 23. Logic for injecting dependencies </li></ul></ul>
  24. 24. Service Layers <ul><li>The guts of your application </li></ul>
  25. 25. Applications are like onions … … they have layers Photo © 2008, Mike Chaput-Branson
  26. 26. Data Access Objects and Data store(s) Data Mappers Domain Models Service Layer Service Layer in Perspective
  27. 27. Benefits to Service Layers <ul><li>Allows easy consumption of the application via your MVC layer
  28. 28. Allows easy re-use of your application via services
  29. 29. Write CLI scripts that consume the Service Layer </li></ul>
  30. 30. What kind of application logic? <ul><li>Validation and filtering
  31. 31. Authentication and Authorization
  32. 32. Transactions and interactions between model entities </li></ul>
  33. 33. class PersonService { public function create( array $data ) { $person = new Person(); if (! $data = $this ->getValidator() ->isValid( $data ) ) { throw new InvalidArgumentException(); } $person ->username = $data [ 'username' ]; $person ->password = $data [ 'password' ]; $person ->email = $data [ 'email' ]; $this ->getMapper()->save( $person ); return $person ; } }
  34. 34. Techniques
  35. 35. Return objects implementing __toString() and/or toArray() <ul><li>Easily converted to a variety of formats for your XHR clients </li><ul><li>JSON, XML </li></ul><li>Easy to cache </li><ul><li>Strings and arrays serialize and deserialize easily </li></ul></ul>
  36. 36. class Foo { public function __toString() { return 'foo' ; } public function toArray() { $array = array (); foreach ( $this ->_nodes as $node ) { $array [] = $node ->name; } return $array ; } }
  37. 37. if (! $data = $cache ->load( 'foo_collection' )) { $data = $this ->fooObjects->toArray(); $cache ->save( $data , 'foo-collections' ); } if (! $data = $cache ->load( 'foo-item' )) { $data = ( string ) $this ->foo; $cache ->save( $data , 'foo-item' ); }
  38. 38. Return collections as Paginators <ul><li>Consumers do not need to be aware of data format
  39. 39. Consumers can then provide offset and limit
  40. 40. Consumers can decide how to cast </li><ul><li>Paginator implements IteratorAggregate, toJson() </li></ul><li>Most Paginator adapters will only operate once results are requested
  41. 41. Return paginators from Domain Objects </li></ul>
  42. 42. class Foo_Model_User { public function fetchAll() { $select = $this ->getDbTable()-> select () ->where( 'disabled = ?' , 0); $paginator = new Zend_Paginator( new Zend_Paginator_Adapter_DbSelect( $select ) ); return $paginator ; } }
  43. 43. $this ->users->setItemCountPerPage( $this ->numItems) ->setCurrentPageNumber( $this ->page); echo $this ->users->toJson();
  44. 44. Caching <ul><li>Write-through caches are trivial to implement in service layers
  45. 45. Caching of result sets is trivial in service layers </li><ul><li>Or, built-in with Zend_Paginator! </li></ul></ul>
  46. 46. class Foo_Model_User { public function update( array $data ) { // ... update ... $cache = $this ->getCache(); $cache ->clean( Zend_Cache::CLEANING_MODE_MATCHING_TAG, array ( 'users' , 'user-' . $data [ 'username' ] ) ); $cache ->save( $data , 'user-' . $data [ 'username' ]); } }
  47. 47. Zend_Paginator::setCache( $cache ); $users ->setCacheEnabled(true);
  48. 48. Implement ACLs in Service Layers <ul><li>Each service object is a resource
  49. 49. Once passed an ACL object, the service object defines the ACLs
  50. 50. Pass in role to the service object so it may do ACL checks
  51. 51. Throw a unique exception type for ACL failures; check for it in error handlers
  52. 52. Great way to make your RPC endpoints, such as JSON-RPC, ACL-aware </li></ul>
  53. 53. class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function getResourceId() { return 'team' ; } // ... }
  54. 54. class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function setAcl(Zend_Acl $acl ) { $acl ->add( $this ) // ... ->allow( 'admin' , $this , array ( 'register' , 'update' , 'list' , 'delete' )); $this ->_acl = $acl ; return $this ; } // ... }
  55. 55. class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function setRole( Zend_Role_Interface $role ) { $this ->_role = $role ; return $this ; } // ... }
  56. 56. class Foo_Service_Team implements Zend_Acl_Resource_Interface { // ... public function fetchAll() { if (! $this ->getAcl()->isAllowed( $this ->getRole(), $this , 'list' ) ) { throw new UnauthorizedException(); } // ... } // ... }
  57. 57. Writing Re-usable Models: Synopsis <ul><li>MVC may be only one consumer of your application; you may want to create JSON-RPC or other RPC endpoints
  58. 58. Use return values that are not data-source dependent
  59. 59. Use Service Layers as your application's public API
  60. 60. Implement strategies such as caching, validation, and authorization in service layers </li></ul>
  61. 61. Create Standards-based Services <ul><li>Standards are easier to consume </li></ul>
  62. 62. RPC and REST <ul><li>RPC: Remote Procedure Call </li><ul><li>Typical when wanting to invoke methods outside of standard CRUD
  63. 63. Often namespaced: “resource.method”
  64. 64. Typically, request includes a method name and typed arguments; response will be typed based on the method signature
  65. 65. Usually uses HTTP POST for all operations </li></ul></ul>
  66. 66. RPC and REST (cont.) <ul><li>REST: Representational State Transfer </li><ul><li>Typically manipulating resources; e.g. CRUD
  67. 67. Payloads vary based on requested Content-Type; XML and JSON are common
  68. 68. Uses HTTP verbs to indicate operations: </li><ul><li>GET: list all or retrieve individual resources
  69. 69. POST: create a new resource
  70. 70. PUT: update an existing resource
  71. 71. DELETE: delete a resource </li></ul></ul></ul>
  72. 72. RPC Services <ul><li>Define application behaviors and expose them </li></ul>
  73. 73. RPC Services in Zend Framework <ul><li>JSON-RPC: Zend_Json_Server
  74. 74. XML-RPC: Zend_XmlRpc_Server
  75. 75. SOAP: Zend_Soap_Server
  76. 76. AMF: Zend_Amf_Server </li></ul>
  77. 77. Common functionality <ul><li>Mimic SoapServer API from PHP </li><ul><li>Instantiate server
  78. 78. Attach one or more objects/classes, optionally with a namespace
  79. 79. Handle the request
  80. 80. Return the response </li></ul></ul>
  81. 81. JSON-RPC server $server = new Zend_Json_Server(); $server ->setClass( 'App_Service_Team' , 'team' ) ->setClass( 'Scrum_Service_Backlog' , 'backlog' ) ->setClass( 'Scrum_Service_Sprint' , 'sprint' ); if ( 'GET' == $_SERVER [ 'REQUEST_METHOD' ]) { $server ->setTarget( '/jsonrpc' ) ->setEnvelope( Zend_Json_Server_Smd::ENV_JSONRPC_2 ); header ( 'Content-Type: application/json' ); echo $server ->getServiceMap(); return ; } $server ->handle();
  82. 82. $server = new Zend_XmlRpc_Server(); $server ->setClass( 'App_Service_Team' , 'team' ) ->setClass( 'Scrum_Service_Backlog' , 'backlog' ) ->setClass( 'Scrum_Service_Sprint' , 'sprint' ); $response = $server ->handle(); echo $response ; XML-RPC server
  83. 83. SOAP server if ( 'GET' == $_SERVER [ 'REQUEST_METHOD' ]) { $server = new Zend_Soap_AutoDiscover(); } else { $server = new Zend_Soap_Server( $soapUrl ); } $server ->setClass( 'App_Service_Team' ) ->setClass( 'Scrum_Service_Backlog' ) ->setClass( 'Scrum_Service_Sprint' ); $server ->handle();
  84. 84. Common RPC questions <ul><li>What about authentication? </li><ul><li>Determine method and authentication token from request object
  85. 85. Check for authentication prior to handling request </li></ul></ul>
  86. 86. Authentication with RPC $request = $server ->getRequest(); if (! in_array ( $method , array ( 'user.login' ))) { $params = $request ->getParams(); $authKey = array_shift ( $params ); $adapter = new My_Auth_Adapter_Api( $authKey ); $result = Zend_Auth::getInstance() ->authenticate( $adapter ); if (Zend_Auth_Result::SUCCESS != $result ->getCode() ) { $response = $server ->fault( 'Unauthorized access' , 401); echo $fault ; return ; } }
  87. 87. Common RPC questions <ul><li>What about ACLs? </li><ul><li>Implement them in your service layer
  88. 88. Inject authentication identity when handling
  89. 89. Alternately, define ACLs based on request methods, and bail early </li></ul></ul>
  90. 90. Injecting the auth identity $server ->setClass( 'Scrum_Service_Sprint' , // class 'sprint' , // namespace $auth ->getIdentity() // ctor arg );
  91. 91. Checking ACLs based on method list ( $resource , $action ) = explode ( '.' , $method , 2); if (! $acl ->isAllowed( $auth ->getIdentity(), $resource , $action ) ) { $response = $server ->fault( 'Unauthorized' , 401 ); echo $response ; return ; }
  92. 92. Common RPC questions <ul><li>My Service Layer objects have getters and setters I don't want to expose. </li><ul><li>Create a proxy object that exposes only those methods you want exposed.
  93. 93. Pass in dependencies via constructor arguments, use a service locator, or use lazy-loading with sane defaults. </li></ul></ul>
  94. 94. Creating a proxy object class Foo_Service_Bar { public function setBar( Foo_Model_Bar $bar ) { } public function getBar() { } public function find( $id ) { } public function fetchAll() { } } class Foo_Service_BarProxy { public function __construct( $options = null){} protected function _setService( Foo_Service_Bar $service ) { } protected function _getService() { } public function find( $id ) { } public function fetchall() { } }
  95. 95. Injecting a service locator class Foo_Service_BarProxy { public function __construct( $serviceLocator = null ) { } } $server ->setService( 'Foo_Service_BarProxy' , 'bar' , $serviceLocator );
  96. 96. Common RPC questions <ul><li>How do I expose services via the MVC </li><ul><li>Don't.
  97. 97. Seriously, you want your services snappy; bypass the MVC when exposing RPC services.
  98. 98. Re-use Zend_Application to bootstrap dependencies
  99. 99. Alternately, extend your application Bootstrap, and override the run() method </li></ul></ul>
  100. 100. Selectively bootstrapping resources $application ->bootstrap( 'auth' ); $application ->bootstrap( 'db' ); $server ->handle();
  101. 101. Overriding the application bootstrap class XmlRpcService_Bootstrap extends Bootstrap { public function run() { $server = new Zend_XmlRpc_Server(); // ... $server ->handle(); } } $application ->bootstrap()->run();
  102. 102. Common RPC questions <ul><li>How do I use methods other than POST? </li><ul><li>You don't.
  103. 103. Using the raw POST body is built into most RPC protocols
  104. 104. JSON Schema allows you to define a Service Mapping Description, which can allow you to selectively map HTTP methods to RPC methods; ZF does not yet support this in Zend_Json_Server. </li></ul></ul>
  105. 105. RESTful Resources <ul><li>Define resources and expose them </li></ul>
  106. 106. RESTful Resources in Zend Framework <ul><li>Use Zend_Rest_Route to map controllers as RESTful resources </li><ul><li>Maps HTTP methods to actions
  107. 107. Allows for mimicing PUT and DELETE </li></ul><li>Use Zend_Rest_Controller, or implement: </li><ul><li>indexAction()
  108. 108. postAction()
  109. 109. getAction()
  110. 110. putAction()
  111. 111. deleteAction() </li></ul></ul>
  112. 112. What does REST have to do with Ajax? <ul><li>It's predictable; if you have a RESTful resource, you know how to do CRUD
  113. 113. New client-side technologies such as JSON-REST automate REST operations with XmlHttpRequest </li></ul>
  114. 114. Creating RESTful routes // Entire application: $router ->addRoute( 'default' , new Zend_Rest_Route( $front ) ); // All controllers in a module: $router ->addRoute( 'rest' , new Zend_Rest_Route( $front , array (), array ( 'scrum' ) ) );
  115. 115. Creating RESTful routes (cont.) // Adding select controllers for a // given module $router ->addRoute( 'rest' , new Zend_Rest_Route( $front , array (), array ( 'scrum' => array ( 'backlog' , 'sprint' , ) )) );
  116. 116. RESTful controllers class Scrum_BacklogController extends Zend_Rest_Controller { public function indexAction() { } // list public function postAction() { } // create public function getAction() { } // view public function putAction() { } // update public function deleteAction() { } // delete }
  117. 117. Standardized Services: Synopsis <ul><li>Use standard server types. </li><ul><li>Wealth of existing clients that can consume them
  118. 118. Predictability == maintainability
  119. 119. Flexibility is typically built-in, if you exercise some creativity </li></ul><li>Know when to use RPC and when to use REST – and what the difference is.
  120. 120. Leverage existing application setup, authentication, and ACLs whenever possible. </li></ul>
  121. 121. Use HTTP Wisely <ul><li>Get to know your local HTTP protocol </li></ul>
  122. 122. Use HTTP Wisely <ul><li>Inspect incoming HTTP request headers
  123. 123. Set appropriate HTTP response headers </li></ul>
  124. 124. Why should I care? <ul><li>Often you can evaluate the success of an XHR request based on just the HTTP response code.
  125. 125. You can tell the server what kind of content is being provided in the request, as well as what kind of content you want returned, via HTTP headers. </li><ul><li>This is a two-way street; the server needs to respond correctly! </li></ul></ul>
  126. 126. Common HTTP Request Headers <ul><li>Content-Type: the content type being sent to your application. E.g. application/json, application/xml, etc.
  127. 127. Accept: what content type the client is expecting in response. E.g., application/json, application/xml, etc.
  128. 128. Range: how many bytes or items should be returned in the response. </li></ul>
  129. 129. Common HTTP Response Codes <ul><li>201 (Created) </li><ul><li>After a POST that creates a resource </li></ul><li>400 (Bad Request) </li><ul><li>Failed validations </li></ul><li>401 (Unauthorized)
  130. 130. 204 (No Content) </li><ul><li>Often used after successful DELETE operations </li></ul><li>500 (Application Error) </li></ul>
  131. 131. Common HTTP Response Headers <ul><li>Content-Type: The content type of the response body.
  132. 132. Content-Range: the number of bytes returned and total number of bytes, OR the range of items returned, and total number of items.
  133. 133. Vary: hint to client-side caching what headers should be used to determine whether or not to cache. </li></ul>
  134. 134. Use HTTP Wisely: Synopsis <ul><li>Inspect incoming HTTP request headers, and vary output and responses based on them.
  135. 135. Consider client-side caching when setting response headers. </li></ul>
  136. 136. Perform Context Switching based on the requested Content-Type <ul><li>How to vary your responses based on the request </li></ul>
  137. 137. Context Switching <ul><li>Inspect incoming HTTP request headers, and vary output and responses based on them.
  138. 138. Built-in support via the ContextSwitch and AjaxContext action helpers
  139. 139. … but you need to give them some help. </li></ul>
  140. 140. Context Switching: Basics <ul><li>Map controller actions to “contexts”
  141. 141. A “format” request parameter will indicate which context was detected
  142. 142. When a context is detected, an additional suffix is added to the view script
  143. 143. Optionally, additional headers might be sent. </li></ul>
  144. 144. Mapping contexts to actions class FooController extends Zend_Controller_Action { public function init() { $switch = $this ->_helper ->getHelper( 'ContextSwitch' ); // Add &quot;xml&quot; context to &quot;bar&quot; action: $switch ->addActionContext( 'bar' , 'xml' ); // Add &quot;json&quot; context to &quot;baz&quot; action: $switch ->addActionContext( 'baz' , 'json' ); // Detect context $switch ->initContext(); } }
  145. 145. How AjaxContext differs <ul><li>AjaxContext works just like ContextSwitch
  146. 146. … but it only triggers on XMLHttpRequest (i.e., when the “X-Requested-With: XMLHttpRequest” header is present)
  147. 147. When detecting Accept and Content-Type headers, often you can simply use ContextSwitch </li></ul>
  148. 148. Request Header Detection <ul><li>When to do it? </li><ul><li>Once per request
  149. 149. Front controller plugin: routeShutdown() or dispatchLoopStartup() </li></ul><li>How to do it? </li><ul><li>Use the request object
  150. 150. getHeader($headerName) is your friend </li></ul><li>What to do? </li><ul><li>Set request parameters accordingly </li></ul></ul>
  151. 151. Sample Accept header detection class AcceptPlugin extends Zend_Controller_Plugin_Abstract { public function dispatchLoopStartup( Zend_Controller_Request_Abstract $request ) { $header = $request ->getHeader( 'Accept' ); switch (true) { case ( strstr ( $header , 'application/json' )): $request ->setParam( 'format' , 'json' ); break ; case ( strstr ( $header , 'application/xml' ) && (! strstr ( $header , 'html' ))): $request ->setParam( 'format' , 'xml' ); break ; } } }
  152. 152. Views per Context <ul><li>When a context is detected, that context name is prepended to the view script suffix: .xml.phtml , .json.phtml , etc.
  153. 153. View scripts should set appropriate response headers </li><ul><li>Controllers then do not need to be aware of current context
  154. 154. Headers are primarily the realm of the view </li></ul><li>Create appropriate output for the context </li></ul>
  155. 155. Creating alternate views per context application |-- controllers | `-- SprintController |-- views | |-- scripts | | |-- sprint | | | |-- index.phtml | | | |-- index.xml.phtml | | | |-- index.json.phtml <h2>Sprints</h2> <div id= &quot;sprints&quot; ></div>
  156. 156. Creating alternate views per context application |-- controllers | `-- SprintController |-- views | |-- scripts | | |-- sprint | | | |-- index.phtml | | | |-- index.xml.phtml | | | |-- index.json.phtml $this ->response->setHeader( 'Content-Type' , 'application/xml' ); $xmlWriter = new Phly_Array2Xml(); echo $xmlWriter ->toXml( $this ->sprints->toArray());
  157. 157. Creating alternate views per context application |-- controllers | `-- SprintController |-- views | |-- scripts | | |-- sprint | | | |-- index.phtml | | | |-- index.xml.phtml | | | |-- index.json.phtml $this ->response->setHeader( 'Content-Type' , 'application/json' ); echo $this ->json( $this ->sprints->toArray());
  158. 158. Putting it all Together <ul><li>Content-Types, Ranges, and paginators – oh, my! </li></ul>
  159. 159. Retrieving and decoding parameters from non-standard Content-Types
  160. 160. Parameters: The Problem <ul><li>Non form-encoded Content-Types typically mean parameters are passed in the raw request body
  161. 161. Additionally, they likely need to be decoded and serialized to a PHP array
  162. 162. Use an action helper to automate the process </li></ul>
  163. 163. Retrieving parameters class Scrummer_Controller_Helper_Params extends Zend_Controller_Action_Helper_Abstract { protected $_bodyParams = array (); public function init() { $request = $this ->getRequest(); $rawBody = $request ->getRawBody(); if (empty( $rawBody )) { return ; } // ... } }
  164. 164. Retrieving parameters (cont.) // ... $contentType = $request ->getHeader( 'Content-Type' ); switch (true) { case ( strstr ( $contentType , 'application/json' )): $this ->setBodyParams( Zend_Json::decode( $rawBody )); break ; // ... }
  165. 165. Retrieving parameters (cont.) // ... switch (true) { // ... case ( strstr ( $contentType , 'application/xml' )): $config = new Zend_Config_Xml( $rawBody ); $this ->setBodyParams( $config ->toArray()); break ; // ... }
  166. 166. Retrieving parameters (cont.) // ... switch (true) { // ... default: if ( $request ->isPut()) { parse_str ( $rawBody , $params ); $this ->setBodyParams( $params ); } break ; }
  167. 167. Retrieving parameters (cont.) class Scrummer_Controller_Helper_Params extends Zend_Controller_Action_Helper_Abstract { public function getBodyParam( $name ) { } public function hasBodyParam( $name ) { } public function hasBodyParams() { } public function getSubmitParams() { if ( $this ->hasBodyParams()) { return $this ->getBodyParams(); } return $this ->getRequest()->getPost(); } public function direct() { return $this ->getSubmitParams(); } }
  168. 168. Retrieving parameters – Usage class FooController extends Zend_Rest_Controller { public function postAction() { // assuming Params helper // is registered... $params = $this ->_helper->params(); $model = $this ->getModel(); if (! $model ->create( $params )) { // ... } // ... } }
  169. 169. Detecting and Using Range Headers
  170. 170. Range: The Problem <ul><li>Requesting all items from a RESTful resource is bad; so is returning all results </li><ul><li>More results == worse performance
  171. 171. More results == more time to retrieve
  172. 172. More results often leads to exhausting memory resources </li></ul><li>The Range header may be used to request a subset of the items
  173. 173. Use a plugin to detect the Range, and inject it into the request object </li></ul>
  174. 174. Detecting the Range header public function dispatchLoopStartup( Zend_Controller_Request_Abstract $request ) { if (! $range = $request ->getHeader( 'Range' )) { return ; } // Format is: Range: items=0-9 $range = explode ( '=' , $range ); list ( $start , $end ) = explode ( '-' , $range [1]); $request ->setParams( array ( 'range_start' => $start , 'range_end' => $end , )); }
  175. 175. Using range in a view script // Assuming that sprints is a Paginator object: $request = $this ->request; if ( $request ->range_start || $request ->range_end) { $start = ( int ) $request ->range_start; $end = ( int ) $request ->range_end; $count = $this ->sprints->getTotalItemCount(); $limit = (! $end ) ? $count : $end - $start ; $end = (! $end ) ? $count : $end ; $items = $this ->sprints->getAdapter() ->getItems( $start , $limit ); $this ->response->setHeader( 'Content-Range' , 'items ' . $start . '-' . $end . '/' . $count ); }
  176. 176. Leveraging REST to create CRUD applications using Dojo
  177. 177. CRUD: The Problem <ul><li>CRUD == Create – Read – Update – Delete
  178. 178. … bo-ring!
  179. 179. REST solves CRUD on the server-side and makes it trivial
  180. 180. Leverage client-side UI toolkits to solve the problem of CRUD screens </li><ul><li>dojox.data.JsonRestStore interfaces with REST endpoints
  181. 181. dojox.grid.DataGrid can consume JsonRestStore </li></ul></ul>
  182. 182. Defining a Simple Mapping Description scrum.sprint.schema = {properties: { id: { type : &quot;integer&quot; }, backlog_id: { type : &quot;integer&quot; }, startDate: { type : &quot;string&quot; , &quot;default&quot; : new Date() }, endDate: { type : &quot;string&quot; , &quot;default&quot; : new Date() }, scrumTime: { type : &quot;string&quot; , &quot;default&quot; : &quot;T10:00:00&quot; } }};
  183. 183. Defining a JsonRestStore dojo.require( &quot;dojox.data.JsonRestStore&quot; ); var store = new dojox.data.JsonRestStore({ target : &quot;/sprint&quot; , idAttribute: &quot;id&quot; , schema: scrum.sprint.schema });
  184. 184. Defining a DataGrid layout var layout = [ { field: 'backlog_id' , name : 'Backlog' , editable: true }, { field: 'startDate' , name : 'Start Date' , editable: true , cellType: &quot;dojox.grid.cells.DateTextBox&quot; }, // ... { field: 'scrumTime' , name : 'Scrum Time' , editable: true , cellType: &quot;scrum.grid.TimeTextBox&quot; } ];
  185. 185. Defining a DataGrid grid var grid = new dojox.grid.DataGrid({ store: store, rowsPerPage: 10, autoheight: 5, structure: layout }, document .createElement( 'div' ));
  186. 186. Defining a DataGrid (declarative) <table id = &quot;sprintGrid&quot; dojoType = &quot;dojox.grid.DataGrid&quot; rowsPerPage = &quot;10&quot; store = &quot;store&quot; autoheight = &quot;5&quot; > <thead><tr> <th field = &quot;backlog_id&quot; width = &quot;auto&quot; editable = &quot;true&quot; > Backlog </th> <th field = &quot;startDate&quot; width = &quot;auto&quot; cellType = &quot;dojox.grid.cells.DateTextBox&quot; editable = &quot;true&quot; > Start Date </th> <!-- ... --> <th field = &quot;scrumTime&quot; width = &quot;auto&quot; cellType = &quot;scrum.grid.TimeTextBox&quot; editable = &quot;true&quot; > Scrum Time </th>
  187. 187. DataGrid in Action
  188. 188. In summary … <ul><li>Listen to the fat guy sing … </li></ul>
  189. 189. Conclusions <ul><li>Think about what behaviors you want to expose, and write models that do so.
  190. 190. Use predictable, standards-based services, and use them throughout your application.
  191. 191. Use HTTP wisely; examine request headers and send appropriate response headers.
  192. 192. Perform context switching based on the Accept header; check for the Content-Type when you examine the request. </li></ul>
  193. 193. Conclusions (cont.) <ul><li>Leverage headers such as Range to reduce the size of the response, and use Vary to aid in client-side caching.
  194. 194. Leverage client-size UI widgets to simplify common CRUD tasks. </li></ul>
  195. 195. Most importantly … <ul><li>Keep it simple and predictable. </li></ul>
  196. 196. Thank you! <ul><li>Feedback: http://joind.in/talk/view/883
  197. 197. ZF site: http://framework.zend.com/
  198. 198. Twitter: http://twitter.com/weierophinney
  199. 199. Blog: http://weierophinney.net/matthew/ </li></ul>
  1. Gostou de algum slide específico?

    Recortar slides é uma maneira fácil de colecionar informações para acessar mais tarde.

×