Writing a CMS/Community with Smarty and the Zend Framework Part 2


Before we begin I want to stress that this is not a newbie tutorial. If the current level is too much for you I recommend these tutorials:
For the Zend Framework
For Smarty

Let’s begin with menu generation:
In my index.tpl (which is based on YAML) the following will render a menu:

{insert name=”item” controller=”Menu” id=”topmenu”}

Ok so it’s a Smarty insert function, lets take a look at the function:

function insert_item($params){
    $function = empty($params['function']) ? 'render' : $params['function'];
    $controller = empty($params['controller']) ? 'index' : $params['controller'];
    return Common::loadController($controller)->$function($params);
}

Since we didn’t pass a function in the parameter list it will default to “render” in the above example. The controller is apparently called “Menu”. Let’s look at the current code for MenuController:

class MenuController extends ExtController{

    function init(){
        parent::init();
        $this->name = 'menu';
        $this->obj = Common::loadModel($this->name);
        parent::finishInit();
    }

    function render($params){
        $menu = $this->obj->fetchRowToArr($params['id'], false, 'menu_slug');
        $menu_items = $menu->findDependentRowset('menuitem', 'db_menu')->toArray();
        Common::sort2DAsc($menu_items, 'position');
        $this->smarty->assign('menu', $menu->toArray());
        $this->smarty->assign('menuitems', $menu_items);
        return $this->fetch();
    }

}

Common is a class with only static methods used to kind of extend the basic functions of PHP, that is also the place where I have all my factory functions. ExtController is a custom class I use to extend Zend_Controller_Action with. All my controllers extend this class. And the top of it looks like this:

class ExtController extends Zend_Controller_Action{

    function __construct($request = false, $response = false, $invokeArgs = false){
        if(!$request)
			$request = new Zend_Controller_Request_Http();

        if(!$response)
			$response = new Zend_Controller_Response_Http();

        if(!$invokeArgs)
			$invokeArgs = array();

        parent::__construct($request, $response, $invokeArgs);
    }

    function init(){
        $registry = Zend_Registry::getInstance();
        $this->smarty = new Smarty_Zend();
        $this->date_format = $registry['date_format'];
        $this->db = $registry['db'];
        $this->mysql_date_format = $registry['mysql_date_format'];
        $this->base_url = $this->_request->getBaseUrl();
        $this->page_limit = 3;
        $this->globalsess = new Zend_Session_Namespace('globalsess');
        $this->globalsess->language = "eng";
        $this->acl = new Zend_Session_Namespace('acl');
        $this->smarty->assign('baseUrl', $this->base_url);
        $this->smarty->assign('date_format', $this->date_format);
    }

