• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Zend Framework Workshop
 

Zend Framework Workshop

on

  • 27,582 views

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 ...

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.

Statistics

Views

Total Views
27,582
Views on SlideShare
18,049
Embed Views
9,533

Actions

Likes
33
Downloads
439
Comments
2

39 Embeds 9,533

http://mwop.net 7381
http://www.mwop.net 1869
http://mwop.local 77
http://localhost 46
http://www.slideshare.net 39
http://victimofbabylon.com 34
http://www.weierophinney.net 12
http://www.kazaff.me 8
http://www.codingcool.com 8
http://codingcool.com 6
http://1.codingcool.sinaapp.com 4
http://coderwall.com 4
http://test6 4
http://zf2sandbox.lcl 3
http://weierophinney.homelinux.org 3
http://victimofbabylon.posterous.com 3
http://www.jackpeterson.info 3
http://local.mwop.net 2
http://test.mwop.net 2
http://mwop.net.local 2
http://54.221.220.20 2
http://zf2-mwop 2
http://webcache.googleusercontent.com 2
http://mwop.localhost 2
http://localhost:4000 1
http://posterous.com 1
http://matthew.weierophinney.net 1
http://paper.li 1
http://test.local.com 1
http://translate.googleusercontent.com 1
http://ec2-54-221-220-20.compute-1.amazonaws.com 1
http://ec2-50-17-250-246.compute-1.amazonaws.com 1
http://zf2sandbox.dev 1
http://www.medinfo.bg 1
http://zf2-sandbox.banana 1
http://a0.twimg.com 1
http://localhost:10101 1
http://dev.defyblog.com 1
http://mwop.net. 1
More...

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

CC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

