Templating has been a recent issue up for debate in the PHP community, which is why I think it’s time to share what we’ve been up to with PEAR2 and Savant.
Savant is the foundation of the top PHP-based templating systems. In particular Solar View and Zend Framework’s View have roots in Savant and the great work of Paul M. Jones. Each of these are very successful templating systems, and they make for a proven foundation for templating and ease of use. So now that PEAR2 is gathering energy, and needs a strong templating system, Savant makes a good fit. But, with PEAR2, we have a lot of new features available with PHP 5.3, which means we can re-think some aspects of templating.
First, I want to talk about template/view annoyances, then about how Savant has solved these issues in PEAR2_Templates_Savant.
Annoyances:
1.) assigning data to the view
This is a problematic issue. Either you manually assign all of the data to the view, or choose individual pieces of data. Assigning individual pieces takes too much time and can lead to missing data in the view if new fields are added to a model and not assigned, or mismatched variable names. Data assignment is also difficult when the model uses magic methods or iterators for data retrieval.
2.) view selection
If you’re a sane person you probably have a pretty strong map between your models and views. Meaning, for the BaseballPlayer model, you’re probably going to be using a BaseballPlayer[.tpl.php|.phtml] file. This model to view mapping should be logical and happen automatically.
3.) isolation/sandboxing view data and template system
The data used within the view should be isolated from the templating system. The contextual data should not interfere with the templating system and vice-versa. Protected/private methods in the template system should not be callable within the template.
4.) view system setup
You should only have to set up your view system once per application, not per template.
5.) controller and flow logic in templates
It’s easier to maintain one template file for the site rather than separate sandwich templates pieces for the header and footer. The sandwich template method can lead to out of sync HTML tags, but it is difficult to use one template for the site without introducing controller logic into this view.
6.) data escaping
Sometimes this is necessary, sometimes it isn’t. Should the developer handle this or the designer? Does it matter? Flexibility should be allowed for either, and if we want data to be escaped it should happen automatically while still allowing access to un-escaped data if I know what I’m doing.
PEAR2_Templates_Savant – A New Hope
First, I want to talk about the concept of partial templates. If you’ve used the partial helper within Zend View or Solar View, you know how beneficial this can be. Partials are a simple way to break up large templates into smaller templates. This makes for a very object-oriented template usage, which matches many MVC applications, and also aids in data isolation and limiting variable scope. This is the default method of rendering in PEAR2_Templates_Savant.
Now the pitch.
How PEAR2_Templates_Savant fixes the annoyances above
1.) assigning data to the view
Because partials are the default method of rendering, there is no more assignment of data to the view. The data is made available to the template as the $context variable. This means instead of $view->name = $baseballplayer->name; or using $view->assign($baseballplayer);, you’ll simply use $view->render($baseballplayer, [tpl]);
This means that there is never any confusion about what data was assigned, or how to access the data from within the template. It also means that views have access to the public methods of the object rendered, which helps for objects which use getters and setters, or magic methods for data retrieval.
For those of you used to the old way, you can still pass a stdClass object with your own custom assignments.
2.) view selection
The render method accepts two parameters, contextual data and the template you wish to use. BOTH are optional arguments, but the magic happens when the template name is null.
Back to our ’sane’ developers who know that a BaseballPlayer object should always be rendered using the BaseballPlayer.tpl.php template. They have taken the time to build a good structure for the models in their applications, and by doing so, their life is made easier by eliminating what template to select. PEAR2_Templates_Savant uses a mapper which will map objects to templates (configurable of course). The default mapper simply maps class names to template files.
BaseballPlayer => BaseballPlayer.tpl.php
BaseballCoach => BaseballCoach.tpl.php
BaseballTeam_Phillies => BaseballTeam/Phillies.tpl.php
BaseballTeam\Phillies => BaseballTeam/Phillies.tpl.php
You can assign simple replacements to apply to the class name and the file extension is also customizable for the .phtml fans out there. Of course you can provide your own template mapper which implements the MapperInterface.
ClassToTemplateMapper.php
3.) isolation/sandboxing view data and template system
This is where PHP 5.3 shines. Closures allow us to create little sandboxes for data to play in. Feel free to pass in a __config value to the view, knowing that the data won’t interfere with the view object (who would do this?) – without using nasty global variables. Also, your template system can have private/protected methods which aren’t available inside the view which makes the class easy to extend if you need to customize. Side effect, we don’t use $this-> to get to the data, we use the $context to access data given to the view, and $savant to render further partial templates. Win for data and template separation!
4.) view system setup
Because partials are used as the default rendering, the view setup happens once and is used through the entire application.
5.) controller and flow logic in templates
When used in an application which maps objects to templates, this makes it very easy to pass off data to other templates without knowing the type of data. This means there’s no more controller-ish logic within a template such as:
<html>
…
<body>
<?php
if ($this->mydata instanceof BaseballPlayer) echo $this->partial('baseballplayer.tpl.php', array('name', $this->mydata->name));
elseif ($this->mydata instanceof BaseballTeam) echo $this->partial('baseballteam.tpl.php', array('name', $this->mydata->name));
?>
</body>
</html>
Now, you can create one outer page template, and hand off data to another template without having to know what type of data is:
<html>
…
<body>
<?php echo $savant->render($context->mydata); ?>
</body>
</html>
Objects are mapped to templates, arrays are looped over, strings, ints, doubles etc are returned after they’ve passed through any escape methods you’ve assigned.
6.) data escaping
If the developer assigns methods to escape data, this happens automatically. This is handled through an ObjectProxy which handles all requests to the inner object. If you need direct/raw access to unescaped data, you can use $context->__raw(’fieldName’);
This makes escaping easy, automatic, and avoidable if you know what you’re doing.
Teh End.
There are a few other nuances that I can share in another post, but those are the basics. Also, I want to really thank Greg Beaver who contributed many of the ideas for the latest version of Savant – including the title of this post, so thanks Greg!
So feel free to try it out and give some feedback. And so far things are shaping up nicely in the PEAR2 universe – definitely something to keep an eye on in my biased opinion.
A full example is available in the repository.
The development version of PEAR2_Templates_Savant is available through the svn repository, or installable with pyrus.
SVN:
svn checkout http://svn.php.net/repository/pear2/Templates_Savant/trunk
Browse online:
http://svn.php.net/viewvc/pear2/Templates_Savant/trunk/src/Templates/Savant/
On the (under construction) PEAR2 website:
PEAR2_Templates_Savant
Install with pyrus
php pyrus.phar /putithere install PEAR2_Templates_Savant-0.1.0