Latest: XHTML Validation with the W3C validator and PHP

Content with Style

Web Technique

Zend Framework: XSL and self-serializing Views

by Pascal Opitz on November 17 2008, 01:39

A long time ago I argued that MVC style frameworks should use XSL instead of inline PHP code and so on. That's why I knocked together a little proof of concept for Zend Framework where the views files are XSLs and the View object serializes itself into XML for rendering.

Basic MVC structure

I just created a demo layout, utilizing the standard MVC structure of Zend_Controller:

 |-application
 |---default
 |-----controllers
 |-----models
 |-----views
 |-------filters
 |-------helpers
 |-------scripts
 |---------index
 |---------test
 |-library
 |---demo
 |---zendframework_1.6.2
 |-webroot

Of course now we need a bootstrap file:

set_include_path('.'
	. PATH_SEPARATOR . '../library/zendframework_1.6.2/'
	. PATH_SEPARATOR . '../library/demo/'
	. PATH_SEPARATOR . '../application/default/controllers'
	. PATH_SEPARATOR . get_include_path());

require_once('Zend/Loader.php');
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Controller_Action_Helper_ViewRenderer');

$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory(array(
	'default' => '../application/default/controllers',
));

require_once 'View_Xslt.php';
$view = new View_Xslt;
$options = array();
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view, $options);
$viewRenderer->setViewSuffix('xsl');
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

$frontController->dispatch();

Note that I have provided a new viewRenderer and view object, which is called View_Xslt.php and is located in the library/demo folder. Also I set the view suffix to xsl.

A ZIP file containing the whole demo (excluding the Zend Framework files) can be downloaded here.

The View Object

The view object itself needs to be a class that extends Zend_View_Abstract. The rendering of the views happens in the _run method, and the view file will be passed as the first argument. However, this argument needs to be accessed with func_get_arg, otherwise we're confronted with a neat error message that our declaration is incompatible with Zend_View_Abstract.

In order to make my view object self-serializing later, I also added the Serializer in the constructor magic method, plus I added a private function that serializes the view into XML using the Serializer just created.

require_once('Serializer.php');

class View_Xslt extends Zend_View_Abstract
{
 private $serializer;
 private $rootName;
 
 public function __construct($data = array()) {
 $this->serializer = new Serializer();
 parent::__construct($data);
 }

 public function setRootName($name) {
 $this->rootName = $name;
 }
 
 protected function _run() {
 $template = func_get_arg(0); 
 $xslDoc = new DOMDocument();
 $xslDoc->load($template);
 $xmlDoc = $this->toXml();
 $proc = new XSLTProcessor();
 $proc->importStylesheet($xslDoc);
 echo $proc->transformToXML($xmlDoc);
 }
 
 private function toXml() {
 $xml_str = $this->serializer->Serialize($this, $this->rootName);
 return $xml_str;
 }
}

The Serializer

So what does this Serializer do? It utilizes the Reflection functionality to serialize the object into an XML string. This enables us to normally assign variables to the view from within our controller actions, just by saying $this->foo = 'bar'.

I did a quick post on XML Serialization before, and the Serializer I have provided is inspired by what I have found there. DISCLAIMER: Keep in mind that this is just a proof of concept, and to get this working perfectly it probably needs a bit more work.

class Serializer
{
 private $xmlDoc;
 
 public function __construct() {
 $this->xmlDoc = new DOMDocument();
 }
 
 public function Serialize($inst, $nodeName=null) {
 if(is_object($inst))
 {
  $nodeName = ($nodeName == null) ? get_class($inst) : $nodeName;
  $root = $this->xmlDoc->createElement($nodeName);
  $this->xmlDoc->appendChild($root);
  $this->SerializeObject($inst, $nodeName, $root);
 } else if(is_array($inst)) {
  $nodeName = ($nodeName == null) ? get_class($inst) : $nodeName;
  $root = $this->xmlDoc->createElement($nodeName);
  $this->xmlDoc->appendChild($root);
  $this->SerializeArray($inst, $nodeName, $root);
 }

 return $this->xmlDoc;
 }
 
