10 June 2010, RAI, Amsterdam Zend Framework Workshop
Today's Roadmap
what we hope to cover <ul><li>Service Layers, including ACLs and Paginators
Application resources
Routing clinic
Enhanced views
Navigation
Caching
Context switching
Form decorators </li></ul>
Service Layers or: separating your concerns
Applications are like onions; they have layers Photo  ©  2008,  Mike Chaput-Branson
service layer in perspective Data Access Objects and data stores Data Mappers,  Repositories, and Transaction Scripts Doma...
use Plain Old PHP Objects class  User { public function   getId() {} public function   setId( $value ) {} public function ...
use 3 rd  party code via composition use  ZendValidatorValidationChain; class   Entry { public function   setValidator(Val...
Define a schema based on the objects you  use (http://musicbrainz.org/)
use  mappers  or  transaction scripts to translate objects to data & back $user  =  new  User(); $user ->setId( 'matthew' ...
use  service objects   to manipulate entities namespace  BlogService; class   Entries { public function   fetchEntry( $per...
what's found in Service Layers? <ul><li>Resource marshalling and Dependency Injection
Application-specific logic: </li><ul><li>Authentication and Authorization (ACLs)
Input filtering/data validation
Search indexing
Caching
etc. </li></ul></ul>
ACL basics
Authorization is the act of determining  if  somebody  has  permissions  to perform an  action  on a given  resource .
the three “R”s <ul><li>Roles  – what requests the action
Resources  – what is being acted upon
Rights  – the privileges a role has for a given resource </li></ul>
roles <ul><li>Implement  Zend_Acl_Role_Interface </li><ul><li>One method:  getRoleId() </li></ul><li>Can simply use  Zend_...
a “user” role class  User    implements  Zend_Acl_Role_Interface { // ... protected   $_role   =   'pig' ; public function...
resources <ul><li>Implement  Zend_Acl_Resource_Interface </li><ul><li>One method:  getResourceId() </li></ul><li>Can simpl...
an “entry” resource class  Entry    implements  Zend_Acl_Resource_Interface { // ... public function   getResourceId() { r...
rights <ul><li>Zend_Acl denies all access by default
You must whitelist role access to resources
You can also blacklist, though this is less common  and less secure </li></ul>
defining an ACL class  Acl  extends  Zend_Acl { public function   __construct() { $this ->addRole( 'anonymous' ) ->addRole...
checking ACLs <ul><li>Remember the three “R”s; check that: </li><ul><li>The  Role
acting on the  Resource
has  Rights  to perform the action </li></ul><li>Use the  isAllowed()  method </li></ul>
service layer ACL verification class  EntryService { public function   create( array   $data ) { $entry   =   new   Entry;...
Paginators
return paginators from your domain models <ul><li>Consumers do not need to be aware of data format
Consumers can provide offset and limit
Consumers can decide how to cast </li><ul><li>Zend_Paginator  implements  IteratorAggregate ,  toJson() </li></ul><li>Most...
Zend_Paginator basics <ul><li>setCurrentPageNumber($page)
setItemCountPerPage($int)
setPageRange($int)  (number of pages to show in paginator)
Constructor accepts an adapter capable of generating a  count()  and returning items based on an offset and item count </l...
repository returning paginator public function  fetchAll() { $select   =   $this ->getDbTable()->select() ->where( 'disabl...
paginator use in view script $this ->users->setItemCountPerPage(   $this ->numItems) ->setCurrentPageNumber(   $this ->pag...
going generic <ul><li>Alternately, define a “collection” type </li><ul><li>e.g., to accept a  MongoCursor
Implement  Countable,  and  Iterator ; optionally also some serialization interfaces
Define methods like  skip()  or  page(),  and  sort()  and  setItemClass() </li></ul></ul>
mapper returning generic collection public function  find(   array   $query ,  array   $fields  =  array () ) { $query   =...
collection used in view script $this ->entries->start( $this ->offset) ->limit( $this ->numItems); echo   $this ->json(   ...
Zend_Application
how it used to be done $config  =  new  Zend_Config_Ini( $pathToConfig ,  'prod' ); $db   =   Zend_Db::factory( $config ->...
what some savvy users did class  Initialize    extends  Zend_Controller_Plugin_Abstract { public function   initDb() {   /...
problems with the approaches <ul><li>Limited re-usability
Lots of scaffolding
Difficult to create resources with dependencies
Moving from one application to another often means learning a new method of bootstrapping </li></ul>
what Zend_Application provides <ul><li>Common configuration methodology </li><ul><li>And per-environment configuration </l...
Dependency tracking for resources
Resource injection into the application (acts as a repository) </li></ul>
what are resources? <ul><li>Anything you need within your application that requires configuration
Two types: </li><ul><li>Bootstrap-specific resources
Plugin resources </li></ul></ul>
commonalities <ul><li>Zend_Application  instantiates and executes a bootstrap </li><ul><li>An “environment” is passed as w...
You then selectively bootstrap resources, or bootstrap the entire application
Finally, you optionally  run()  the app </li></ul>
bootstrap resources <ul><li>Any  protected  method beginning with  “_init”  is considered a resource; it's name is whateve...
configuration we'll use [production] mongodb.server  =   &quot;appserver&quot; mongodb.dbname  =   &quot;site&quot; [testi...
example bootstrap resource class  Bootstrap extends   Zend_Application_Bootstrap_Bootstrap { protected function  _ initMon...
plugin resources <ul><li>Implement  Zend_Application_Resource_Resource </li><ul><li>Defines an  init()  method </li></ul><...
example plugin resource class  Wopnet_Resource_Mongodb extends   Zend_Application_Resource_ResourceAbstract { protected   ...
invoking the application $application  =  new  Zend_Application( 'production' , '/path/to/application.ini' ); $application...
re-using it for a service endpoint $application  =  new  Zend_Application( 'jsonrpc' , '/path/to/application.ini' ); $appl...
dependency tracking <ul><li>Some resources are dependent on others executing
bootstrap($resource)  ensures that that resource is only bootstrapped once
getResource($resource)  will return whatever that resource method/plugin returns </li></ul>
dependency example public function  _initRoutes() { $this ->bootstrap( 'frontcontroller' ); $fc   =   $this ->getResource(...
how do I use resources elsewhere? <ul><li>Bootstrap acts as a registry
Bootstrap is injected into the front controller as a parameter </li></ul>
retrieving resources // from anywhere: $fc   =   Zend_Controller_Front::getInstance(); $bootstrap   =   $fc ->getParam( 'b...
retrieving configuration [production] lucene.basepath  =   &quot;/data/search/&quot; public function  searchAction() { $bo...
Routing Clinic
basics <ul><li>Name the route: “blog-entry”, “rest”, etc.
Specify the path to match: “blog”, “blog/:id”, etc.
Specify default values to pass to the request object: module, controller, action, etc.
Optionally, specify validations: “id” must match “d+” </li></ul>
static route use  Zend_Controller_Router_Route_Static    as  StaticRoute; $entries   =   new   StaticRoute( 'blog' , array...
standard (named) route use  Zend_Controller_Router_Route    as  StandardRoute; $entry   =   new   StandardRoute( 'blog/:id...
regex route use  Zend_Controller_Router_Route_Regex    as  RegexRoute; $contexts   =   new   RegexRoute( 'blog/(?<id>[^.]*...
hostname route use  Zend_Controller_Router_Route_Hostname    as  HostnameRoute; $hostnameRoute   =   new   HostnameRoute( ...
route chaining // Create chain of &quot;hostname&quot; route, // &quot;entry&quot; route, and &quot;contexts&quot; route /...
RESTful routes <ul><li>HTTP GET , no arguments: list /resource
HTTP POST , no arguments: create /resource + RAW POST
HTTP GET , 1 argument: item /resource/:id
HTTP PUT , 1 argument: update /resource/:id + RAW PUT
HTTP DELETE , 1 argument: delete /resource/:id </li></ul>
defining all routes RESTful // Enable for all controllers $front   =   Zend_Controller_Front::getInstance(); $restRoute   ...
defining select modules as RESTful // Enable for blog module only $front   =     Zend_Controller_Front::getInstance(); $re...
Defining select controllers as RESTful // Enable for specific controllers only $front   =     Zend_Controller_Front::getIn...
sample REST controller class  EntryController    extends  Zend_Rest_Controller { public function  index Action() { } publi...
defining routes in the bootstrap protected function  _initRoutes() { $this ->bootstrap( 'frontcontroller' ); $fc   =   $th...
or via configuration [production] resources.router.routes.entries.type  =   &quot;Zend_Controller_Router_Route_Static&quot...
Layout  View Helpers
layout related view helpers <ul><li>Set metadata, JS & CSS within action view scripts
Aggregate content
Rendered in the layout </li></ul>
doctype() <ul><li>Call before the first view script is rendered
Affects rendering of other view helpers, such as forms </li></ul>
setting the doctype <!-- in layouts/scripts/layout.phtml --> <?php   echo $this ->doctype();  ?> ;application.ini resource...
the head() view helpers <ul><li>Used for setting all the  <head>  tags
Can append and prepend
Available: </li><ul><li>headMeta()
headTitle()
headScript()
headStyle() </li></ul></ul>
the inline() view helpers <ul><li>Used for setting inline CSS within  <head>  & inline JS just before  </body>
Available: </li><ul><li>inlineStyle()
inlineScript() </li></ul></ul>
set in layout before rendering <?php   $this ->headMeta()->prependHttpEquiv( 'Content-Type' ,  'text/html;charset=utf-8' )...
multiple layout scripts? use a view helper! class  Zend_View_Helper_HeadSection  extends  Zend_View_Helper_Abstract  { pub...
all layout script files <?php   echo $this ->doctype();  ?>   <html   xmlns = &quot;http://www.w3.org/1999/xhtml&quot; >  ...
Adding CSS/JS <ul><li>Some actions need JS/CSS included in  <head> </li><ul><li>or JS just before  </body> </li></ul><li>P...
action specific CSS // in product/index.phtml $this ->headLink()->appendStylesheet( $this ->baseUrl() .  '/css/product.css...
inline JS: action view script $script  =  <<<EOT $(document).ready(function() { $(&quot;a[rel^='prettyPhoto']&quot;).prett...
inline JS: layout view script <!-- ... --> <?php   echo $this ->inlineScript();  ?> </body>
Navigation
use cases Zend_Navigation  is  a component  for managing  trees  of  pointers   to  web pages .
use cases <ul><li>Search Engine Optimization (SEO) </li><ul><li>Sitemap and links enhance SEO for a site </li></ul><li>Sit...
Navigation: features
menus
breadcrumbs
header links <ul><li>Provide hints to page regarding pages relative to current page </li><ul><li>&quot;Start&quot; or &quo...
&quot;Next&quot; or &quot;Previous&quot; pages in same depth
&quot;Chapters&quot; or &quot;Sub-Sections&quot; in this area
Alternate formats for this content </li></ul></ul>
sitemaps
body links <ul><li>Render links to related content </li><ul><li>previous/next page
parent
children </li></ul><li>Translate link text
Conditionally display links based on ACLs </li></ul>
Navigation: pages and containers
pages An object that holds a  pointer  to a  web page .
Zend_Navigation_Page metadata <ul><li>label
title
target
rel
order
active
visible
ACL info (resource, privilege) </li></ul>
page types <ul><li>MVC </li><ul><li>Integration with the router for URL generation </li></ul><li>URI </li><ul><li>Specify ...
creating MVC pages $page  = new Zend_Navigation_Page_Mvc(); $page ->title   =   &quot;Foo&quot; ; $page ->controller   =  ...
creating URI pages $page   =   new   Zend_Navigation_Page_Uri(); $page ->title   =   &quot;Foo&quot; ; $page ->uri   =   &...
using the page factory <ul><li>Zend_Navigation_Page::factory()
Accepts array or  Zend_Config  object
Rules </li><ul><li>if 'uri' provided, and no MVC options, URI page created
if any MVC options (action, controller, module, route),  MVC page created
if 'type' provided, assumes this is a Page class to use </li><ul><li>can be &quot;mvc&quot; or &quot;uri&quot; </li></ul><...
containers A  container  for pages.
Zend_Navigation_Container <ul><li>Implements RecursiveIterator (iterate entire tree)
Implements Countable (get total count of pages)
Zend_Navigation_Page  is a container </li><ul><li>Allows having trees of pages </li></ul></ul>
adding a page to a container // addPage() $container ->addPage( $page ); // addPage() using factory $container ->addPage( ...
adding a page to a container  (cont) // addPage() using an array: $container ->addPage( array ( 'uri'   =>   'http://www.e...
adding multiple pages at once // addPages() using an array: $container ->addPages( $array ); // addPages() using a Zend_Co...
removing pages from a container // removePage(): // Accepts a page instance, or an integer // indicating value of order me...
finding pages in a container <ul><li>All finder methods return containers </li><ul><li>So you can iterate, count, etc. </l...
finding pages in a container // findOneBy($property, $value) $page   =   $container ->findOneBy(   'label' ,   'Zend Frame...
iteration of pages (flat) foreach   ( $container   as   $page ) { echo   $page ->label,   &quot;<br />
&quot; ; }
iteration of pages (recursive) $it   =   new   RecursiveIteratorIterator( $container , RecursiveIteratorIterator::SELF_FIR...
container utility methods <ul><li>hasPage(Zend_Navigation_Page $page) </li><ul><li>Does the container have the given page?
if ($container->hasPage($page))  { /* do something */ } </li></ul><li>hasPages() </li><ul><li>Does the container have *any...
Equivalent to count($container) > 1 </li></ul><li>toArray() </li><ul><li>Serialize container to array structure </li></ul>...
methods for defining pages <ul><li>Programmatically
Using arrays
Using  Zend_Config  values </li></ul>
strategies for defining pages <ul><li>Cache the definitions </li><ul><li>First time through: </li><ul><li>Create programma...
Cache to array using  toArray() </li></ul><li>Subsequent times: </li><ul><li>Load from cached array </li></ul></ul><li>Use...
Disable on XHR calls, service calls, etc. </li></ul></ul>
Navigation: using the view helpers
Zend_View_Helper_Navigation <ul><li>Holds a container
Proxies to other Navigation view helpers
Holds Translator and Acl objects </li></ul>
adding a container to the view helper // Directly $view ->getHelper( 'navigation' )->setContainer(   $container ); // Via ...
injecting ACLs into the  navigation view helper (direct) // Inject ACL $view ->navigation()->setAcl( $acl ); // Inject rol...
injecting ACLs into the  navigation view helper (registry) // ACL: Zend_View_Helper_Navigation_HelperAbstract::setDefaultA...
ACL-aware helpers can … <ul><li>can check for ACLs with  hasAcl()
can check for roles with  hasRole()
check against the “resource” and “privilege” registered with a page </li><ul><li>If either is missing, the check is skipped
If the ACL denies access, all  sub-pages will be omitted as well </li></ul></ul>
rendering links to pages // Find page, and render link $page   =   $this ->navigation()->findOneByLabel( 'Foo' ); echo   $...
rendering ACL-aware links // Conditionally render based on ACLs echo   (( $this ->navigation()->accept( $page )) ?   $this...
sitemap.xml Generates output compatible with the  Sitemaps XML Format , used by search engines to determine what pages on ...
rendering a sitemap echo   $this ->navigation()->sitemap();
configuring the sitemap helper <ul><li>Indicate that output should be &quot;formatted&quot; (adds whitespace for readabili...
Indicate whether or not to include the XML declaration:  setUseXmlDeclaration($flag) (true)
Indicate a custom URL to prepend to links:  setServerUrl($string)
Indicate the maximum depth to render:  setMaxDepth($depth) </li></ul>
sitemap-specific page metadata <ul><li>lastmod </li><ul><li>Date of last modification of the file
Outputs using W3C Datetime format; time may be omitted </li></ul><li>changefreq </li><ul><li>How frequently the page is li...
Values: always, hourly, daily, weekly, monthly, yearly, never </li></ul><li>priority </li><ul><li>Priority of this URL rel...
Scale of 0.0 to 1.0 (1.0 being highest) </li></ul></ul>
breadcrumbs Breadcrumbs indicate the ancestry of the current page within the sitemap.
rendering breadcrumbs echo   $this ->navigation()->breadcrumbs();
configuring the breadcrumbs helper <ul><li>indentation:  setIndent($spaces)
Upcoming SlideShare
Loading in...5
×

Zend Framework Workshop

27,984

Published on

Workshop delivered by Rob Allen and Matthew Weier O'Phinney for the 2010 Dutch PHP Conference. Covers a number of different patterns and common paradigms found in ZF applications, ranging from ACLs to Navigation, routing to context switching, and more.

Published in: Technology
1 Comment
33 Likes
Statistics
Notes
  • thanks for posting the slides Matthew, was a great + informative workshop. I posted my notes from the workshop day over at http://www.simonrjones.net/2010/06/zend-framework-application-patterns-dpc10/ which may be of interest to people.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
27,984
On Slideshare
0
From Embeds
0
Number of Embeds
25
Actions
Shares
0
Downloads
451
Comments
1
Likes
33
Embeds 0
No embeds

No notes for slide
  • Been working on a new design for 6 months. Host of new features that old did not have. Look and feel have been reinvented New concepts in place like member directory, online calendar, online map – both using tech from Google Info more easily updated Special pags - Committee pages, ministry pages, youth pages, missions pages Home page that gives quick access to current news, information, and links Archives section to store video, audio, documents, images that can be searched Members area for sensitive information Possible Email Newsletter &amp; Photo slideshows
  • Been working on a new design for 6 months. Host of new features that old did not have. Look and feel have been reinvented New concepts in place like member directory, online calendar, online map – both using tech from Google Info more easily updated Special pags - Committee pages, ministry pages, youth pages, missions pages Home page that gives quick access to current news, information, and links Archives section to store video, audio, documents, images that can be searched Members area for sensitive information Possible Email Newsletter &amp; Photo slideshows
  • Been working on a new design for 6 months. Host of new features that old did not have. Look and feel have been reinvented New concepts in place like member directory, online calendar, online map – both using tech from Google Info more easily updated Special pags - Committee pages, ministry pages, youth pages, missions pages Home page that gives quick access to current news, information, and links Archives section to store video, audio, documents, images that can be searched Members area for sensitive information Possible Email Newsletter &amp; Photo slideshows
  • Been working on a new design for 6 months. Host of new features that old did not have. Look and feel have been reinvented New concepts in place like member directory, online calendar, online map – both using tech from Google Info more easily updated Special pags - Committee pages, ministry pages, youth pages, missions pages Home page that gives quick access to current news, information, and links Archives section to store video, audio, documents, images that can be searched Members area for sensitive information Possible Email Newsletter &amp; Photo slideshows
  • Been working on a new design for 6 months. Host of new features that old did not have. Look and feel have been reinvented New concepts in place like member directory, online calendar, online map – both using tech from Google Info more easily updated Special pags - Committee pages, ministry pages, youth pages, missions pages Home page that gives quick access to current news, information, and links Archives section to store video, audio, documents, images that can be searched Members area for sensitive information Possible Email Newsletter &amp; Photo slideshows
  • I want to encourage you to use the the Zend_Application resources whenever you can. It makes life easier! Also consistent with other people&apos;s code
  • Bootstrap is where we do initialization Create a method that starts with _init. Zend_Application runs them in order Front end and backend adapters both need options array for bothLifetime is length of time to cache - 2 hours in this caseFactory design pattern used. Returns a Front End adapter of type Core
  • Zend_Cache_Manager was designed as a &amp;quot;Lazy Loader&amp;quot;. You set your configuration for caches on it, and instantiate them later using Zend_Cache_Manager::getCache() if needed. Otherwise they are not instantiated.
  • Simplest usable form I could find! There&apos;s an H1, Then the form itself has 2 elements: 1. Password: label, element, description 2. Submit button: just element
  • This is the generated HTML Yes it is easy to style!
  • This is the generated HTML Yes it is easy to style!
  • This is the generated HTML Yes it is easy to style!
  • This is the generated HTML Yes it is easy to style!
  • This is the generated HTML Yes it is easy to style!
  • This is the generated HTML Yes it is easy to style!
  • Zend Framework Workshop

    1. 1. 10 June 2010, RAI, Amsterdam Zend Framework Workshop
    2. 2. Today's Roadmap
    3. 3. what we hope to cover <ul><li>Service Layers, including ACLs and Paginators
    4. 4. Application resources
    5. 5. Routing clinic
    6. 6. Enhanced views
    7. 7. Navigation
    8. 8. Caching
    9. 9. Context switching
    10. 10. Form decorators </li></ul>
    11. 11. Service Layers or: separating your concerns
    12. 12. Applications are like onions; they have layers Photo © 2008, Mike Chaput-Branson
    13. 13. service layer in perspective Data Access Objects and data stores Data Mappers, Repositories, and Transaction Scripts Domain Models and Entities Service Layer
    14. 14. use Plain Old PHP Objects class User { public function getId() {} public function setId( $value ) {} public function getRealname() {} public function setRealname( $value ) {} public function getEmail() {} public function setEmail( $value ) {} }
    15. 15. use 3 rd party code via composition use ZendValidatorValidationChain; class Entry { public function setValidator(ValidationChain $chain ) { $this ->_validator = $chain ; return $this ; } public function fromArray( array $data ) { if (! $this ->getValidator()->isValid( $data )) { throw new Exception( 'Invalid data!' ); } // ... } }
    16. 16. Define a schema based on the objects you use (http://musicbrainz.org/)
    17. 17. use mappers or transaction scripts to translate objects to data & back $user = new User(); $user ->setId( 'matthew' ) ->setName( &quot;Matthew Weier O'Phinney&quot; ); $mapper ->save( $user ); $user = $repository ->find( 'matthew' );
    18. 18. use service objects to manipulate entities namespace BlogService; class Entries { public function fetchEntry( $permalink ) {} public function fetchCommentCount( $permalink ) {} public function fetchComments( $permalink ) {} public function fetchTrackbacks( $permalink ) {} public function addComment( $permalink , array $comment ) {} public function addTrackback( $permalink , array $comment ) {} public function fetchTagCloud() {} }
    19. 19. what's found in Service Layers? <ul><li>Resource marshalling and Dependency Injection
    20. 20. Application-specific logic: </li><ul><li>Authentication and Authorization (ACLs)
    21. 21. Input filtering/data validation
    22. 22. Search indexing
    23. 23. Caching
    24. 24. etc. </li></ul></ul>
    25. 25. ACL basics
    26. 26. Authorization is the act of determining if somebody has permissions to perform an action on a given resource .
    27. 27. the three “R”s <ul><li>Roles – what requests the action
    28. 28. Resources – what is being acted upon
    29. 29. Rights – the privileges a role has for a given resource </li></ul>
    30. 30. roles <ul><li>Implement Zend_Acl_Role_Interface </li><ul><li>One method: getRoleId() </li></ul><li>Can simply use Zend_Acl_Role, but this doesn't tie directly to your models </li></ul>
    31. 31. a “user” role class User implements Zend_Acl_Role_Interface { // ... protected $_role = 'pig' ; public function getRoleId() { return $this ->_role; } }
    32. 32. resources <ul><li>Implement Zend_Acl_Resource_Interface </li><ul><li>One method: getResourceId() </li></ul><li>Can simply use Zend_Acl_Resource , but this doesn't tie directly to your models </li></ul>
    33. 33. an “entry” resource class Entry implements Zend_Acl_Resource_Interface { // ... public function getResourceId() { return 'entry' ; } }
    34. 34. rights <ul><li>Zend_Acl denies all access by default
    35. 35. You must whitelist role access to resources
    36. 36. You can also blacklist, though this is less common and less secure </li></ul>
    37. 37. defining an ACL class Acl extends Zend_Acl { public function __construct() { $this ->addRole( 'anonymous' ) ->addRole( 'user' ) ->addRole( 'author' , array ( 'user' )); $this ->add( 'comment' ) ->add( 'entry' ); $this ->allow( 'user' , 'comment' , array ( 'write' )) ->allow( 'author' , 'entry' , array ( 'write' )); } }
    38. 38. checking ACLs <ul><li>Remember the three “R”s; check that: </li><ul><li>The Role
    39. 39. acting on the Resource
    40. 40. has Rights to perform the action </li></ul><li>Use the isAllowed() method </li></ul>
    41. 41. service layer ACL verification class EntryService { public function create( array $data ) { $entry = new Entry; $entry ->fromArray( $data ); $acl = $this ->getAcl(); $user = $this ->getCurrentUser(); if (! $acl ->isAllowed( $user , $entry , 'write' )) { throw new InvalidUserException(); } // ... } }
    42. 42. Paginators
    43. 43. return paginators from your domain models <ul><li>Consumers do not need to be aware of data format
    44. 44. Consumers can provide offset and limit
    45. 45. Consumers can decide how to cast </li><ul><li>Zend_Paginator implements IteratorAggregate , toJson() </li></ul><li>Most paginator adapters will only operate once results are requested </li></ul>
    46. 46. Zend_Paginator basics <ul><li>setCurrentPageNumber($page)
    47. 47. setItemCountPerPage($int)
    48. 48. setPageRange($int) (number of pages to show in paginator)
    49. 49. Constructor accepts an adapter capable of generating a count() and returning items based on an offset and item count </li></ul>
    50. 50. repository returning paginator public function fetchAll() { $select = $this ->getDbTable()->select() ->where( 'disabled = ?' , 0); $paginator = new Zend_Paginator( new Zend_Paginator_Adapter_DbSelect( $select ) ); return $paginator ; }
    51. 51. paginator use in view script $this ->users->setItemCountPerPage( $this ->numItems) ->setCurrentPageNumber( $this ->page); echo $this ->users->toJson();
    52. 52. going generic <ul><li>Alternately, define a “collection” type </li><ul><li>e.g., to accept a MongoCursor
    53. 53. Implement Countable, and Iterator ; optionally also some serialization interfaces
    54. 54. Define methods like skip() or page(), and sort() and setItemClass() </li></ul></ul>
    55. 55. mapper returning generic collection public function find( array $query , array $fields = array () ) { $query = $this ->_formatQuery( $query ); // mongo collection: $cursor = $this ->getCollection() ->find( $query , $fields ); // paginator: $collection = new Collection( $cursor ); $collection ->setItemClass( 'Entry' ); return $collection ; }
    56. 56. collection used in view script $this ->entries->start( $this ->offset) ->limit( $this ->numItems); echo $this ->json( $this ->entries->toArray() );
    57. 57. Zend_Application
    58. 58. how it used to be done $config = new Zend_Config_Ini( $pathToConfig , 'prod' ); $db = Zend_Db::factory( $config ->db); Zend_Db_Table::setDefaultAdapter( $db ); $view = new Zend_View( $config ->view->toArray()); $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer ->setView( $view ); // etc... $front = Zend_Controller_Front::getInstance(); $front ->setConfig( $config ) ->setControllerPath( $config ->controller->path) ->dispatch();
    59. 59. what some savvy users did class Initialize extends Zend_Controller_Plugin_Abstract { public function initDb() { /* */ } public function initView() { /* */ } public function initController() { /* */ } public function routeStartup() { /* */ } public function dispatchLoopStartup() { $this ->initDb() ->initView() ->initController(); } }
    60. 60. problems with the approaches <ul><li>Limited re-usability
    61. 61. Lots of scaffolding
    62. 62. Difficult to create resources with dependencies
    63. 63. Moving from one application to another often means learning a new method of bootstrapping </li></ul>
    64. 64. what Zend_Application provides <ul><li>Common configuration methodology </li><ul><li>And per-environment configuration </li></ul><li>Reusable resources
    65. 65. Dependency tracking for resources
    66. 66. Resource injection into the application (acts as a repository) </li></ul>
    67. 67. what are resources? <ul><li>Anything you need within your application that requires configuration
    68. 68. Two types: </li><ul><li>Bootstrap-specific resources
    69. 69. Plugin resources </li></ul></ul>
    70. 70. commonalities <ul><li>Zend_Application instantiates and executes a bootstrap </li><ul><li>An “environment” is passed as well, indicating what configuration to load </li></ul><li>Bootstraps extend either Zend_Application_Bootstrap_Bootstrap or Zend_Application_Module_Bootstrap
    71. 71. You then selectively bootstrap resources, or bootstrap the entire application
    72. 72. Finally, you optionally run() the app </li></ul>
    73. 73. bootstrap resources <ul><li>Any protected method beginning with “_init” is considered a resource; it's name is whatever follows that string, lowercased: </li><ul><li>_initMongodb() is the “mongodb” resource </li></ul></ul>
    74. 74. configuration we'll use [production] mongodb.server = &quot;appserver&quot; mongodb.dbname = &quot;site&quot; [testing] mongodb.server = &quot;localhost&quot; mongodb.dbname = &quot;testing&quot; [development] mongodb.server = &quot;localhost&quot; mongodb.dbname = &quot;blog&quot;
    75. 75. example bootstrap resource class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { protected function _ initMongodb() { $options = $this ->getOption( 'mongodb' ); $mongo = new Mongo( $options [ 'server' ]); $db = $mongo ->selectDB( $options [ 'dbname' ]); return $db ; } }
    76. 76. plugin resources <ul><li>Implement Zend_Application_Resource_Resource </li><ul><li>Defines an init() method </li></ul><li>Resource name is everything following a plugin prefix: Wopnet_Resource_Mongodb becomes the “mongodb” resource </li><ul><li>Register plugin prefixes with the application configuration </li></ul></ul>
    77. 77. example plugin resource class Wopnet_Resource_Mongodb extends Zend_Application_Resource_ResourceAbstract { protected $_options = array ( 'server' => 'localhost' , 'dbname' => 'test' , ); public function init() { $options = $this ->getOptions(); $mongo = new Mongo( $options [ 'server' ]); $db = $mongo ->selectDB( $options [ 'dbname' ]); return $db ; } }
    78. 78. invoking the application $application = new Zend_Application( 'production' , '/path/to/application.ini' ); $application ->bootstrap() ->run();
    79. 79. re-using it for a service endpoint $application = new Zend_Application( 'jsonrpc' , '/path/to/application.ini' ); $application ->bootstrap(); $mongo = $application ->getResource( 'mongodb' ); // ...
    80. 80. dependency tracking <ul><li>Some resources are dependent on others executing
    81. 81. bootstrap($resource) ensures that that resource is only bootstrapped once
    82. 82. getResource($resource) will return whatever that resource method/plugin returns </li></ul>
    83. 83. dependency example public function _initRoutes() { $this ->bootstrap( 'frontcontroller' ); $fc = $this ->getResource( 'frontcontroller' ); $router = $fc ->getRouter(); // ... }
    84. 84. how do I use resources elsewhere? <ul><li>Bootstrap acts as a registry
    85. 85. Bootstrap is injected into the front controller as a parameter </li></ul>
    86. 86. retrieving resources // from anywhere: $fc = Zend_Controller_Front::getInstance(); $bootstrap = $fc ->getParam( 'bootstrap' ); $mongodb = $bootstrap ->getResource( 'mongodb' ); // from action controller: public function preDispatch() { $boot = $this ->getInvokeArg( 'bootstrap' ); $this ->mongodb = $boot ->getResource( 'mongodb' ); }
    87. 87. retrieving configuration [production] lucene.basepath = &quot;/data/search/&quot; public function searchAction() { $boot = $this ->getInvokeArg( 'bootstrap' ); $options = $boot ->getOption( 'lucene' ); $lucene = Zend_Search_Lucene::open( $options [ 'basepath' ] ); // ... }
    88. 88. Routing Clinic
    89. 89. basics <ul><li>Name the route: “blog-entry”, “rest”, etc.
    90. 90. Specify the path to match: “blog”, “blog/:id”, etc.
    91. 91. Specify default values to pass to the request object: module, controller, action, etc.
    92. 92. Optionally, specify validations: “id” must match “d+” </li></ul>
    93. 93. static route use Zend_Controller_Router_Route_Static as StaticRoute; $entries = new StaticRoute( 'blog' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'index' , ) );
    94. 94. standard (named) route use Zend_Controller_Router_Route as StandardRoute; $entry = new StandardRoute( 'blog/:id' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'entry' , ) );
    95. 95. regex route use Zend_Controller_Router_Route_Regex as RegexRoute; $contexts = new RegexRoute( 'blog/(?<id>[^.]*+).(?<format>xml|json)' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'entry' , ) );
    96. 96. hostname route use Zend_Controller_Router_Route_Hostname as HostnameRoute; $hostnameRoute = new HostnameRoute( ':author.example.com' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'by-author' , ) );
    97. 97. route chaining // Create chain of &quot;hostname&quot; route, // &quot;entry&quot; route, and &quot;contexts&quot; route // and &quot;contexts&quot; route: $contexts = new RegexRoute( '(?<format>xml|json)' ); $chain = $hostname ->chain( $entry ); $chain ->chain( $contexts , '.' ); $router ->addRoute( 'author-rest-entry' , $chain );
    98. 98. RESTful routes <ul><li>HTTP GET , no arguments: list /resource
    99. 99. HTTP POST , no arguments: create /resource + RAW POST
    100. 100. HTTP GET , 1 argument: item /resource/:id
    101. 101. HTTP PUT , 1 argument: update /resource/:id + RAW PUT
    102. 102. HTTP DELETE , 1 argument: delete /resource/:id </li></ul>
    103. 103. defining all routes RESTful // Enable for all controllers $front = Zend_Controller_Front::getInstance(); $restRoute = new Zend_Rest_Route( $front ); $front ->getRouter()->addRoute( 'default' , $restRoute );
    104. 104. defining select modules as RESTful // Enable for blog module only $front = Zend_Controller_Front::getInstance(); $restRoute = new Zend_Rest_Route( $front , array (), array ( 'blog' ) ); $front ->getRouter()->addRoute( 'rest' , $restRoute );
    105. 105. Defining select controllers as RESTful // Enable for specific controllers only $front = Zend_Controller_Front::getInstance(); $restRoute = new Zend_Rest_Route( $front , array (), array ( 'blog' => array ( 'comment' , 'trackback' , ), ) ); $front ->getRouter()->addRoute( 'rest' , $restRoute );
    106. 106. sample REST controller class EntryController extends Zend_Rest_Controller { public function index Action() { } public function postAction() { } public function getAction() { } public function putAction() { } public function deleteAction() { } }
    107. 107. defining routes in the bootstrap protected function _initRoutes() { $this ->bootstrap( 'frontcontroller' ); $fc = $this ->getResource( 'frontcontroller' ); $router = $fc ->getRouter(); $entries = new StaticRoute( 'blog/entries' , array ( 'module' => 'blog' , 'controller' => 'entries' , 'action' => 'index' , ) ); // ... }
    108. 108. or via configuration [production] resources.router.routes.entries.type = &quot;Zend_Controller_Router_Route_Static&quot; resources.router.routes.entries.route = &quot;blog/entries&quot; resources.router.routes.entries.defaults.module = &quot;blog&quot; resources.router.routes.entries.defaults.controller = &quot;entries&quot; resources.router.routes.entries.defaults.action = &quot;index&quot;
    109. 109. Layout View Helpers
    110. 110. layout related view helpers <ul><li>Set metadata, JS & CSS within action view scripts
    111. 111. Aggregate content
    112. 112. Rendered in the layout </li></ul>
    113. 113. doctype() <ul><li>Call before the first view script is rendered
    114. 114. Affects rendering of other view helpers, such as forms </li></ul>
    115. 115. setting the doctype <!-- in layouts/scripts/layout.phtml --> <?php echo $this ->doctype(); ?> ;application.ini resources.view.doctype = XHTML1_STRICT
    116. 116. the head() view helpers <ul><li>Used for setting all the <head> tags
    117. 117. Can append and prepend
    118. 118. Available: </li><ul><li>headMeta()
    119. 119. headTitle()
    120. 120. headScript()
    121. 121. headStyle() </li></ul></ul>
    122. 122. the inline() view helpers <ul><li>Used for setting inline CSS within <head> & inline JS just before </body>
    123. 123. Available: </li><ul><li>inlineStyle()
    124. 124. inlineScript() </li></ul></ul>
    125. 125. set in layout before rendering <?php $this ->headMeta()->prependHttpEquiv( 'Content-Type' , 'text/html;charset=utf-8' ); $this ->headTitle( 'Blog' )->setSeparator( ' - ' ); echo $this ->doctype(); ?> <html xmlns = &quot;http://www.w3.org/1999/xhtml&quot; > <head> <?php echo $this ->headMeta(); ?> <?php echo $this ->headTitle(); ?> </head>
    126. 126. multiple layout scripts? use a view helper! class Zend_View_Helper_HeadSection extends Zend_View_Helper_Abstract { public function headSection() { $view = $this ->view; $view ->headMeta()->appendHttpEquiv( 'Content-Type' , 'text/html;charset=utf-8' ); $view ->headTitle( 'My Blog' ) ->setSeparator( ' - ' ); $html = $view ->headMeta(); $html .= $view ->headTitle(); return $html ; } }
    127. 127. all layout script files <?php echo $this ->doctype(); ?> <html xmlns = &quot;http://www.w3.org/1999/xhtml&quot; > <head> <?php echo $this ->headSection(); ?> </head>
    128. 128. Adding CSS/JS <ul><li>Some actions need JS/CSS included in <head> </li><ul><li>or JS just before </body> </li></ul><li>Place this code in action's view script </li></ul>
    129. 129. action specific CSS // in product/index.phtml $this ->headLink()->appendStylesheet( $this ->baseUrl() . '/css/product.css' , 'screen,print' ); <!-- layout.phtml --> <?php echo $this ->headLink() ->prependStylesheet( $this ->baseUrl() . '/css/site.css' , 'screen,print' ); ?>
    130. 130. inline JS: action view script $script = <<<EOT $(document).ready(function() { $(&quot;a[rel^='prettyPhoto']&quot;).prettyPhoto(); $(&quot;.toolbar a&quot;).hover(function(){ $(this).addClass('ui-state-hover'); }, function(){ $(this).removeClass('ui-state-hover'); }); }); EOT; $this ->inlineScript()->appendScript( $script );
    131. 131. inline JS: layout view script <!-- ... --> <?php echo $this ->inlineScript(); ?> </body>
    132. 132. Navigation
    133. 133. use cases Zend_Navigation is a component for managing trees of pointers to web pages .
    134. 134. use cases <ul><li>Search Engine Optimization (SEO) </li><ul><li>Sitemap and links enhance SEO for a site </li></ul><li>Site Usability </li><ul><li>Navigation elements such as breadcrumbs and menus make traversing a site easier for end-users </li></ul></ul>
    135. 135. Navigation: features
    136. 136. menus
    137. 137. breadcrumbs
    138. 138. header links <ul><li>Provide hints to page regarding pages relative to current page </li><ul><li>&quot;Start&quot; or &quot;Home&quot; page
    139. 139. &quot;Next&quot; or &quot;Previous&quot; pages in same depth
    140. 140. &quot;Chapters&quot; or &quot;Sub-Sections&quot; in this area
    141. 141. Alternate formats for this content </li></ul></ul>
    142. 142. sitemaps
    143. 143. body links <ul><li>Render links to related content </li><ul><li>previous/next page
    144. 144. parent
    145. 145. children </li></ul><li>Translate link text
    146. 146. Conditionally display links based on ACLs </li></ul>
    147. 147. Navigation: pages and containers
    148. 148. pages An object that holds a pointer to a web page .
    149. 149. Zend_Navigation_Page metadata <ul><li>label
    150. 150. title
    151. 151. target
    152. 152. rel
    153. 153. order
    154. 154. active
    155. 155. visible
    156. 156. ACL info (resource, privilege) </li></ul>
    157. 157. page types <ul><li>MVC </li><ul><li>Integration with the router for URL generation </li></ul><li>URI </li><ul><li>Specify specific URI for the page </li></ul></ul>
    158. 158. creating MVC pages $page = new Zend_Navigation_Page_Mvc(); $page ->title = &quot;Foo&quot; ; $page ->controller = &quot;foo&quot; ; $page ->action = &quot;index&quot; ; $page ->route = &quot;default&quot; ;
    159. 159. creating URI pages $page = new Zend_Navigation_Page_Uri(); $page ->title = &quot;Foo&quot; ; $page ->uri = &quot;/foo&quot; ;
    160. 160. using the page factory <ul><li>Zend_Navigation_Page::factory()
    161. 161. Accepts array or Zend_Config object
    162. 162. Rules </li><ul><li>if 'uri' provided, and no MVC options, URI page created
    163. 163. if any MVC options (action, controller, module, route), MVC page created
    164. 164. if 'type' provided, assumes this is a Page class to use </li><ul><li>can be &quot;mvc&quot; or &quot;uri&quot; </li></ul></ul></ul>
    165. 165. containers A container for pages.
    166. 166. Zend_Navigation_Container <ul><li>Implements RecursiveIterator (iterate entire tree)
    167. 167. Implements Countable (get total count of pages)
    168. 168. Zend_Navigation_Page is a container </li><ul><li>Allows having trees of pages </li></ul></ul>
    169. 169. adding a page to a container // addPage() $container ->addPage( $page ); // addPage() using factory $container ->addPage( Zend_Navigation_Page::factory( array ( 'uri' => 'http://www.example.com/' , ) ));
    170. 170. adding a page to a container (cont) // addPage() using an array: $container ->addPage( array ( 'uri' => 'http://www.example.com/' , )); // addPage() using a Zend_Config object: $container ->addPage( $config ->page);
    171. 171. adding multiple pages at once // addPages() using an array: $container ->addPages( $array ); // addPages() using a Zend_Config object: $container ->addPages( $config );
    172. 172. removing pages from a container // removePage(): // Accepts a page instance, or an integer // indicating value of order metadatum within // container $container ->removePage( $page ); $container ->removePage(0); $container ->removePage(20); // where order => 20 // Remove all pages: $container ->removePages();
    173. 173. finding pages in a container <ul><li>All finder methods return containers </li><ul><li>So you can iterate, count, etc. </li></ul></ul>
    174. 174. finding pages in a container // findOneBy($property, $value) $page = $container ->findOneBy( 'label' , 'Zend Framework' ); // findAllBy($property, $value) $pages = $container ->findAllBy( 'tag' , 'zendframework' ); // Combine property name with method: $page = $container ->findOneByLabel( 'Zend Framework' ); $pages = $container ->findAllByTag( 'zendframework' );
    175. 175. iteration of pages (flat) foreach ( $container as $page ) { echo $page ->label, &quot;<br /> &quot; ; }
    176. 176. iteration of pages (recursive) $it = new RecursiveIteratorIterator( $container , RecursiveIteratorIterator::SELF_FIRST ); foreach ( $it as $page ) { echo $page ->label, &quot;<br /> &quot; ; }
    177. 177. container utility methods <ul><li>hasPage(Zend_Navigation_Page $page) </li><ul><li>Does the container have the given page?
    178. 178. if ($container->hasPage($page)) { /* do something */ } </li></ul><li>hasPages() </li><ul><li>Does the container have *any* pages?
    179. 179. Equivalent to count($container) > 1 </li></ul><li>toArray() </li><ul><li>Serialize container to array structure </li></ul></ul>
    180. 180. methods for defining pages <ul><li>Programmatically
    181. 181. Using arrays
    182. 182. Using Zend_Config values </li></ul>
    183. 183. strategies for defining pages <ul><li>Cache the definitions </li><ul><li>First time through: </li><ul><li>Create programmatically or from Zend_Config
    184. 184. Cache to array using toArray() </li></ul><li>Subsequent times: </li><ul><li>Load from cached array </li></ul></ul><li>Use only when needed </li><ul><li>Reduces performance hit
    185. 185. Disable on XHR calls, service calls, etc. </li></ul></ul>
    186. 186. Navigation: using the view helpers
    187. 187. Zend_View_Helper_Navigation <ul><li>Holds a container
    188. 188. Proxies to other Navigation view helpers
    189. 189. Holds Translator and Acl objects </li></ul>
    190. 190. adding a container to the view helper // Directly $view ->getHelper( 'navigation' )->setContainer( $container ); // Via &quot;method call&quot; $view ->navigation( $container ); // Via registry Zend_Registry::set( 'Zend_Navigation' , $container );
    191. 191. injecting ACLs into the navigation view helper (direct) // Inject ACL $view ->navigation()->setAcl( $acl ); // Inject role // $role may be either a string role name, // or an object implementing // Zend_Acl_Role_Interface $view ->navigation()->setRole( $role );
    192. 192. injecting ACLs into the navigation view helper (registry) // ACL: Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl( $acl ); // Role: Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole( $role );
    193. 193. ACL-aware helpers can … <ul><li>can check for ACLs with hasAcl()
    194. 194. can check for roles with hasRole()
    195. 195. check against the “resource” and “privilege” registered with a page </li><ul><li>If either is missing, the check is skipped
    196. 196. If the ACL denies access, all sub-pages will be omitted as well </li></ul></ul>
    197. 197. rendering links to pages // Find page, and render link $page = $this ->navigation()->findOneByLabel( 'Foo' ); echo $this ->navigation()->htmlify( $page );
    198. 198. rendering ACL-aware links // Conditionally render based on ACLs echo (( $this ->navigation()->accept( $page )) ? $this ->navigation()->htmlify( $page ) : '' );
    199. 199. sitemap.xml Generates output compatible with the Sitemaps XML Format , used by search engines to determine what pages on a site to crawl and index, and when to update this information.
    200. 200. rendering a sitemap echo $this ->navigation()->sitemap();
    201. 201. configuring the sitemap helper <ul><li>Indicate that output should be &quot;formatted&quot; (adds whitespace for readability): setFormatOutput($flag) (false)
    202. 202. Indicate whether or not to include the XML declaration: setUseXmlDeclaration($flag) (true)
    203. 203. Indicate a custom URL to prepend to links: setServerUrl($string)
    204. 204. Indicate the maximum depth to render: setMaxDepth($depth) </li></ul>
    205. 205. sitemap-specific page metadata <ul><li>lastmod </li><ul><li>Date of last modification of the file
    206. 206. Outputs using W3C Datetime format; time may be omitted </li></ul><li>changefreq </li><ul><li>How frequently the page is likely to change
    207. 207. Values: always, hourly, daily, weekly, monthly, yearly, never </li></ul><li>priority </li><ul><li>Priority of this URL relative to others on the site
    208. 208. Scale of 0.0 to 1.0 (1.0 being highest) </li></ul></ul>
    209. 209. breadcrumbs Breadcrumbs indicate the ancestry of the current page within the sitemap.
    210. 210. rendering breadcrumbs echo $this ->navigation()->breadcrumbs();
    211. 211. configuring the breadcrumbs helper <ul><li>indentation: setIndent($spaces)
    212. 212. set minimum breadcrumb depth: setMinDepth($depth) </li><ul><li>set to 1 (default) to omit root page
    213. 213. set to 0 to include root page </li></ul><li>set maximum breadcrumb depth: setMaxDepth($depth)
    214. 214. set breadcrumbs separator: setSeparator($separator)
    215. 215. indicate whether to link last crumb in trail: setLinkLast($flag)
    216. 216. indicate a view partial to use when rendering: setPartial($script) </li></ul>
    217. 217. menus Renders trees of links for use as navigational menus. Typically these will be unordered lists (UL elements) that you will then style with CSS to create a menu.
    218. 218. configuring the menu helper <ul><li>Set the class for the UL element: setUlClass($class)
    219. 219. Indicate whether to render only pages and branches marked &quot;active&quot;: setOnlyActiveBranch($flag)
    220. 220. Indicate whether to render parents when only rendering active branches: setRenderParents($flag)
    221. 221. Indicate a partial script to use to render the menu: setPartial($script) </li></ul>
    222. 222. calling renderMenu() <ul><li>Takes options array as second argument
    223. 223. Options (in addition to setters): </li><ul><li>indent : either a string to indent with, or an integer number of spaces
    224. 224. minDepth : minimum depth to render; null indicates no minimum
    225. 225. maxDepth : maximum depth to render; null indicates no maximum </li></ul></ul>
    226. 226. rendering menus <ul><li>By default, render the entire menu
    227. 227. renderMenu() and renderSubMenu() allow specifying trees </li><ul><li>&quot;null&quot; argument will grab from active root node
    228. 228. otherwise pass in a Page , and that tree will be used </li></ul></ul>
    229. 229. rendering menus: trees // Full tree echo $this ->navigation()->menu(); // Selective tree $subtree = $this ->navigation()->findOneByLabel( 'Zend Framework' ); $options = array ( 'ulClass' => 'subtree' , ); echo $this ->navigation()->menu()->renderMenu( $subtree , $options );
    230. 230. Rendering menus: deepest active menu // Deepest active menu echo $this ->navigation()->menu() ->renderSubMenu(); // Equivalent to: echo $this ->navigation()->menu() ->renderMenu( null, array ( 'minDepth' => null, 'maxDepth' => null, 'onlyActiveBranch' => true, 'renderParents' => false, ) );
    231. 231. links Renders LINK elements that describe document relationships of the currently active page.
    232. 232. link-specific metadata $page = new Zend_Navigation_Page_Mvc( array ( 'rel' => array ( 'alternate' => array ( 'label' => 'Example.org' , 'uri' => 'http://www.example.org' , ), 'next' => array ( 'label' => 'Foo' , 'uri' => '/foo' , ), 'prev' => array ( 'label' => 'Bar' , 'uri' => '/bar' , ), ), 'rev' => array ( 'alternate' => 'http://www.example.net' , ), ));
    233. 233. rendering links <ul><li>Typically from within your site layout
    234. 234. All links </li><ul><li>echo $this->navigation() ->links(); </li></ul></ul>
    235. 235. selectively rendering links $this ->navigation()->links()->setRenderFlag( Zend_View_Helper_Navigation_Links::RENDER_NEXT | Zend_View_Helper_Navigation_Links::RENDER_PREV ); echo $this ->navigation()->links();
    236. 236. Caching
    237. 237. the principle of caching
    238. 238. Zend_Cache <ul><li>Front end adapters </li><ul><li>What to cache </li></ul><li>Back end adapters </li><ul><li>Where to cache </li></ul></ul>
    239. 239. creating a Zend_Cache object function _initCache() { $frontendOptions = array ( 'lifetime' => '7200' , 'automatic_serialization' => true ); $backendOptions = array ( 'cache_dir' => APPLICATION_PATH . '/../var/cache' ); $cache = Zend_Cache::factory( 'Core' , 'File' , $frontendOptions , $backendOptions ); return $cache ; }
    240. 240. however! There is a better way
    241. 241. Zend_Cache_Manager <ul><li>Manages multiple cache objects
    242. 242. Lazy loads on demand
    243. 243. Contains preconfigured caches
    244. 244. Action helper for access
    245. 245. Application resource for creation </li></ul>
    246. 246. implementing caching in an app <ul><li>Create a cache object
    247. 247. Wrap current code with cache loading code
    248. 248. Clear cache on data change </li></ul>
    249. 249. creating a cache object application.configs/application.ini: resources.cachemanager.default .frontend.options.lifetime = 7200 resources.cachemanager.default .frontend.options.automatic_serialization = true resources.cachemanager.default .backend.options.cache_dir = APPLICATION_PATH &quot;/../var/cache&quot;
    250. 250. accessing the cache object protected function _getCache() { if (! $this ->_cache) { $fc = Zend_Controller_Front::getInstance(); $cache = $fc ->getParam( 'bootstrap' ) ->getResource( 'cachemanager' ) ->getCache( 'default' ); $this ->_cache = $cache ; } return $this ->_cache; }
    251. 251. accessing the cache object // Within a controller $this ->_helper->getHelper( 'Cache' ) ->getManager() ->getCache( 'default' );
    252. 252. wrapping current code: simple case // in Application_Model_DbTable_Entries public function fetchRecent( $limit =15, $start =0 ) { $select = $this ->select(); $select ->where( 'is_draft' , 0); $select ->where( 'timestamp < ?' , $_SERVER [ 'REQUEST_TIME' ]); $select ->limit( $limit , $start ); $select ->order( array ( 'timestamp DESC' )); return $this ->fetchAll( $select ); }
    253. 253. // in Application_Model_DbTable_Entries public function fetchRecent( $limit =15, $start =0) { $cacheId = 'recentEntries' ; $cache = $this ->_getCache(); $rows = $cache ->load( $cacheId ); if ( $rows === false ) { $rows = $this ->_fetchRecent( $limit , $start ); $cache ->save( $rows , $cacheId , array ( 'entries' )); } return $rows ; } wrapping current code: simple case Move existing code to new method Tag for selective clearing
    254. 254. empty the cache // Application_Model_DbTable_Entries protected function _cleanCache() { $this ->getCache()->clean( Zend_Cache::CLEANING_MODE_MATCHING_TAG, array ( 'tasks' )); }
    255. 255. cache the entire page <ul><li>Bypass entire MVC stack
    256. 256. Super fast!
    257. 257. Implement in index.php </li></ul>
    258. 258. $frontendOptions = array ( 'lifetime' => 3600, 'default_options' => array ( 'cache' => false ), 'regexps' => array ( '^/$' => array ( 'cache' => true ), '^/entry/' => array ( 'cache' => true ), )); $backendOptions = array ( 'cache_dir' => $cacheDir ); $cache = Zend_Cache::factory( 'Page' , 'File' , $frontendOptions , $backendOptions ); $cache ->start(); page caching
    259. 259. some numbers to think about <ul><li>No caching: 12 trans/sec
    260. 260. Cache DB: 29 trans/sec
    261. 261. Cache page: 359 trans/sec </li></ul><ul><li>No caching: 19 trans/sec
    262. 262. Cache DB: 289 trans/sec
    263. 263. Cache page: 3231 trans/sec </li></ul>With APC:
    264. 264. Context Switching
    265. 265. in a nutshell... Context switching is the act of providing different output based on criteria from the request.
    266. 266. when you might use it <ul><li>Provide different output for requests originating via XMLHttpRequest
    267. 267. Provide different output based on Accept HTTP headers (e.g., REST endpoints)
    268. 268. Provide alternate layouts/content based on browser detection </li></ul>
    269. 269. how it works <ul><li>format request parameter </li><ul><li>As explicit part of request
    270. 270. Or set by a plugin that does HTTP header detection </li></ul><li>ContextSwitch action helper determines if the currently requested action has a context matching the format
    271. 271. An additional view suffix is added, meaning a different view script will be rendered </li></ul>
    272. 272. tricks and tips for getting the format parameter <ul><li>Easiest: use a query parameter
    273. 273. Easy: create a special route
    274. 274. Also easy: use chained routes
    275. 275. Reusable: detect HTTP headers via a plugin (more later) </li></ul>
    276. 276. a simple route $commentXml = new Zend_Controller_Router_Route_Static( 'blog/comment.xml' , array ( 'module' => 'blog' , 'controller' => 'comment' , 'action' => 'post' , 'format' => 'xml' , ) );
    277. 277. chained routes $xmlRoute = new Zend_Controller_Router_Route_Static( 'xml' , array ( 'format' => 'xml' ) ); $comment = new Zend_Controller_Router_Route_Static( 'blog/comment' , array ( 'module' => 'blog' , 'controller' => 'comment' , 'action' => 'post' , ) ); $comment ->chain( $xmlRoute , '.' );
    278. 278. Adding context switching to an action controller class Blog_CommentController extends Zend_Controller_Action { public function init() { $contextSwitch = $this ->_helper->getHelper( 'contextSwitch' ); $contextSwitch ->addActionContext( 'post' , 'xml' ) ->initContext(); } }
    279. 279. add view scripts per-context blog |-- views | |-- scripts | | |-- comment | | | |-- post.phtml | | | `-- post.xml.phtml
    280. 280. “html” view <?php $comment = $this ->entryUrl . '#' . $this ->comment->getId(); $response = $this ->response; $response ->setHttpResponseCode(201) ->setHeader( 'Location' , $comment );
    281. 281. “xml” view <?php $comment = $this ->entryUrl . '#' . $this ->comment->getId(); ?> <?xml version= &quot;1.0&quot; encoding= &quot;utf-8&quot; ?> <response> <status>success</status> <entry> <url><?php echo $comment ?></url> </entry> </response>
    282. 282. Case study: sitemap and robots.txt
    283. 283. the problem <ul><li>We want Google to hit our sitemap.xml
    284. 284. We want to generate our sitemap.xml using Zend Framework's Zend_Navigation component </li></ul>
    285. 285. the routes // route for /robots.txt $robots = new Zend_Controller_Router_Route_Static( 'robots.txt' , array ( 'controller' => 'index' , 'action' => 'robots' , 'format' => 'txt' )); // route for /sitemap.xml $googlesitemap = new Zend_Controller_Router_Route_Static( 'sitemap.xml' , array ( 'controller' => 'index' , 'action' => 'sitemap' , 'format' => 'xml' ));
    286. 286. context switch definitions public function init() { $this ->_helper->contextSwitch() ->addContext( 'txt' , array ( 'suffix' => 'txt' , 'headers' => array ( 'Content-Type' => 'text/plain' ) )) ->addActionContext( 'robots' , 'txt' ) ->addActionContext( 'sitemap' , 'xml' ) ->initContext(); }
    287. 287. robots.txt.phtml Sitemap: <?php echo $this ->fullUrl( array (), null, true); ?>sitemap.xml User-agent: * Disallow: /index.php?*
    288. 288. sitemap.phtml <?php echo $this ->navigation() ->menu() ->setUlClass( 'site-map' ); ?>
    289. 289. sitemap.xml.phtml <?php echo $this ->navigation() ->sitemap(); ?>
    290. 290. Advanced topics: header detection
    291. 291. Accept detection class AcceptHandler extends Zend_Controller_Plugin_Abstract { public function dispatchLoopStartup( Zend_Controller_Request_Abstract $request ) { $this ->getResponse()->setHeader( 'Vary' , 'Accept' ); $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 ; default : break ; } } }
    292. 292. Content-Type detection class Params extends Zend_Controller_Action_Helper_Abstract { public function init() { $request = $this ->getRequest(); $contentType = $request ->getHeader( 'Content-Type' ); $rawBody = $request ->getRawBody(); if (! $rawBody ) { return ; }
    293. 293. Content-Type detection (cont.) switch (true) { case (strstr( $contentType , 'application/json' )): $this ->setBodyParams( Zend_Json::decode( $rawBody )); break ; case (strstr( $contentType , 'application/xml' )): $config = new Zend_Config_Xml( $rawBody ); $this ->setBodyParams( $config ->toArray()); break ; default : if ( $request ->isPut()) { parse_str( $rawBody , $params ); $this ->setBodyParams( $params ); } break ; }
    294. 294. Content-Type detection (cont.) public function getSubmitParams() { if ( $this ->hasBodyParams()) { return $this ->getBodyParams(); } return $this ->getRequest()->getPost(); } public function direct() { return $this ->getSubmitParams(); }
    295. 295. using the helper public function postAction() { $params = $this ->_helper->params(); if (! $item = $this ->service->create( $params )) { // ... } // ... }
    296. 296. automating REST contexts class RestContexts extends Zend_Controller_Action_Helper_Abstract { protected $_contexts = array ( 'xml' , 'json' ); public function preDispatch() { $controller = $this ->getActionController(); if (! $controller instanceof Zend_Rest_Controller ) { return ; }
    297. 297. automating REST contexts (cont.) $cs = $this ->getActionController() ->contextSwitch; $cs ->setAutoJsonSerialization(false); foreach ( $this ->_contexts as $context ) { foreach ( array ( 'index' , 'post' , 'get' , 'put' , 'delete' ) as $action ) { $cs ->addActionContext( $action , $context ); } } $cs ->initContext();
    298. 298. inject the request and response into the view object use Zend_Controller_Action_HelperBroker as HelperBroker; class InjectRequestResponse extends Zend_Controller_Plugin_Abstract { public function dispatchLoopStartup( Zend_Controller_Request_Abstract $request ) { $vr = HelperBroker::getStaticHelper( 'ViewRenderer' ); $vr ->view->assign( array ( 'request' => $request , 'response' => $this ->getResponse(), )); } }
    299. 299. automating scaffolding: configuration resources.frontcontroller.plugins[] = &quot;Accept&quot; resources.frontcontroller.plugins[] = &quot;InjectRequestResponse&quot;
    300. 300. automating scaffolding: bootstrap use Zend_Controller_Action_HelperBroker as HelperBroker; class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { public function _initContexts() { $this ->bootstrap( 'frontcontroller' ); $params = new Params(); $restContexts = new RestContexts(); HelperBroker::addHelper( $params ); HelperBroker::addHelper( $restContexts ); } }
    301. 301. putting it together: Blog_EntryController::postAction() public function postAction() { $data = $this ->params(); $service = $this ->getService(); $result = $service ->add( $data ); if (! $result ) { $this ->view->form = $service ->getForm(); return ; } $this ->view->success = true; $this ->view->entry = $result ; }
    302. 302. putting it together: entry/post.phtml <?php if ( $this ->success): $this ->response->setRedirect( $this ->url( array ( 'id' => $this ->entry->getId(), ), 'entry' , true)); else : ?> <h2>Create new entry</h2> <?php $this ->form->setAction( $this ->url()) ->setMethod( 'post' ); echo $this ->form; endif ?>
    303. 303. putting it together: entry/post.json.phtml <?php if ( $this ->success) { $url = $this ->url( array ( 'id' => $this ->entry->getId(), ), 'entry' , true); $this ->response->setHeader( 'Location' , $url ) ->setHttpResponseCode(201); echo $this ->json( $this ->entry->toArray()); return ; } $form = $this ->form; $form ->setAction( $this ->url()) ->setMethod( 'post' ); echo $this ->jsonFormErrors( $form );
    304. 304. more uses for ContextSwitch <ul><li>Mobile browser detection
    305. 305. Search-engine robot detection
    306. 306. Re-using MVC endpoints for service endpoints (JSON-RPC, SOAP, etc.)
    307. 307. etc. </li></ul>
    308. 308. Form Decorators
    309. 309. decorators <ul><li>Render elements & forms
    310. 310. Combination Decorator and Strategy pattern; decorates string content using element and form metadata
    311. 311. Decorators stack from inside to outside </li></ul>
    312. 312. how it works
    313. 313. a typical form
    314. 314. HTML for an element <dt id = &quot;email-label&quot; > <label for = &quot;email&quot; class = &quot;required&quot; > Your email </label> </dt> <dd id = &quot;email-element&quot; > <input type = &quot;text&quot; name = &quot;email&quot; id = &quot;email&quot; value = &quot;asdf&quot; > <ul class = &quot;errors&quot; > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = &quot;description&quot; > Your registered email address </p> </dd>
    315. 315. Default element decorators <ul><li>Zend_Form_Decorator_ ViewHelper
    316. 316. Zend_Form_Decorator_ Errors
    317. 317. Zend_Form_Decorator_ Description
    318. 318. Zend_Form_Decorator_ HtmlTag
    319. 319. Zend_Form_Decorator_ Label </li></ul>
    320. 320. ViewHelper <dt id = &quot;email-label&quot; > <label for = &quot;email&quot; class = &quot;required&quot; > Your email </label> </dt> <dd id = &quot;email-element&quot; > <input type = &quot;text&quot; name = &quot;email&quot; id = &quot;email&quot; value = &quot;asdf&quot; > <ul class = &quot;errors&quot; > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = &quot;description&quot; > Your registered email address </p> </dd>
    321. 321. Errors <dt id = &quot;email-label&quot; > <label for = &quot;email&quot; class = &quot;required&quot; > Your email </label> </dt> <dd id = &quot;email-element&quot; > <input type = &quot;text&quot; name = &quot;email&quot; id = &quot;email&quot; value = &quot;asdf&quot; > <ul class = &quot;errors&quot; > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = &quot;description&quot; > Your registered email address </p> </dd>
    322. 322. Description <dt id = &quot;email-label&quot; > <label for = &quot;email&quot; class = &quot;required&quot; > Your email </label> </dt> <dd id = &quot;email-element&quot; > <input type = &quot;text&quot; name = &quot;email&quot; id = &quot;email&quot; value = &quot;asdf&quot; > <ul class = &quot;errors&quot; > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = &quot;description&quot; > Your registered email address </p> </dd>
    323. 323. HtmlTag <dt id = &quot;email-label&quot; > <label for = &quot;email&quot; class = &quot;required&quot; > Your email </label> </dt> <dd id = &quot;email-element&quot; > <input type = &quot;text&quot; name = &quot;email&quot; id = &quot;email&quot; value = &quot;asdf&quot; > <ul class = &quot;errors&quot; > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = &quot;description&quot; > Your registered email address </p> </dd>
    324. 324. Label <dt id = &quot;email-label&quot; > <label for = &quot;email&quot; class = &quot;required&quot; > Your email </label> </dt> <dd id = &quot;email-element&quot; > <input type = &quot;text&quot; name = &quot;email&quot; id = &quot;email&quot; value = &quot;asdf&quot; > <ul class = &quot;errors&quot; > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = &quot;description&quot; > Your registered email address </p> </dd>
    325. 325. how was that built? // Zend_Form_Element::loadDefaultDecorators() $this ->addDecorator( 'ViewHelper' ) ->addDecorator( 'Errors' ) ->addDecorator( 'Description' , array ( 'tag' => 'p' , 'class' => 'description' )) ->addDecorator( 'HtmlTag' , array ( 'tag' => 'dd' , 'id' => $this ->getName() . '-element' )) ->addDecorator( 'Label' , array ( 'tag' => 'dt' ));
    326. 326. form-level decorators <form enctype = &quot;application/x-www-form-urlencoded&quot; action = &quot;&quot; method = &quot;post&quot; > <dl class = &quot;zend_form&quot; > <!-- elements go here --> </dl> </form> <ul><li>Zend_Form_Decorator_ FormElements
    327. 327. Zend_Form_Decorator_ HtmlTag
    328. 328. Zend_Form_Decorator_ Form </li></ul>
    329. 329. Form-level decorator code // Zend_Form::loadDefaultDecorators() $this ->addDecorator( 'FormElements' ) ->addDecorator( 'HtmlTag' , array ( 'tag' => 'dl' , 'class' => 'zend_form' )) ->addDecorator( 'Form' );
    330. 330. common things to do <ul><li>Use UL rather than DL
    331. 331. Create your own decorator
    332. 332. Override a decorator </li></ul>
    333. 333. change to unsigned list // Element $email ->clearDecorators(); $email ->addDecorator( 'ViewHelper' ) ->addDecorator( 'Errors' ) ->addDecorator( 'Description' , array ( 'tag' => 'p' , 'class' => 'description' )) ->addDecorator( 'Label' ) ->addDecorator( 'HtmlTag' , array ( 'tag' => 'li' , 'id' => $email ->getName(). '-element' ));
    334. 334. change to unsigned list // Form $this ->getDecorator( 'HtmlTag' ) ->setTag( '<ul>' );
    335. 335. setup your paths $this ->addPrefixPath( 'App_Form' , 'App/Form' ); $this ->addElementPrefixPath( 'App_Form' , 'App/Form' ); $this ->addElementPrefixPath( 'App_Validate' , 'App/Validate' , 'VALIDATE' );
    336. 336. write your own decorator class App_Form_Decorator_ThumbsUp extends Zend_Form_Decorator_Abstract { public function render( $content ) { $errors = $this ->getElement()->getMessages(); $separator = $this ->getSeparator(); if ( empty ( $errors )) { return $separator . '<img src=&quot;/thumbsUp.gif&quot; />' ; } return $content ; } }
    337. 337. override a supplied decorator class App_Form_Decorator_Description extends Zend_Form_Decorator_Description { public function render( $content ) { $element = $this ->getElement(); $desc = $element ->getDescription(); $element ->setDescription(markdown( $desc )); $this ->setEscape( false ); $html = parent ::render( $content ); $element ->setDescription( $desc ); return $html ; } } // Usage in form's init() $email ->setDescription( 'Your **registered** email' );
    338. 338. Testing
    339. 339. what to test <ul><li>Domain models
    340. 340. Service layer objects
    341. 341. Integration testing: </li><ul><li>Does your MVC generate the content you're expecting? </li></ul></ul>
    342. 342. testing controllers <ul><li>Extend Zend_Test_PHPUnit_ControllerTestCase
    343. 343. Attach a Zend_Application instance as your bootstrap
    344. 344. Call $this->dispatch($url)
    345. 345. Perform assertions against content, the request, or the response </li></ul>
    346. 346. example use Zend_Test_PHPUnit_ControllerTestCase as ControllerTestCase; class FooControllerTest extends ControllerTestCase { public function setUp() { $this ->bootstrap = new Zend_Application( 'testing' , APPLICATION_PATH . '/configs/application.ini' ); parent::setUp(); } }
    347. 347. CSS selector assertions <ul><li>assertQuery($path, $message = '')
    348. 348. assertQueryContentContains($path, $match, $message = '')
    349. 349. assertQueryContentRegex($path, $pattern, $message = '')
    350. 350. assertQueryCount($path, $count, $message = '')
    351. 351. assertQueryCountMin($path, $count, $message = '')
    352. 352. assertQueryCountMax($path, $count, $message = '')
    353. 353. each has a &quot;Not&quot; variant </li></ul>
    354. 354. XPath assertions <ul><li>assertXpath($path, $message = '')
    355. 355. assertXpathContentContains($path, $match, $message = '')
    356. 356. assertXpathContentRegex($path, $pattern, $message = '')
    357. 357. assertXpathCount($path, $count, $message = '')
    358. 358. assertXpathCountMin($path, $count, $message = '')
    359. 359. assertXpathCountMax($path, $count, $message = '')
    360. 360. each has a &quot;Not&quot; variant </li></ul>
    361. 361. redirect assertions <ul><li>assertRedirect($message = '')
    362. 362. assertRedirectTo($url, $message = '')
    363. 363. assertRedirectRegex($pattern, $message = '')
    364. 364. each has a &quot;Not&quot; variant </li></ul>
    365. 365. response assertions <ul><li>assertResponseCode($code, $message = '')
    366. 366. assertHeader($header, $message = '')
    367. 367. assertHeaderContains($header, $match, $message = '')
    368. 368. assertHeaderRegex($header, $pattern, $message = '')
    369. 369. each has a &quot;Not&quot; variant </li></ul>
    370. 370. request assertions <ul><li>assertModule($module, $message = '')
    371. 371. assertController($controller, $message = '')
    372. 372. assertAction($action, $message = '')
    373. 373. assertRoute($route, $message = '')
    374. 374. each has a &quot;Not&quot; variant </li></ul>
    375. 375. examples <ul><li>Test sitenav contains at least 3 items: $this->assertQueryCountMin( '#sitenav li', 3);
    376. 376. Test that entry contains a title $this->assertQuery( '#content h2'); </li></ul>
    377. 377. Thank You Feedback: http://joind.in/1526 http://twitter.com/akrabat http://twitter.com/weierophinney
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×