• Share
  • Sharebar
  • Share

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.