PHP, Zend Framework and Other Crazy Stuff
Archive for May, 2007
May’s php|architect Magazine - worthy weekend reading
May 26th
I usually don’t extol the virtues of magazines in blog posts. However, I’ve been sitting down with a printout of May’s php|architect Magazine and figured it was well worth some attention. A lot of PHP developers whether newcomers or even old hands just looking to expand their knowledge would love this issue.
The two that stick in my mind are Jim Delahunt’s “Unicode and PHP: A Gentle Introduction” and Jeff Moore’s “Test Pattern: Model View Controller”. Both are something those seeking illumination on these topics should certainly read. Jeff’s article presents an overview of the MVC Pattern and injects a lot of clarity into a confusing subject for many PHP developers. This is one of the few times reading about MVC in PHP that there’s more than a vague reference to a really important point - that Views may (even should) be empowered to read data from the Model. As Jeff notes the continuing popular trend of forcing an application to shuffle between Controllers just to inject Model data in part of a View is completely unnecessary. In my own humble opinion, over-dependency on Controllers to inject Model data into Views is wasteful of resources by pushing folk into adding more and more Controller classes for every teeny View->Model interaction (more work and files to maintain). I’d be hard pressed to believe it doesn’t impose a performance cost too.
Jim Delahunt’s article is another noteworthy one. I have a pet peeve about developers and popular online websites which store usernames with Unicode characters, render HTML which declares itself UTF-8, and which results in garbage characters like question marks everywhere. For a long time, my own name with a common European character (a-acute) failed to render on most sites claiming to output UTF-8 - assuming of course the site even allowed me to submit my real name… These days there’s a marked improvement. This excellent article digs into Unicode in an easy to access way. If you are still not familiar with Unicode, UTF-8, the other Unicode standards, and why they are essential knowledge when building internationalised web apps then this is the article for you.
What else is in the mag this month? There’s a cool overview of the Symfony Framework, Ilia Alshanetsky on Dictionary Attacks, and a few other articles I really enjoyed but won’t mention here directly. As PHP magazines go, this is an action packed issue worth being enthusiastic about. You can review and buy the PDF version of May’s issue for a handful of Canadian Dollars over at php|architect.
Complex Views with the Zend Framework - Part 5: The Two-Step View Pattern
May 23rd
It’s been a while since I continued this series. Unfortunately real life workloads are unforgiving of the best of intentions . Part 5 of our series takes a small time-out from approaching a Composite View solution to reusable Views to take a peek at a simpler approach useful for simpler types of web applications. As we’ve discussed previously Composite Views allow the nesting of reusable View elements, effectively building a View based on a hierarchy of Views. But often there are simpler solutions to simpler problems. One such solution is the Two-Step View pattern, sometimes called Layouts if implemented in a specific way (as we do below!).
Imagine a simple website. You have hundreds of pages of unique content, but the header and footer for each page is identical. Applying the Composite View approach has the same problem as applying a standard PHP include or Zend_View::render() call to including these common elements - the calls themselves are scattered across each and every template. This is the hallmark of a Layout - duplicated includes/renders across multiple views:
[geshi lang=php]render(‘standard_header.phtml’); ?>
Here is our unique content! But look, all unique templates
now have the same “standard” headers and footers defined by a
render() call. How do we remove these completely and apply them
automatically instead?
render(‘standard_footer.phtml’); ?>[/geshi]
The key is to take the duplicated calls and other duplicated markup and stick them in a Layout template which will encapsulate all Views automatically. Then the only thing our templates contain is unique content!
[geshi lang=php]
Here is our unique content template! But where have the
standard header and footer includes vanished to?
[/geshi]
All the Layout file needs to do is provide a “hook”, a method placement which signifies where the main View output (which is generated by the current dispatch cycle) should be placed. Since we define two parts the View here we’ll refer to them as the “Layout” and the “Main”. A Layout might look like:
[geshi lang=php]render(‘standard_header.phtml’); ?>
main(); ?>
render(‘standard_footer.phtml’); ?>[/geshi]
The new Zps_View::main() method (Zps_View is a subclass of Zend_View to which we can add customised behaviour) simply tells the View to render its output at this location in the template. This assumes the default Zend_View::render() method now takes a two step approach to rendering (this is where the Two-Step View Pattern comes into play).
1. Render a Layout if one is defined
2. Render the Main template into the Layout
The only funky logic is that the presence of a Layout forces our View object’s render method to take a detour so that Layouts are rendered first. This is a pretty simple change to Zend_View. Here’s our Zps_View class with the revised logic.
Please forgive the lack of proper phpDoc comments - Serendipity won’t play nice with them.
[geshi lang=php]// Zend_View */
require_once ‘Zend/View.php’;
// Zps_View_Interface */
require_once ‘Zps/View/Interface.php’;
class Zps_View extends Zend_View implements Zps_View_Interface
{
//
// The Main Template (i.e. the template file a Controller wishes
// to render).
// _mainFile cannot be set by a public setter so it doubles as
// as a safety valve to prevent unwarranted use of main().
//
// @var string
//
protected $_mainFile = null;
//
// The Layout Template
//
// @var string
//
protected $_layoutFile = null;
//
// Overrides Zend_View::render() to introduce a two step view approach when
// a Layout template has been defined. The two steps are handled using
// separate calls to parent::render() which calls the Zend_View render()
// method without overriding.
//
// @param string $name The script script name to process.
// @return string The script output.
//
public function render($name)
{
if ($this->hasLayout() && !isset($this->_mainFile)) {
$this->_mainFile = $name;
return parent::render( $this->getLayout() );
}
return parent::render($name);
}
//
// Set the filename of a Layout template to be used. The existence of a
// Layout filename will force the over-ridden render() method to detour
// and render the Layout, only rendering the Main template when a main()
// call is issued in the Layout template.
//
// @param $file string
// @return void
//
public function setLayout($file = ‘layout.phtml’)
{
$this->_layoutFile = $file;
}
//
// Return the filename of the Layout. Layouts are like any
// other template script and are located in the same place in
// application filesystem.
//
// @return string
//
public function getLayout()
{
return $this->_layoutFile;
}
//
// Returns true if a Layout has been set for this View.
//
// @return bool
///
public function hasLayout()
{
return isset($this->_layoutFile);
}
//
// Inform the View object that it should render the Main View, i.e.
// render the template handed to the render() method by a Controller.
// This method is only useful if a Layout is being used, otherwise
// expect an Exception.
//
// @return string
// @throws Zps_View_Exception
//
public function main()
{
if (isset($this->_mainFile)) {
return parent::render($this->_mainFile);
}
require_once ‘Zps/View/Exception.php’;
throw new Zps_View_Exception(‘Invalid call: There is no primary View template to render’);
}
//
// Method to clone this View assuming the sub-View (the clone) is from
// the same application Module as the original.
// Here we are simply getting rid of the inherited public variables which
// represent the ancestor View’s model.
// We also disable any Layouts (the inheritance would lead to infinite
// looping otherwise - Apache would bark and die on the spot!)
//
// @return null
//
public function __clone()
{
foreach(get_object_vars($this) as $key=>$value) {
$this->__unset($key);
}
$this->setLayout(null);
}
}[/geshi]
So there we go, functional code for allowing Layouts in a Two-Step View approach. Notice how the render() and main() methods interact. Because it’s both have very specific uses, main() is only useable within templates when injecting the main template into a Layout.
Sample usage is pretty simple - I won’t delve into any details since you just need two additional pieces of work when instantiating a View object:
1. Create a Layout template (you’ll notice a setLayout() default is “layout.phtml” but you’re not bound to that convention by any means.
2. Set the Layout on a View object, e.g.
[geshi lang=php]$view = new Zps_View;
$view->setBasePath(‘/path/to/default/view/directory’);
$view->setLayout(‘layout.phtml’); // this will force render() to perform a Two-Step
$view->render(‘sometemplate.phtml’);[/geshi]
If you followed my previous posts you’ll be able to integrate the Two-Step View/Layout approach pretty easily into your Views. Of course, as usual, a key observation is that in using a Zend_View subclass be sure not to rely on Zend_Controller_Action::initView(). You’ll need to override that method in your own application specific Zend_Controller_Action subclass.
If the lack of a Zps_View_Interface worries you it’s just a declaration of all the public methods above. I won’t post it here, this entry is long enough .
Any final words? A similar system is also possible using an alternate implementation. Matthew Weier O’Phinney, when I originally started asking about complex views in the Zend Framework, posted a Two-Step View implementation using a dispatchLoopShutdown() plugin. You can read more about this over at the mailing list archives - here’s the exact link to Matthew’s email.
http://framework.zend.com/wiki/display/ZFMLGEN/mail/27145
Finally, this is completely compatible with using a Composite View system. You can imagine creating a component full of widgets or plugin output. Each of these would be aggregated into a Composite View. But that doesn’t mean they don’t all share one thing - a common layout. So Layouts (Two-Step View) and Composite Views play quite nicely together.
Have fun! If anyone comes up more bright ideas throw them into a comment for the hordes of blog readers to consume!