Horde_View
PHP 5 library targetted for Horde 4 that implements a view pattern.
Bugs
People
ChuckHagenbuch is the primary author of Horde_View.
Description
Notes on views and templates
Some of this may be badly outdated.
http://blog.sig9.net/2008/01/21/template-engines/
I use the Two Step View pattern. My applications render content to the
response object (just the application content, none of the site
skeleton), and then in a dispatchLoopShutdown() plugin I pull the
response content and inject it into a sitewide template, and finally
inject that rendered content back into the response object. I've
provided a sample of such a plugin in the past on either the fw-mvc or
fw-general list.
http://paul-m-jones.com/blog/?p=247
http://framework.zend.com/wiki/display/ZFMLGEN/mail/27145
http://www.ingredients.com.au/nick/2006/06/10/getting-to-know-zend\_view/
http://fosterburgess.com/kimsal/?p=237
Horde_View notes:
- http://benramsey.com/archives/zend-framework-view-notes/
- some way to automatically call views
- should I change from "script" to "template"?
To read:
rev 4332:
- Add optional ability to generate notices for undeclared variables in
templates. Use $view->strictVars(true); to turn on functionality
- Added Zend_View_Helper_DeclareVars helper. Allows developer to optionally
declare template variables and default values. When used with
strictVars(), will prevent notices by declaring unused variables in the
view object
rev 4338: Added getScriptPath($name), getHelperPath($name),
getFilterPath($name), getHelper($name), getFilter($name), and getFilters()
methods. Notes:
- getHelper() and getFilter() will instantiate the filter of $name if not
already instantiated
- getHelpers() and getFilters() only return already instantiated helpers and
filters; i.e., they won't return all helpers and filters available
- getHelperPath() and getFilterPath() will instantiate the associated helper
or filter in the process of discovering the path, if not already
instantiated
View notes:
We're using some different "views" to get a useful layout for each purpose.
We start using:
- portal-layout.php
- clean-layout.php
The first one has the header, footer, top menu, the main content and optionally the left and right menus. The clean layout only has the header and footer.
So, in our scripts we can do the following (extending the Zend_View component):
$view = new TemplateView();
$view->tplMain = 'articles/view.php';
$view->tplLeft = 'articles/leftMenu.php';
$view->tplRight = 'articles/contextual.php';
$view->articles = $oArticles->get();
$view->render('portal-layout.php');
The "portal-layout.php" is constructed this way:
$this->render('header.php');
$this->render('topMenu.php');
if(isset($this->tplLeft))
$this->render($this->tplLeft);
$this->render($this->tplMain);
if(isset($this->tplRight))
$this->render($this->tplRight);
$this->render('footer.php');
ACLs and views
I'm not quite sure how I should use ACL with Views. I have a menu where I'd like items not to be visible if users role(s) have no access to it. So I was planning on doing array of the menu items and passing that to the view or passing the whole ACL object to view.
Definitely go for the array of menu items. A simple rule to follow is give the view simple data structures only. Otherwise, should your application code switch from Zend_Acl to something else, you would need to change every view involved.
One simple approach that i user is to set up a dir like:
/app/views/public/modules/
I have view helper RenderModule:
/**
* renders a module:
* moduleDir_moduleName
*
* @param string, module
*/
public function RenderModule($module){
$view = Zend_Registry::get('view');
$config = Zend_Registry::get('config');
$path = $config->view->publicModules;
$currModule = str_replace('_','/',$module);
return $view->render('./' . $path . '/' . $currModule . '.tpl.php');
}
so for the show new news module:public function dispatchLoopShutdown\(\)
\{
* assume that we've already determined the request is ajax
$request = $this-\>getRequest\(\);
$response = $this-\>getResponse\(\);
$view = Zend\_Registry::get\('view'\);
if \($request-\>getParam\('isAjax'\)\) \{
* Ajax request detected
* Get any variables set in the view
$vars = get\_object\_vars\($view\);
* Merge with named path segments in response
$vars = array\_merge\($vars, $response-\>getBody\(true\)\);
* Create a header and set the response body to a JSON value
$response-\>setHeader\('Content-Type', 'text/x-json'\);
$response-\>setBody\(Zend\_Json::encode\($vars\)\);
return;
\}
* Otherwise, process as normal
$view-\>assign\($response-\>getBody\(true\)\);
$response-\>setHeader\('Content-Type', 'text/html'\);
$response-\>setBody\($view-\>render\('site.php'\)\);
\}
}
The site wide template that I use is composed of a header, 3 columns, and a footer. The header and footer are the same for each page. I assign the content for each column by appending the response object with a named section. The named sections eventually become the view variables used in the site template. Here's an example of a normal HTML response:
class IndexController extends Zend_Controller_Action {
* Normal HTML response
public function indexAction\(\)
\{
$response = $this-\>getResponse\(\);
$view = Zend\_Registry::get\('view'\);
$view-\>title = 'My Index Title';
$response-\>appendBody\($view-\>render\('index\_css.php'\), 'inline\_css'\);
$response-\>appendBody\($view-\>render\('index\_left.php'\), 'left\_content'\);
$response-\>appendBody\($view-\>render\('index\_center.php'\), 'center\_content'\);
$response-\>appendBody\($view-\>render\('index\_right.php'\), 'right\_content'\);
\}
* AJAX response
public function viewAction\(\)
\{
$request = $this-\>getRequest\(\);
$request-\>setParam\("isAjax", true\);
$view = Zend\_Registry::get\('view'\);
$view-\>status = 0;
$view-\>message = "Problem encounted";
}
}
Here's the site wide template:
W3CDTD XHTML 1.0 StrictEN" "http:*www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"\>\
\This is the header.\
\
\
\
\center\_content; ?\>
\
\
\left\_content; ?\>
\
\
\right\_content; ?\>
\
\
\
class Wopnet\_Plugins\_SiteTemplate extends Zend\_Controller\_Plugin\_Abstract \{
/**
\* View script path
\* @var string
\*/
public $directory;
/**
- View script for sitewide template
- @var string /**
\* View object
\* @var Zend\_View\_Interface
\*/
public $view;
/**
- Constructor $config = Zend\_Registry::get\('siteConfig'\);
if \(\(null \!== $directory\) \&\& is\_dir\($directory\)\) \{
$this-\>directory = $directory;
\} else \{
if \(\!isset\($config-\>vars-\>path-\>app\)\) \{
throw new Exception\('No sitewide app path set'\);
\}
$this-\>directory = $config-\>vars-\>path-\>app . DIRECTORY\_SEPARATOR . 'views' . DIRECTORY\_SEPARATOR . 'scripts';
\}
if \(\!is\_dir\($this-\>directory\)\) \{
throw new Exception\('Sitewide view script path does not exist'\);
\}
if \(null \!== $file\) \{
$this-\>file = \(string\) $file;
\} else \{
if \(\!isset\($config-\>templates-\>site-\>filename\)\) \{
throw new Exception\('Sitewide template not set in config'\);
\}
$this-\>file = $config-\>templates-\>site-\>filename;
\}
if \(\!file\_exists\($this-\>directory . DIRECTORY\_SEPARATOR . $this-\>file\)\) \{
throw new Exception\("Sitewide view script '\{$this-\>directory\}/\{$this-\>file\}' does not exist"\);
\}
$this-\>view-\>addScriptPath\($this-\>directory\);
\}
/\*\*
- Inject generated content into sitewide view script $this-\>view-\>setScriptPath\($this-\>directory\);
$this-\>view-\>assign\(get\_object\_vars\($response\)\);
$this-\>view-\>content = $body;
$response-\>setBody\($this-\>view-\>render\($this-\>file\)\);
\}
}
Now, that's all fine and dandy, but it doesn't get the widgets in like you're discussing. For that, I've seen a few ideas. One is a Zend View Helper somebody posted to JIRA (and I think there's also a similar proposal in the wiki). In those, the helper basically does something like this:
public function widget\($action, $controller, $module, array $params = array\(\)\)
\{
$front = Zend\_Controller\_Front::getInstance\(\);
$request = clone $front-\>getRequest\(\);
$request-\>setActionName\($action\)
-\>setControllerName\($controller\)
-\>setModuleName\($module\)
-\>setParams\($params\);
$response = new Zend\_Controller\_Response\_Http\(\);
$front-\>getDispatcher\(\)-\>dispatch\($request, $response\);
return $response-\>getBody\(\);
\}
(this is rough; I haven't tested this at all). Then, you would call this in your views to grab the necessary components of your page:
\= $this-\>widget\('items', 'list', 'blog', array\('limit' =\> 10\)\) ?\>
There's a little overhead, but not much. The con side to this is that your widgets don't get to tie into the dispatch loop, so if you were doing any ACL checks in a preDispatch() plugin, for instance, you'd lose those checks.
Theoretically, you could call $front->dispatch($request, $response), but this would overwrite the current request/response objects, and could lead to some unintended behaviour.
I wanted to post this now 1 week or so but i am very busy and didn't had time to write a