In a previous post entitled "How well do you know your preferred scripting language?" I highlighted a few of PHP's less well known features. This post looks at another PHP function, extract, and related concepts I've found particularly useful recently.
The "extract" function takes an associative array and creates variables formed from it's key/value pairs. For example:
<?php$aPost = array('title' => 'Do links on your site make sense out of context?','author' => 'Ed Eliot');extract($aPost);// output variables created by extractecho "Title: $title, Author: $author";?>
"extract" is essentially a shortcut to using variable variables. The following code is functionally equivalent to "extract":
<?phpforeach ($aPost as $sKey => $vValue) {$$sKey = $vValue;}?>
A Practical Example
A little while ago I stumbled across an article written by Brian Lozier which questioned the need for PHP templating engines like Smarty. After all PHP already performs this job very well, why build another on top of it with new syntax to learn and all the associated performance impacts.
What he proposes instead is a lightweight Template class which uses PHP as it's templating language. This way one benefits from the performance advantages of using native PHP in templates whilst still maintaining the abstraction of business logic and presentation. In his example "extract" is used to pass variables set in the scope of the class to the template. This means that variables can be referenced this way:
<?=$title?>
in templates as opposed to:
<?=$this->aVars['title']?>
My Version
I've put together my own version of this idea which:
- Adds support for post filter functions by making use of a related PHP construct, variable functions
- Adds Options for automatically stripping HTML and encoding entities when setting variables
- Makes variables set in the parent template available to sub-templates but honours template specificity. It won't override existing variables set against the sub-template with the same name as variables defined in the parent.
- Provides a simple debug mechanism which makes it easier to determine which templates are outputting which content.
A Simple Example
<?php$oTemplate = new Template('test.php');$oTemplate->Set('title', 'Do links on your site make sense out of context?');$oTemplate->Set('author', 'Ed Eliot');echo $oTemplate->Display();?>
The corresponding template file might look like this:
<h1><?=$title?></h1><p><?=$author?></p>
An Example of Nested Templates
One might use this to define a parent layout file and a sub template for content. In the context of HTML this would allow one to use one file to contain all framework HTML rather than separate files for header and footer.
<?php$oTemplate = new Template('layout.php');$oTemplate->Set('title', '<a href="">Do links on your site make sense out of context?</a>');$oTemplate->Set('author', 'Ed Eliot');$oTemplate->Set('content', new Template('content.php'));echo $oTemplate->Display();?>
The parent template, the overall site layout HTML, might look like this:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title><?=$title?></title></head><body><?=$content?></body></html>
and the sub-template, for the content, like this:
<h1><?=$title?></h1><p><?=$author?></p>
Using Post Filters
A post filter could be used, for example, to process replacement of translation keys. In the simple example below all instances of my name in the compiled templates are wrapped with <em> tags.
<?phpfunction EmphasiseName($sContent) {return str_replace('Ed Eliot', '<em>Ed Eliot</em>', $sContent);}$oTemplate = new Template('test.php');$oTemplate->Set('title', '<a href="">Do links on your site make sense out of context?</a>');$oTemplate->Set('author', 'Ed Eliot');$oTemplate->Set('content', new Template('content.php'));$oTemplate->AddPostFilter('EmphasiseName');echo $oTemplate->Display();?>
Debugging
In complex sites it may be hard to tell at a glance which template is outputting what. The class includes a debugging option which outputs HTML comment markers at the start and end of the output of each template.
<!-- start test.php --><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Do links on your site make sense out of context?</title></head><body><!-- start content.php --><h1>Do links on your site make sense out of context?</h1><p>by <em>Ed Eliot</em></p><!-- end content.php --></body></html><!-- end test.php -->
Download
- Download my template class.
- Download updated template class with caching support. Read more about this updated class here.
<?phpextract($_GET, EXTR_PREFIX_ALL, 'imported');echo $imported_title;?><?php$title = 'Do links on your site make sense out of context?';$author = 'Ed Eliot';$aPost = compact('title', 'author');print_r($aPost);?>Array([title] => Do links on your site make sense out of context?[author] => Ed Eliot)
A Word on Syntax
The syntax used in these examples require you to configure PHP to recognise short open tags. This can be done in PHP ini configuration file or when compiling PHP with "--enable-short-tags".
Extract: A Word of Caution
As stressed in the PHP Manual, avoid using "extract" on the super global arrays ($_GET, $_POST etc). Doing so has the same effect as having register_globals switched on and will result in security holes in your code. If you absolutely have to do this then make sure that you pass configuration options to "extract" to ensure it doesn't overwrite existing variables by prepending a standard prefix to each variable as shown in the example below (or by skipping variables which already exist with option "EXTR_SKIP"):
You'll also need to validate the contents of those variables before using them to ensure they don't contain harmful data - it basically boils down to never trusting user entered data.
Let's Reverse Things
To finish up I thought I'd mention the "compact" function which does the exact opposite of "extract" - pass it variable names and it returns an associative array with key/value pairs matching their names and values.
would output:
I've never liked Smarty myself, felt like it complicated the job more than it benefit. Adding the extra overhead to the equation and it comes us as just plain stupid.
Your class is definitely the proper way to go about it. Except of course the shorthand tags. That's not very portable. :)
Martin - Totally, although Smarty is more efficient than most it's still an awful lot of extra code to perform a relatively simple task. I take your point about short tags although there is no reason why one has to use them and I do think they are simpler in the context of the templates.
My top tip for the use of extract() is to only ever use it inside of a small, dedicated function. That way, anything that might be accidentally or maliciously overwritten is out of variable scope.
what is the license of yout template class? at the very begining is placed "Copyright By Ed Elliot" which in my opinion prohibits its use -- the exclusive copyright is to you and it does not mention anything about using by someone else? Am I mistaken?
nedko - when I posted the code I hadn't decided what license I wanted to go with so I simply put a generic copyright notice on it. It wasn't intended to limit use. I've now updated it with a FreeBSD license notice.
thank you
I am very concerned that in your article you do not mention the security issues of using the extract method.
If somebody follows your example and thinks wow thats handy! They are likely to open up their system for remote include injection attacks, SQL insertion and a host of other nasties.
You should at least tell them to use a prefix!! lol.
Thank you for the explanation and the interesting comments as well. Regards from www.webtechnik.net
Antony - Not to put to finer point on it but I suggest you go back and re-read my post in full. I included a complete sub-section entitied "Extract: A Word of Caution" on possible security issues with using extract.
Just to re-iterate to anyone else stopping by make sure you read that sub-section and be sure you understand the potential security implications of "extract" before using it.
We are always looking for tips regarding PHP else. Thank you for sharing you knowledge with the internet community. Best regards
Very good Article. Using X-Cart with Smarty and was thinking the other the same thing. Populating an object in PHP and then population the same object to be use for smarty is very time consuming. You don't see with a few products on your System, but when you have 50k it makes sense. Elliot keep the good Work!!!
Thanks for the tip. To facilitate the separation of application code from presentation is very useful.
Your Tipp is very helpfull. Thx. Please change your colours - black and green isnīt good for my eyes ;-)
Thanks for all the different interesting articles. We like to visit your website and the diverse postings.
Hey, that is very good Tutorial ... thank you.