12 of 2 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • dragonkicks.com take you into the Air Jordan's world, where every pair of jordan shoes have been documented in Michael Jordan in the basketball court has walked the streets.
    http://www.kobebryantshoess.com/
    Are you sure you want to
    Your message goes here
    Processing…
  • 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.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • 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 & 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 & 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 & 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 & 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 & 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'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 "Lazy Loader". 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'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 Zend Framework Workshop Presentation Transcript

  • 10 June 2010, RAI, Amsterdam Zend Framework Workshop
  • Today's Roadmap
  • what we hope to cover
    • Service Layers, including ACLs and Paginators
    • Application resources
    • Routing clinic
    • Enhanced views
    • Navigation
    • Caching
    • Context switching
    • Form decorators
  • 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 Domain Models and Entities Service Layer
  • 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 ) {} }
  • 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!' ); } // ... } }
  • 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' ) ->setName( "Matthew Weier O'Phinney" ); $mapper ->save( $user ); $user = $repository ->find( 'matthew' );
  • 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() {} }
  • what's found in Service Layers?
    • Resource marshalling and Dependency Injection
    • Application-specific logic:
      • Authentication and Authorization (ACLs)
      • Input filtering/data validation
      • Search indexing
      • Caching
      • etc.
  • ACL basics
  • Authorization is the act of determining if somebody has permissions to perform an action on a given resource .
  • the three “R”s
    • Roles – what requests the action
    • Resources – what is being acted upon
    • Rights – the privileges a role has for a given resource
  • roles
    • Implement Zend_Acl_Role_Interface
      • One method: getRoleId()
    • Can simply use Zend_Acl_Role, but this doesn't tie directly to your models
  • a “user” role class User implements Zend_Acl_Role_Interface { // ... protected $_role = 'pig' ; public function getRoleId() { return $this ->_role; } }
  • resources
    • Implement Zend_Acl_Resource_Interface
      • One method: getResourceId()
    • Can simply use Zend_Acl_Resource , but this doesn't tie directly to your models
  • an “entry” resource class Entry implements Zend_Acl_Resource_Interface { // ... public function getResourceId() { return 'entry' ; } }
  • rights
    • 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
  • 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' )); } }
  • checking ACLs
    • Remember the three “R”s; check that:
      • The Role
      • acting on the Resource
      • has Rights to perform the action
    • Use the isAllowed() method
  • 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(); } // ... } }
  • Paginators
  • return paginators from your domain models
    • Consumers do not need to be aware of data format
    • Consumers can provide offset and limit
    • Consumers can decide how to cast
      • Zend_Paginator implements IteratorAggregate , toJson()
    • Most paginator adapters will only operate once results are requested
  • Zend_Paginator basics
    • 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
  • 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 ; }
  • paginator use in view script $this ->users->setItemCountPerPage( $this ->numItems) ->setCurrentPageNumber( $this ->page); echo $this ->users->toJson();
  • going generic
    • Alternately, define a “collection” type
      • 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()
  • 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 ; }
  • collection used in view script $this ->entries->start( $this ->offset) ->limit( $this ->numItems); echo $this ->json( $this ->entries->toArray() );
  • Zend_Application
  • 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();
  • 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(); } }
  • problems with the approaches
    • 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
  • what Zend_Application provides
    • Common configuration methodology
      • And per-environment configuration
    • Reusable resources
    • Dependency tracking for resources
    • Resource injection into the application (acts as a repository)
  • what are resources?
    • Anything you need within your application that requires configuration
    • Two types:
      • Bootstrap-specific resources
      • Plugin resources
  • commonalities
    • Zend_Application instantiates and executes a bootstrap
      • An “environment” is passed as well, indicating what configuration to load
    • Bootstraps extend either Zend_Application_Bootstrap_Bootstrap or Zend_Application_Module_Bootstrap
    • You then selectively bootstrap resources, or bootstrap the entire application
    • Finally, you optionally run() the app
  • bootstrap resources
    • Any protected method beginning with “_init” is considered a resource; it's name is whatever follows that string, lowercased:
      • _initMongodb() is the “mongodb” resource
  • configuration we'll use [production] mongodb.server = "appserver" mongodb.dbname = "site" [testing] mongodb.server = "localhost" mongodb.dbname = "testing" [development] mongodb.server = "localhost" mongodb.dbname = "blog"
  • 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 ; } }
  • plugin resources
    • Implement Zend_Application_Resource_Resource
      • Defines an init() method
    • Resource name is everything following a plugin prefix: Wopnet_Resource_Mongodb becomes the “mongodb” resource
      • Register plugin prefixes with the application configuration
  • 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 ; } }
  • invoking the application $application = new Zend_Application( 'production' , '/path/to/application.ini' ); $application ->bootstrap() ->run();
  • re-using it for a service endpoint $application = new Zend_Application( 'jsonrpc' , '/path/to/application.ini' ); $application ->bootstrap(); $mongo = $application ->getResource( 'mongodb' ); // ...
  • dependency tracking
    • 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
  • dependency example public function _initRoutes() { $this ->bootstrap( 'frontcontroller' ); $fc = $this ->getResource( 'frontcontroller' ); $router = $fc ->getRouter(); // ... }
  • how do I use resources elsewhere?
    • Bootstrap acts as a registry
    • Bootstrap is injected into the front controller as a parameter
  • 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' ); }
  • retrieving configuration [production] lucene.basepath = "/data/search/" public function searchAction() { $boot = $this ->getInvokeArg( 'bootstrap' ); $options = $boot ->getOption( 'lucene' ); $lucene = Zend_Search_Lucene::open( $options [ 'basepath' ] ); // ... }
  • Routing Clinic
  • basics
    • 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+”
  • static route use Zend_Controller_Router_Route_Static as StaticRoute; $entries = new StaticRoute( 'blog' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'index' , ) );
  • standard (named) route use Zend_Controller_Router_Route as StandardRoute; $entry = new StandardRoute( 'blog/:id' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'entry' , ) );
  • 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' , ) );
  • hostname route use Zend_Controller_Router_Route_Hostname as HostnameRoute; $hostnameRoute = new HostnameRoute( ':author.example.com' , array ( 'module' => 'blog' , 'controller' => 'entry' , 'action' => 'by-author' , ) );
  • 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 );
  • RESTful routes
    • 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
  • defining all routes RESTful // Enable for all controllers $front = Zend_Controller_Front::getInstance(); $restRoute = new Zend_Rest_Route( $front ); $front ->getRouter()->addRoute( 'default' , $restRoute );
  • 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 );
  • 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 );
  • 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() { } }
  • 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' , ) ); // ... }
  • 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;
  • Layout View Helpers
  • layout related view helpers
    • Set metadata, JS & CSS within action view scripts
    • Aggregate content
    • Rendered in the layout
  • doctype()
    • Call before the first view script is rendered
    • Affects rendering of other view helpers, such as forms
  • setting the doctype <!-- in layouts/scripts/layout.phtml --> <?php echo $this ->doctype(); ?> ;application.ini resources.view.doctype = XHTML1_STRICT
  • the head() view helpers
    • Used for setting all the <head> tags
    • Can append and prepend
    • Available:
      • headMeta()
      • headTitle()
      • headScript()
      • headStyle()
  • the inline() view helpers
    • Used for setting inline CSS within <head> & inline JS just before </body>
    • Available:
      • inlineStyle()
      • inlineScript()
  • 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>
  • 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 ; } }
  • all layout script files <?php echo $this ->doctype(); ?> <html xmlns = &quot;http://www.w3.org/1999/xhtml&quot; > <head> <?php echo $this ->headSection(); ?> </head>
  • Adding CSS/JS
    • Some actions need JS/CSS included in <head>
      • or JS just before </body>
    • Place this code in action's view script
  • 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' ); ?>
  • 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 );
  • 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
    • Search Engine Optimization (SEO)
      • Sitemap and links enhance SEO for a site
    • Site Usability
      • Navigation elements such as breadcrumbs and menus make traversing a site easier for end-users
  • Navigation: features
  • menus
  • breadcrumbs
  • header links
    • Provide hints to page regarding pages relative to current page
      • &quot;Start&quot; or &quot;Home&quot; page
      • &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
  • sitemaps
  • body links
    • Render links to related content
      • previous/next page
      • parent
      • children
    • Translate link text
    • Conditionally display links based on ACLs
  • Navigation: pages and containers
  • pages An object that holds a pointer to a web page .
  • Zend_Navigation_Page metadata
    • label
    • title
    • target
    • rel
    • order
    • active
    • visible
    • ACL info (resource, privilege)
  • page types
    • MVC
      • Integration with the router for URL generation
    • URI
      • Specify specific URI for the page
  • 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; ;
  • creating URI pages $page = new Zend_Navigation_Page_Uri(); $page ->title = &quot;Foo&quot; ; $page ->uri = &quot;/foo&quot; ;
  • using the page factory
    • Zend_Navigation_Page::factory()
    • Accepts array or Zend_Config object
    • Rules
      • 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
        • can be &quot;mvc&quot; or &quot;uri&quot;
  • containers A container for pages.
  • Zend_Navigation_Container
    • Implements RecursiveIterator (iterate entire tree)
    • Implements Countable (get total count of pages)
    • Zend_Navigation_Page is a container
      • Allows having trees of pages
  • 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/' , ) ));
  • 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);
  • adding multiple pages at once // addPages() using an array: $container ->addPages( $array ); // addPages() using a Zend_Config object: $container ->addPages( $config );
  • 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();
  • finding pages in a container
    • All finder methods return containers
      • So you can iterate, count, etc.
  • 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' );
  • iteration of pages (flat) foreach ( $container as $page ) { echo $page ->label, &quot;<br /> &quot; ; }
  • iteration of pages (recursive) $it = new RecursiveIteratorIterator( $container , RecursiveIteratorIterator::SELF_FIRST ); foreach ( $it as $page ) { echo $page ->label, &quot;<br /> &quot; ; }
  • container utility methods
    • hasPage(Zend_Navigation_Page $page)
      • Does the container have the given page?
      • if ($container->hasPage($page)) { /* do something */ }
    • hasPages()
      • Does the container have *any* pages?
      • Equivalent to count($container) > 1
    • toArray()
      • Serialize container to array structure
  • methods for defining pages
    • Programmatically
    • Using arrays
    • Using Zend_Config values
  • strategies for defining pages
    • Cache the definitions
      • First time through:
        • Create programmatically or from Zend_Config
        • Cache to array using toArray()
      • Subsequent times:
        • Load from cached array
    • Use only when needed
      • Reduces performance hit
      • Disable on XHR calls, service calls, etc.
  • Navigation: using the view helpers
  • Zend_View_Helper_Navigation
    • Holds a container
    • Proxies to other Navigation view helpers
    • Holds Translator and Acl objects
  • 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 );
  • 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 );
  • injecting ACLs into the navigation view helper (registry) // ACL: Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl( $acl ); // Role: Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole( $role );
  • ACL-aware helpers can …
    • can check for ACLs with hasAcl()
    • can check for roles with hasRole()
    • check against the “resource” and “privilege” registered with a page
      • If either is missing, the check is skipped
      • If the ACL denies access, all sub-pages will be omitted as well
  • rendering links to pages // Find page, and render link $page = $this ->navigation()->findOneByLabel( 'Foo' ); echo $this ->navigation()->htmlify( $page );
  • rendering ACL-aware links // Conditionally render based on ACLs echo (( $this ->navigation()->accept( $page )) ? $this ->navigation()->htmlify( $page ) : '' );
  • 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.
  • rendering a sitemap echo $this ->navigation()->sitemap();
  • configuring the sitemap helper
    • Indicate that output should be &quot;formatted&quot; (adds whitespace for readability): setFormatOutput($flag) (false)
    • 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)
  • sitemap-specific page metadata
    • lastmod
      • Date of last modification of the file
      • Outputs using W3C Datetime format; time may be omitted
    • changefreq
      • How frequently the page is likely to change
      • Values: always, hourly, daily, weekly, monthly, yearly, never
    • priority
      • Priority of this URL relative to others on the site
      • Scale of 0.0 to 1.0 (1.0 being highest)
  • breadcrumbs Breadcrumbs indicate the ancestry of the current page within the sitemap.
  • rendering breadcrumbs echo $this ->navigation()->breadcrumbs();
  • configuring the breadcrumbs helper
    • indentation: setIndent($spaces)
    • set minimum breadcrumb depth: setMinDepth($depth)
      • set to 1 (default) to omit root page
      • set to 0 to include root page
    • set maximum breadcrumb depth: setMaxDepth($depth)
    • set breadcrumbs separator: setSeparator($separator)
    • indicate whether to link last crumb in trail: setLinkLast($flag)
    • indicate a view partial to use when rendering: setPartial($script)
  • 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.
  • configuring the menu helper
    • Set the class for the UL element: setUlClass($class)
    • Indicate whether to render only pages and branches marked &quot;active&quot;: setOnlyActiveBranch($flag)
    • Indicate whether to render parents when only rendering active branches: setRenderParents($flag)
    • Indicate a partial script to use to render the menu: setPartial($script)
  • calling renderMenu()
    • Takes options array as second argument
    • Options (in addition to setters):
      • indent : either a string to indent with, or an integer number of spaces
      • minDepth : minimum depth to render; null indicates no minimum
      • maxDepth : maximum depth to render; null indicates no maximum
  • rendering menus
    • By default, render the entire menu
    • renderMenu() and renderSubMenu() allow specifying trees
      • &quot;null&quot; argument will grab from active root node
      • otherwise pass in a Page , and that tree will be used
  • 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 );
  • 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, ) );
  • links Renders LINK elements that describe document relationships of the currently active page.
  • 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' , ), ));
  • rendering links
    • Typically from within your site layout
    • All links
      • echo $this->navigation() ->links();
  • 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();
  • Caching
  • the principle of caching
  • Zend_Cache
    • Front end adapters
      • What to cache
    • Back end adapters
      • Where to cache
  • 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 ; }
  • however! There is a better way
  • Zend_Cache_Manager
    • Manages multiple cache objects
    • Lazy loads on demand
    • Contains preconfigured caches
    • Action helper for access
    • Application resource for creation
  • implementing caching in an app
    • Create a cache object
    • Wrap current code with cache loading code
    • Clear cache on data change
  • 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;
  • 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; }
  • accessing the cache object // Within a controller $this ->_helper->getHelper( 'Cache' ) ->getManager() ->getCache( 'default' );
  • 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 ); }
  • // 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
  • empty the cache // Application_Model_DbTable_Entries protected function _cleanCache() { $this ->getCache()->clean( Zend_Cache::CLEANING_MODE_MATCHING_TAG, array ( 'tasks' )); }
  • cache the entire page
    • Bypass entire MVC stack
    • Super fast!
    • Implement in index.php
  • $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
  • some numbers to think about
    • No caching: 12 trans/sec
    • Cache DB: 29 trans/sec
    • Cache page: 359 trans/sec
    • No caching: 19 trans/sec
    • Cache DB: 289 trans/sec
    • Cache page: 3231 trans/sec
    With APC:
  • Context Switching
  • in a nutshell... Context switching is the act of providing different output based on criteria from the request.
  • when you might use it
    • Provide different output for requests originating via XMLHttpRequest
    • Provide different output based on Accept HTTP headers (e.g., REST endpoints)
    • Provide alternate layouts/content based on browser detection
  • how it works
    • format request parameter
      • As explicit part of request
      • Or set by a plugin that does HTTP header detection
    • ContextSwitch action helper determines if the currently requested action has a context matching the format
    • An additional view suffix is added, meaning a different view script will be rendered
  • tricks and tips for getting the format parameter
    • Easiest: use a query parameter
    • Easy: create a special route
    • Also easy: use chained routes
    • Reusable: detect HTTP headers via a plugin (more later)
  • a simple route $commentXml = new Zend_Controller_Router_Route_Static( 'blog/comment.xml' , array ( 'module' => 'blog' , 'controller' => 'comment' , 'action' => 'post' , 'format' => 'xml' , ) );
  • 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 , '.' );
  • 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(); } }
  • add view scripts per-context blog |-- views | |-- scripts | | |-- comment | | | |-- post.phtml | | | `-- post.xml.phtml
  • “html” view <?php $comment = $this ->entryUrl . '#' . $this ->comment->getId(); $response = $this ->response; $response ->setHttpResponseCode(201) ->setHeader( 'Location' , $comment );
  • “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>
  • Case study: sitemap and robots.txt
  • the problem
    • We want Google to hit our sitemap.xml
    • We want to generate our sitemap.xml using Zend Framework's Zend_Navigation component
  • 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' ));
  • 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(); }
  • robots.txt.phtml Sitemap: <?php echo $this ->fullUrl( array (), null, true); ?>sitemap.xml User-agent: * Disallow: /index.php?*
  • sitemap.phtml <?php echo $this ->navigation() ->menu() ->setUlClass( 'site-map' ); ?>
  • sitemap.xml.phtml <?php echo $this ->navigation() ->sitemap(); ?>
  • Advanced topics: header detection
  • 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 ; } } }
  • 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 ; }
  • 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 ; }
  • Content-Type detection (cont.) public function getSubmitParams() { if ( $this ->hasBodyParams()) { return $this ->getBodyParams(); } return $this ->getRequest()->getPost(); } public function direct() { return $this ->getSubmitParams(); }
  • using the helper public function postAction() { $params = $this ->_helper->params(); if (! $item = $this ->service->create( $params )) { // ... } // ... }
  • 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 ; }
  • 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();
  • 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(), )); } }
  • automating scaffolding: configuration resources.frontcontroller.plugins[] = &quot;Accept&quot; resources.frontcontroller.plugins[] = &quot;InjectRequestResponse&quot;
  • 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 ); } }
  • 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 ; }
  • 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 ?>
  • 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 );
  • more uses for ContextSwitch
    • Mobile browser detection
    • Search-engine robot detection
    • Re-using MVC endpoints for service endpoints (JSON-RPC, SOAP, etc.)
    • etc.
  • Form Decorators
  • decorators
    • Render elements & forms
    • Combination Decorator and Strategy pattern; decorates string content using element and form metadata
    • Decorators stack from inside to outside
  • how it works
  • a typical form
  • 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>
  • Default element decorators
    • Zend_Form_Decorator_ ViewHelper
    • Zend_Form_Decorator_ Errors
    • Zend_Form_Decorator_ Description
    • Zend_Form_Decorator_ HtmlTag
    • Zend_Form_Decorator_ Label
  • 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>
  • 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>
  • 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>
  • 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>
  • 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>
  • 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' ));
  • 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>
    • Zend_Form_Decorator_ FormElements
    • Zend_Form_Decorator_ HtmlTag
    • Zend_Form_Decorator_ Form
  • Form-level decorator code // Zend_Form::loadDefaultDecorators() $this ->addDecorator( 'FormElements' ) ->addDecorator( 'HtmlTag' , array ( 'tag' => 'dl' , 'class' => 'zend_form' )) ->addDecorator( 'Form' );
  • common things to do
    • Use UL rather than DL
    • Create your own decorator
    • Override a decorator
  • 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' ));
  • change to unsigned list // Form $this ->getDecorator( 'HtmlTag' ) ->setTag( '<ul>' );
  • setup your paths $this ->addPrefixPath( 'App_Form' , 'App/Form' ); $this ->addElementPrefixPath( 'App_Form' , 'App/Form' ); $this ->addElementPrefixPath( 'App_Validate' , 'App/Validate' , 'VALIDATE' );
  • 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 ; } }
  • 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' );
  • Testing
  • what to test
    • Domain models
    • Service layer objects
    • Integration testing:
      • Does your MVC generate the content you're expecting?
  • testing controllers
    • Extend Zend_Test_PHPUnit_ControllerTestCase
    • Attach a Zend_Application instance as your bootstrap
    • Call $this->dispatch($url)
    • Perform assertions against content, the request, or the response
  • 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(); } }
  • CSS selector assertions
    • assertQuery($path, $message = '')
    • assertQueryContentContains($path, $match, $message = '')
    • assertQueryContentRegex($path, $pattern, $message = '')
    • assertQueryCount($path, $count, $message = '')
    • assertQueryCountMin($path, $count, $message = '')
    • assertQueryCountMax($path, $count, $message = '')
    • each has a &quot;Not&quot; variant
  • XPath assertions
    • assertXpath($path, $message = '')
    • assertXpathContentContains($path, $match, $message = '')
    • assertXpathContentRegex($path, $pattern, $message = '')
    • assertXpathCount($path, $count, $message = '')
    • assertXpathCountMin($path, $count, $message = '')
    • assertXpathCountMax($path, $count, $message = '')
    • each has a &quot;Not&quot; variant
  • redirect assertions
    • assertRedirect($message = '')
    • assertRedirectTo($url, $message = '')
    • assertRedirectRegex($pattern, $message = '')
    • each has a &quot;Not&quot; variant
  • response assertions
    • assertResponseCode($code, $message = '')
    • assertHeader($header, $message = '')
    • assertHeaderContains($header, $match, $message = '')
    • assertHeaderRegex($header, $pattern, $message = '')
    • each has a &quot;Not&quot; variant
  • request assertions
    • assertModule($module, $message = '')
    • assertController($controller, $message = '')
    • assertAction($action, $message = '')
    • assertRoute($route, $message = '')
    • each has a &quot;Not&quot; variant
  • examples
    • Test sitenav contains at least 3 items: $this->assertQueryCountMin( '#sitenav li', 3);
    • Test that entry contains a title $this->assertQuery( '#content h2');
  • Thank You Feedback: http://joind.in/1526 http://twitter.com/akrabat http://twitter.com/weierophinney