 private function SerializeObject($inst, $nodeName, $parent) {
 $obj = new ReflectionObject($inst);
 $properties = $obj->getProperties();
 
 foreach($properties as $prop) {
  if(!$prop->isPrivate()) {
  $elem = $this->SerializeData($prop->getName(), $prop->getValue($inst), $parent);
  }
 }
 }
 
 private function SerializeArray($array, $nodeName, $parent) {
 foreach($array as $key => $val) {
  $keyStr = (is_numeric($key)) ? 'ArrayValue' : $key;
  $elem = $this->SerializeData($keyStr, $val, $parent);
  
  if(is_numeric($key)) {
  $elem->setAttribute('index', $key);
  }
 }
 }
 
 private function SerializeData($key, $val, $parent) {
 if(is_object($val)) {
  $propNodeName = get_class($val);
  $elem = $this->xmlDoc->createElement($propNodeName);
  $parent->appendChild($elem);   
  $this->SerializeObject($val, $propNodeName, $parent);
  $elem->setAttribute('type', 'object');
 } else if(is_array($val)) {
  $elem = $this->xmlDoc->createElement($key);
  $parent->appendChild($elem);
  $this->SerializeArray($val, $key, $elem);
  $elem->setAttribute('type', 'array');
 } else {
  $elem = $this->xmlDoc->createElement($key, $val);
  $parent->appendChild($elem);
  $elem->setAttribute('type', 'property');
 }
 
 return $elem;
 }
}

Controller and View files

Nearly there. We'll just need some XSL file and a controller with an action to get the demo running. First the controller and action. I included a little Demo class, so we can see the Serializer in action:

class IndexController extends Zend_Controller_Action {
	public function indexAction() {
	 $this->view->setRootName('DataObject');

	 $this->view->foo = 'bar';
	 $this->view->super = array(
	 'here' => 'there', 'foo' => array(1,2,'test'),
	 );
	 $this->view->testObject = new DemoObject();
	 $this->view->testObject->var = 'testObjectVar';
	}
}

class DemoObject {}

Then the view file(s). You could get away with just one, but because I wanted to imitate Zend_Layout, I did utilize xsl:import in order to do something similar.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:import href="../layout.xsl"/>

 <xsl:template match="DataObject">
 <xsl:apply-templates select="*" />
 </xsl:template>
 
 <xsl:template match="*">
 <div>
  <h2><xsl:value-of select="name()" /></h2>
  <xsl:apply-templates select="text()" />
  <xsl:apply-templates select="*" />
 </div> 
 </xsl:template>
</xsl:stylesheet>
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" encoding="ISO-8859-1" omit-xml-declaration="no" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" indent="yes" />
 <xsl:template match="/">
 <html>
  <head>
  <title>Test</title>
  </head>
  
  <body>
  <xsl:apply-templates select="/*" />
  </body>
 </html>
 </xsl:template>
</xsl:stylesheet>

Result

And that's it! Rendering the index page should give you an output looking somehow like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
 <title>Test</title>
 </head>
 <body>
 <div><h2>foo</h2>bar</div>

 <div>
 <h2>super</h2>
 <div><h2>here</h2>there</div>
 <div>
 <h2>foo</h2>
 <div><h2>ArrayValue</h2>1</div>

 <div><h2>ArrayValue</h2>2</div>
 <div><h2>ArrayValue</h2>test</div>
 </div>
 </div>
 <div>
 <h2>DemoObject</h2>

 </div>
 <div><h2>var</h2>testObjectVar</div>
 </body>
</html>

Comments

Don't miss the opportunity to leave the first comment.

Leave your comment

Comments are moderated.
Tags allowed: a, strong, em, code, ul, ol, li, q, blockquote, br, p

Advertisement

Want to buy a cheap laptop for your design work? read laptop reviews at laptopical.com