Zend Framework 1.10 and Smarty 3 RC4
Here is a quick tutorial on how to integrate Smarty 3 with Zend Framework. I’m currently using Zend Framework 1.10, but this integration should work for all versions of Zend Framework from 1.8 and above. With Smarty, I’m using Smarty 3 RC4. There are several different ways of integrating Smarty with Zend Framework, I find this the most straightforward.
With this implementation you will be able to use all of Zend Frameworks view helpers. You will be able to use Zend_Layout, and you will be able to split your Zend Framework application into modules. You will also be able to set Smarty configuration via Zend Frameworks application.ini configuration file.
First things first, download Zend Framework from The Zend Framework Website, and the same for Smarty from The Smarty Website be sure to download version 3. Setting up Zend Framework is not within the scope of this post. Please read the quick start guide on Zend’s website. If I get enough demand I may consider writing a quick tutorial on getting Zend Framework up and running.
If you have downloaded Zend Framework, and have followed the quick start guide, or already have an existing Zend Framework install, you should have a directory structure similar to the one below.
/myproject
/application
/configs
/layouts
/models
/modules
/mymodule
/controllers
/models
/views
/Bootstrap.php
/bin
/docs
/library
/public
/tests
Under the library directory create three folders, one for Smarty, one for Zend and one for your own library. Copy your Smarty download into the Smarty folder and copy the Zend Framework library files into the Zend folder. Your library folder structure should look like the example below.
/library
/Smarty
/plugins
/sysplugins
/debug.tpl
/Smarty.class.php
/Zend
/Acl
/Amf
/Application
/Auth
...
...
/Mine
Next we must implement our own view class, which will be used in place of Zend_View. It will extend Zend_View_Abstract which implements Zend_View_Interface. In this file we will be re-creating the methods that are contained within Zend_View, but our version will be setting and talking to Smarty. This file will be stored in your own library. Keeping in line with Zend Frameworks pear style naming convention, the name of the class in my structure will be Mine_View_Smarty, where “Mine” is the name of your library.
/library
/Mine
/View
/Smarty.php
This is what the file should look like;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | class Mine_View_Smarty extends Zend_View_Abstract { protected $_smarty; public function __construct($tmplPath = null, $extraParams = array()) { $this->_smarty = new Mine_Smarty(); if(null !== $tmplPath) { $this->setScriptPath($tmplPath); } foreach($extraParams as $key => $value) { $this->_smarty->$key = $value; } } public function _run() { parent::run(); } public function getEngine() { return $this->_smarty; } public function setScriptPath($path) { if(is_readable($path)) { $this->_smarty->template_dir = $path; return; } else { throw new Exception('Invalid path provided -> '.$path); } } public function getScriptPaths() { return array($this->_smarty->template_dir); } public function setBasePath($path, $classPrefix = 'Zend_View') { return $this->setScriptPath($path); } public function addBasePath($path, $classPrefix = 'Zend_View') { return $this->setSCriptPath($path); } public function __set($key, $val) { $this->_smarty->assign($key,$val); } public function __isset($key) { return (null !== $this->_smarty->get_template_vars($key)); } public function __unset($key) { $this->_smarty->clearAssign($key); } public function assign($spec, $value = null) { if(is_array($spec)) { $this->_smarty->assign($spec); return; } $this->_smarty->assign($spec, $value); } public function clearVars() { $this->_smarty->clearAllAssign(); } public function render($name) { return $this->_smarty->fetch($name); } } |
Next we create our own Smarty class, this will extend the original Smarty class. Any additional functionality that you would like to add to Smarty should be written to this class. At the moment mine is empty. Again following Zend Frameworks pear naming convention this file should sit in the following directory structure.
/library
/Mine
/Smarty.php
The file should look like this;
1 2 3 4 | require_once(APPLICATION_PATH .'/../library/Smarty/Smarty.class.php'); class Mine_Smarty extends Smarty { } |
OK, that’s all the foundation work finished. Now we just need to tie into the Zend Framework. To do this we create a controller plugin and override the “dispatchLoopStartup” method. Finally using the Bootstrap class we register our library namespace with the Zend autoloader, pull in the Smarty config and store it using Zend_Registry and register our newly created plugin with the front controller. This should make our version of Zend_View containing the Smarty implementation available to Zend Framework instead of the default view.
So the controller plugin should be saved to the following directory within your library.
/library
/Mine
/Controller
/Plugin
/View.php
The view plugin file should look like the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class Mine_Controller_Plugin_View extends Zend_Controller_Plugin_Abstract { public function dispatchLoopStartup(Zend_Controller_Request_Abstract $objRequest) { $inflector = new Zend_Filter_Inflector(APPLICATION_PATH.'/layouts/scripts/:script.:suffix') $inflector->setRules(array( 'script' => array('Word_CamelCaseToDash', 'StringToLower'), 'suffix' => 'tpl' )); Zend_Layout::startMvc( array( "layoutPath" => APPLICATION_PATH ."/layouts/scripts", "layout" => "layout", "viewSuffix" => "tpl", "inflector" => $inflector ) ); $arrSmartyConfig = Zend_Registry::get('config')->smarty->toArray(); foreach($arrSmartyConfig as $key => $config) { $arrSmartyConfig[$key] =str_replace(":moduleName:",$objRequest->getModuleName(),$config); } $view = new Ventutec_View_Smarty( APPLICATION_PATH .'/modules/'.$objRequest->getModuleName().'/views/scripts', $arrSmartyConfig ); $view->assign('this', $view); $viewRenderer =Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer'); $arrPath = $view->getScriptPaths(); $viewRenderer->setView($view) ->setViewBasePathSpec($arrPath[0]) ->setViewScriptPathSpec(':controller/:action.:suffix') ->setViewScriptPathNoControllerSpec(':action.:suffix') ->setViewSuffix('tpl'); } } |
So in the plugin, we setup Zend_Layout, telling it where to look for our layout files, and by using Zend_Inflector_Filter we setup some rules of what the naming conventions are for the layout files. We then pull in the Smarty configuration from the registry, convert it to an array and loop over the values replacing “:moduleName:” with the actual name of the module, which we can get from the request object. We then create an instance of our own View implementation, pass it the script path and the Smarty configuration array. Assign our view instance to the current view with the keyword “this”. This will be used later on in our templates. Finally register our view instance with the ViewRenderer.
Now we turn our attention to the bootstrap where we need to register the plugin with the front controller and save the smarty configuration in Zend_Registry. You can do this by adding the following methods to the applications bootstrap class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | protected function _initConfig() { $objConfig = new Zend_Config($this->getOptions(),true); Zend_Registry::set('config',$objConfig); return $objConfig; } protected function _initPlugins() { $front = Zend_Controller_Front::getInstance(); $front->registerPlugin(new Mine_Controller_Plugin_View()); return $front; } protected function _initLibraries() { $autoloader = Zend_Loader_Autoloader::getInstance(); $autoloader->registerNamespace('Mine_'); return $autoloader; } |
The first method _initConfig simply saves the whole config into Zend_Registry. This is one part of the integration I am not keen on. I just wish Zend_Application was a singleton so that the config was accessible throughout the application. So this to mean seems lazy and a bit of a hack. You could just save the smarty config to the registry, but why not save it all while we’re there. The second method _initPlugins, registers our newly created view plugin with the front controller and finally the third method _initLibraries register your own library namespace with Zend Frameworks autoloader.
I’ve mentioned saving the smarty config to the registry and using this config when creating an instance of our view in the view plugin, but I haven’t shown you an example of this config. Until now;
smarty.caching = 0 smarty.sub_dirs = 0 smarty.compile_check = 1 smarty.force_complile = 1 smarty.cache_dir = APPLICATION_PATH "/modules/:moduleName:/views/cache" smarty.compile_dir = APPLICATION_PATH "/modules/:moduleName:/views/templates_c"
Finally we need to create a Smarty plugin that wraps calls to Zend helpers. With Smarty 3 I believe this can be implemented much nicer using a Smarty resource plugin, but as yet I have been unable to get this method working. For now the plugin wrapper is the implementation I will be using.
Remember assigning an instance of the view class to the template using the keyword ‘this’? This was implemented in Mine_Controller_Plugin_View, this will enable you to call helpers that do not require parameters directly in Smarty templates without using the plugin wrapper, like the examples below.
1 2 | {$this->headScript()} {$this->headStyle()} |
However this will not work for helpers such as;
1 | $this->url($urlOptions, $name, $reset) |
For this we require the plugin wrapper. The plugin needs to be able to accept a helper name, and parameters, including arrays. It should look like the file below;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | function smarty_function_helper($params, $smarty, $template) { $output = 1; $arrVars = array(); if(isset($params['output'])) { $output = $params['output']; unset($params['output']); } if(isset($params['name'])) { $name = trim($params['name']); unset($params['name']); $function = '$smarty->tpl_vars[\'this\']->value->' .$name; foreach($params as $key => $value) { if(preg_match("/^([a-z0-9_\-]+\s?=>\s?[a-z0-9_\-]+,?){1,}$/",$value)) { $arrArrayStrings = explode(",",$value); $arrTmp = array(); foreach($arrArrayStrings as $string) { list($arrayKey,$arrayValue) = explode("=>",$string); $arrTmp[trim($arrayKey)] = trim($arrayValue); } $arrVars[] = $arrTmp; unset($arrTmp); } else { $arrVars[] = $value; } } if($output) { echo call_user_func_array(array($smarty->tpl_vars['this']->value,$name),$arrVars); } else { call_user_func_array(array($smarty->tpl_vars['this']->value,$name),$arrVars); } } else { trigger_error('No parameters provided to the helper plugin!'); } } |
This should be saved in a file called function.helper.php in one of your smarty plugin directories.
This plugin can now be used to call helpers that require parameters like so.
1 | {helper name=url options="action=>myaction,module=>mymodule,controller=>mycontroller" route="myroute"} |
The options parameter string will be seen by the plugin as an array. The plugin will generate an array from this string before passing it as a parameter to the helper. The route parameter is a normal parameter and will be passed as is to the helper method. Also what is really important when using this plugin, is the order in which the parameters are passed, not their keys. The parameter options could be called myoptions the key is irrelevant but because we are instructing the plugin to use the “url” view helper, this requires an array of options as its first parameter, so it must be passed to the plugin as the first parameter.




November 19th, 2010 at 10:43 pm
Would like to digg this article.
November 23rd, 2010 at 1:21 am
Please do, I’ve added a share bar to each post so this can be done quickly and simply. Thanks!
December 3rd, 2010 at 11:05 pm
Great article. Can you outline the benefits of adding Smarty to Zend?
December 14th, 2010 at 11:09 am
Joe, it’s all down to personal preference really, as we all know PHP is already a kind of template language. I just hate to see PHP code weaved into HTML, and prefer a clear separation between PHP and the markup. Smarty as well as other template engines give you this benefit.
December 30th, 2010 at 2:44 am
Well writed, thanks for this post!
August 12th, 2011 at 6:26 pm
Hi, would you have a zip file with the content of this sample site?
Thanks!
October 11th, 2011 at 2:16 pm
As a noob to Zend you’ve left me hanging. Here is what I know:
1. After a lot of research ZF is the framework I feel most fits my needs.
2. Like you, I feel that there needs to be a separation of the PHP code and the presentation code. To that end I feel Smarty is my best option for that.
3. (Most importantly) There are a lot of great examples of how to extend classes or implement interfaces but every example forgets the most important thing … What do I put in the .tpl, indexController and index.php files!!!
The example I found on the Zend site only had one class. It all seemed reasonable but when I tried to use it by changing the “quickstart” example … miserable fail. Nothing displayed. No source at all is being written to the page and no errors are being thrown to the logs.
So, I search some more and come across your example. Again, seems reasonable and logical … so I give it a try (or attempt to) but get to the end to find that I don’t know what to put in the .tpl, indexController and index.php file to get this to work. The guesses I’ve made obviously haven’t panned out or I wouldn’t be writing you.
Please complete your tutorial by showing us noobs to Zend and Smarty what we need to put in our .tpl, indexController and index.php file to use this class … or at least make the completed example project available for download so we can dissect it ourselves.
Thank you!
October 11th, 2011 at 3:40 pm
Hi DC,
Sorry you feel this article doesn’t explain my solution clear enough for noobs. This post was written almost a year ago, and there are perhaps better ways of doing this now. However I will try and clarify the points you made.
You do not need to do anything different in your controller files, simply use $this->view->variableName = ‘myvariableValue’ as you would if you wasn’t using Smarty. In your tpl files, you will be able to access the variable as you would in any Smarty project like so {$variableName}.
The index.php file should not be touched either. Smarty is setup using a controller plugin, which is registered in Bootstrap.php shown in the article above and in the comments below:
protected function _initPlugins() {
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Mine_Controller_Plugin_View());
return $front;
}
So to confirm, you will need to extend the view class (Mine_View_Smarty), create a controller plugin (Mine_Controller_Plugin_View), and register the plugin in Bootstrap.php.
October 12th, 2011 at 5:39 pm
Dave,
I apologize for bothering you again. Though your example may be a year old, it makes a lot of sense from an OOP standpoint and with some playing around I was able to get it working.
Your comments did help clear some things up. I’ve been working with what I have and now can display the main page, set the doctype using Smarty, display some array data, etc. with Smarty … all good. Thank you for that.
Now I’m trying to use the helper function to write a URL This is what I have in my code:
index,module=>default,controller=>guestbook” route=”default”}”>Guestbook
Again, still using the quickstart example as my basis and converting to the Smarty/Zend functionality. I get an error saying:
Warning: Missing argument 3 for smarty_function_helper(), called in ~cachedTemplateFile~ on line 42 and defined in /media/sda1/Interact/framework/library/Smarty/libs/plugins/function.helper.php on line 3
I copied your code above for the helper and, as you can see, I am using the code you supplied to call the helper. My values for action and route may be incorrect. I’m just going to the index file of the default module. Your example for the URL code using the wrapper doesn’t include a third argument and again, guessing hasn’t been successful. Additional insight would be greatly appreciated!
Thank you …
October 12th, 2011 at 5:44 pm
Sorry, the code part didn’t post correctly:
[a href="{helper name=url options="action=>index,module=>default,controller=>guestbook" route="default"}"]Guestbook[/a]
January 25th, 2012 at 9:05 pm
I have a problem … maybe i mixed up the directory structure …
Fatal error: Class ‘Mine_Controller_Plugin_View’ not found in /home/blacky/testapp/www/application/Bootstrap.php on line 13
January 26th, 2012 at 11:44 pm
It’s possible that I’ve made a mistake as I’m sure the methods in the Bootstrap are executed in order, therefore you will probably need to move the _initPlugins() method below the _initLibraries method.