    function finishInit(){
        $this->smarty->assign('controller', $this->name);
        $this->template = $this->name.".tpl";
        $this->session = new Zend_Session_Namespace($this->name);
        $this->smarty->assign('config_file', “{$this->globalsess->language}_{$this->name}.conf");
    }

    function display($template = false){
        if($template == false){
            $page = utf8_encode($this->smarty->fetch($this->template));
        }else
			$page = utf8_encode($this->smarty->fetch($template));
        echo $page;
        exit;

    }

    function fetch($template = false){
        if($template == false)
            return utf8_encode($this->smarty->fetch($this->template));
        else
            return utf8_encode($this->smarty->fetch($template));
    }

Apparently we create an instance of some model in the MenuController, let’s first look at the Common::loadModel() factory function:

class Common{
    static function loadModel($table){
        $class_name = strtolower($table);
        include_once($class_name.".php");
        return new $class_name;
    }

And this is the contents of models/menu.php:

class Menu extends ExtModel{
    protected $_name = 'db_menus';
    protected $_primary = 'id';
}

In the MenuController you might have noticed that we use the ZF function findDependentRowset with ‘menuitem’ so lets take a look at models/menuitem.php too:

class Menuitem extends ExtModel{
    protected $_name = 'db_menuitems';
    protected $_primary = 'id';
    protected $_referenceMap = array(
        'db_menu' => array(
            'columns' => 'db_menu_id',
            'refTableClass' => 'Menu',
            'refColumns' => 'id',
        )
    );

}

All my models extend a class called ExtModel, let’s look at that and the fetchRowToArr function that is called in the MenuController:

class ExtModel extends Zend_Db_Table_Abstract{

    function fetchRowToArr($id_value = false, $as_array = true, $id_field = false, $where = false){
        if($where != false){
            $where_sql = array();
            foreach($where as $field => $value)
                $where_sql[] = $this->getAdapter()->quoteInto("$field = ?", $value);
            $row = $this->fetchRow($where_sql);
        }else if($id_field == false){
            $row = $this->find($id_value)->current();
        }else{
            $where = $this->getAdapter()->quoteInto("$id_field = ?", $id_value);
            $row = $this->fetchRow($where);
        }

        if($as_array == true && $row != false)
            return $row->toArray();
        else
            return $row;
    }

The name of the fetchRowToArr function is not representative, It should really be renamed since it obviously can return row objects too. Now that we have all this information let’s recap what happens in the MenuController:

1. The init() function is always run with some general stuff through parent::init() and parent::finishInit(). We will use the variable name consistently to emulate some of the convention over configuration mentality of Ruby on Rails.
2. The render() function is called with the id ‘topmenu’ which we first use to retrieve all the information for that menu through fetchRowToArr().
3. Next we use that information to retrieve all menuitems with the help of the $_referenceMap in the menuitem model.
4. The two dimensional $menu_items array gets sorted by position. I couldn’t find any way to do this through findDependentRowset() or the reference map in the menuitem model. If you know of some way of getting the ORDER BY clause into the underlying SQL (without changing the source of course), please comment on this post!
5. We assign the menu and the items it contains to the template.
6. Finally we return the result which will replace the smarty insert code above.

The menu.tpl template which we use in the above code looks like this if a menu is set as “flowing” for instance:

{elseif $menu.menu_type eq “flowing"}
	<table class="{$menu.menu_class}">
		<tr>
			<td>
			{foreach from=$menuitems item=menu_item}
				{if empty($menu_item.item_class)}
					<span class="{$menu.menu_itemclass}">
				{else}
					<span class="{$menu_item.item_class}">
				{/if}
				
				<a href="{$baseUrl}/{$menu_item.item_link}">
					{if empty($menu_item.item_image) == false}
						<img src="{$baseUrl}/images/{$menu_item.item_image}" alt="{$menu_item.image_alt}" />
					{/if}
					
					{if empty($menu_item.link_text) == false}
						{$menu_item.link_text}
					{/if}
				</a>
				</span>
			{/foreach}
			</td>
		</tr>
	</table>
{/if}

And lastly the SQL for the menus and menuitems:

CREATE TABLE `db_menus` (
`id` bigint(12) NOT NULL auto_increment,
`menu_class` varchar(250) NOT NULL,
`menu_itemclass` varchar(250) NOT NULL,
`menu_type` varchar(250) NOT NULL,
`menu_slug` varchar(250) NOT NULL,
PRIMARY KEY  (`id`),
UNIQUE KEY `menu_slug` (`menu_slug`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `db_menuitems` (
`id` bigint(12) NOT NULL auto_increment,
`db_menu_id` bigint(12) NOT NULL,
`item_class` varchar(250) NOT NULL,
`item_link` varchar(250) NOT NULL,
`item_image` varchar(250) NOT NULL,
`image_alt` varchar(250) NOT NULL,
`link_text` varchar(250) NOT NULL,
`position` bigint(12) NOT NULL,
`item_imageover` varchar(250) NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

In the next part of this series we will take a look at the main content area where we display articles etc. To Part 3

Related Posts

Tags: , ,