Latest: Genstatic, my first sip of coffee

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

  • Thanks a lot! I will try that soon since I'm heading to try this Zend framework and I'm kind of XSL lover... :)

    by Oscar Hiboux on February 10 2009, 04:33 - #

  • Oscar, it's up to you whether or not you want to head into that direction. Of course there are downsides as well, which shouldn't go unmentioned: You're going to loose access to Form decorators and various other helpers. Plus, it's not the most ZEND way of doing things, so you might loose maintainability if someone else used to ZF will have to work on it.

    by Pascal Opitz on February 10 2009, 07:15 - #

  • What about xsl tranformed by the client ? I can't find a way to do that since the client has to get the xsl file after it gets the xml.

    by NicKy on March 22 2009, 00:48 - #

  • Hi Pascal,

    I've just started to investigate the possibilities for the framework to implement xsl at rendering. I've worked with java and the spring framework and a kind of cascading xsl transformation. The controller fills a collection object with the various objects and reference data (as i18n language labels), which will be transformed to a kind of standard xml layout. Finally, this result is transformed to the final xhtml using common html transformation. I've tried CodeIgnitor, but I didn't manage to use the pear library, which has the XML/Serializer.php class. (also the XML/Unserializer.php class exists). I will try your demo, but also will try to extend the concept and maybe use caching for speed up.
    If you have worked out some more examples, I will be interrested.

    Just for NicKy, you can just add a reference to the xsl file like:

    
    <?xml-stylesheet type="text/xsl" href="D:\www\xslt\person.xsl"?>
    

    by Broer on April 29 2009, 18:33 - #

  • Hello, I haven't go any experiences with the PEAR library for object serialization. Let me just say this: The above demo is more meant to be a proof of concept, rather than a proper library suggestion. As far as I can tell, using XSLT will make a lot of things really hard in Zend, as you'll loose the ability to utilize built in things like View helpers and so on.

    I can see why, coming from something like a Spring Framework or C# background, you'd feel familiar with XSLT, but I also believe it's a "non-Zendy" way of doing things, if that makes sense.

    by Pascal Opitz on April 30 2009, 09:21 - #


Comments for this article are closed.