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.
hearmesdragonkicks.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/1 year ago
Are you sure you want to
Simon Jones, MD, Developer, Organiser at Studio 24 Ltdthanks 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.2 years ago
Are you sure you want to
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!
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
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:
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
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() { } }
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();
iteration of pages (flat) foreach ( $container as $page ) { echo $page ->label, "<br />
" ; }
iteration of pages (recursive) $it = new RecursiveIteratorIterator( $container , RecursiveIteratorIterator::SELF_FIRST ); foreach ( $it as $page ) { echo $page ->label, "<br />
" ; }
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 "method call" $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 );
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 "formatted" (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.
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 "active": 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
"null" 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.
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(), )); } }
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 = "email-label" > <label for = "email" class = "required" > Your email </label> </dt> <dd id = "email-element" > <input type = "text" name = "email" id = "email" value = "asdf" > <ul class = "errors" > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = "description" > 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 = "email-label" > <label for = "email" class = "required" > Your email </label> </dt> <dd id = "email-element" > <input type = "text" name = "email" id = "email" value = "asdf" > <ul class = "errors" > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = "description" > Your registered email address </p> </dd>
Errors <dt id = "email-label" > <label for = "email" class = "required" > Your email </label> </dt> <dd id = "email-element" > <input type = "text" name = "email" id = "email" value = "asdf" > <ul class = "errors" > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = "description" > Your registered email address </p> </dd>
Description <dt id = "email-label" > <label for = "email" class = "required" > Your email </label> </dt> <dd id = "email-element" > <input type = "text" name = "email" id = "email" value = "asdf" > <ul class = "errors" > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = "description" > Your registered email address </p> </dd>
HtmlTag <dt id = "email-label" > <label for = "email" class = "required" > Your email </label> </dt> <dd id = "email-element" > <input type = "text" name = "email" id = "email" value = "asdf" > <ul class = "errors" > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = "description" > Your registered email address </p> </dd>
Label <dt id = "email-label" > <label for = "email" class = "required" > Your email </label> </dt> <dd id = "email-element" > <input type = "text" name = "email" id = "email" value = "asdf" > <ul class = "errors" > <li> 'asdf' is not a valid email address </li> <li> Length must be greater than 5 </li> </ul> <p class = "description" > Your registered email address </p> </dd>
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(); } }
http://www.kobebryantshoess.com/ 1 year ago