Zend View Helper and Fluent Interfaces

Lately, I’ve been building a new website using Zend Framework as a base. In addition to having easy to use bootstrap utilities and a solid MVC structure, Zend also has numerous view helpers to assist with common tasks.

…Left, Right, Left, Right, Left…

One common styling feature commonly seen in data grids is alternating row colors. Normally in PHP, you would handle that using something like:

<table>
	...
	<?php $row = 0; ?>
	<?php foreach($items as $item) { ?>
		<?php if($row == 2) $row = 0; ?>
		<tr class="row<?=$row++;?>">
			...
		</tr>
	<?php } ?>
	...
</table>

This works, but it’s not very elegant and pollutes the view namespace with unnecessary variables and code. It would be nice if there was a way to encapsulate the code into a single statement. Fortunately, Zend has a view helper called Cycle that does just that:

<table>
	...
	<?php foreach($items as $item) { ?>
		<tr class="row<?=$this->cycle(array(1,2))->next()?>">
			...
		</tr>
	<?php } ?>
	...
</table>

Much better! Now everything regarding the alternate rows is in a single place. Now, what if instead of alternating every row, you want to alternate every other row? There are two obvious alternatives:

<table>
	...
	<?php foreach($items as $item) { ?>
		<tr class="row<?=$this->cycle(array(1,1,2,2))->next()?>">
			...
		</tr>
	<?php } ?>
	...
</table>
<table>
	...
	<?php $row = 0; ?>
	<?php $cycle = $this->cycle(array(1,2)); ?>
	<?php foreach($items as $item) { ?>
		<?php if($row++%2==0) $cycle->next(); ?>
		<tr class="row<?=$cycle?>">
			...
		</tr>
	<?php } ?>
	...
</table>

Both of these have issues. The first would need a lot of repetition in the array if you wanted to alternate between five numbers every 4 rows instead. The second example is back to polluting the view namespace.

Fluent Interfaces

Declarative languages like SQL are useful because they allow the user to specific what they want without having to worry about the hairy implementation that goes on behind the scene. The user can say “I want this data satisfied these criteria” and the database engine will take care of putting together an efficient search query to get the desired data.

One way to get this kind of declarative power in other languages is to use what is called a “fluent” interface. The idea is to design your API so that you can chain configuration calls into a kind of domain specific language for that class. For example, a fluent cycling view helper would allow us to easily specify:

  1. What values to cycle between
  2. What string to insert the values into
  3. How often to switch to the next value

Something like:

$this->alternate()->between(array(1,2,3,'a','b','c'))->every(2)->using('this is the {N} row');

Using this is a loop would output:

this is the 1 row
this is the 1 row
this is the 2 row
this is the 2 row
this is the 3 row
this is the 3 row
this is the a row
this is the a row
this is the b row
this is the b row
this is the c row
this is the c row
this is the 1 row
...

Setting up a fluent interface like this isn’t very difficult. The critical step is to simply return $this from every method which sets parameters. Below is the class I implemented for my website which uses this fluent interface to alternate values in loops.

Creative Commons License
This PHP code snippet is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

class ATP_View_Helper_Alternate
{
	protected $_every = 1;
	protected $_template = '{N}';
	protected $_modeMap = array(0=>1,1=>2);
	
	protected $_curCount = 0;
	protected $_curMode = 0;
	
	protected static $_alternators = array();
	
	public function alternate($template = '')
	{
		if(!isset(self::$_alternators[$template]))
		{
			self::$_alternators[$template] = new self();
			self::$_alternators[$template]->using($template);
		}
		
		return self::$_alternators[$template];
	}
	
	public function using($template = '')
	{
		if(strpos($template,'{N}')===false)
		{
			$template .= '{N}';
		}
		$this->_template = $template;
		return $this;
	}
	
	public function every($times)
	{
		$this->_every = intval($times) ? intval($times) : 1;
		return $this;
	}
	
	public function between($modes)
	{
		if(is_array($modes))
		{
			$this->_modeMap = array_values($modes);
		}
		else
		{
			$modes = intval($modes) ? intval($modes) : 2;
			$this->_modeMap = array();
			while(count($this->_modeMap)<$modes)
			{
				$this->_modeMap[] = count($this->_modeMap)+1;
			}
		}
		
		return $this;
	}
	
	public function __toString()
	{
		$str = str_replace("{N}",$this->_modeMap[$this->_curMode],$this->_template);
		
		$this->_curCount++;
		if($this->_curCount == $this->_every)
		{
			$this->_curCount = 0;
			$this->_curMode++;
			if($this->_curMode == count($this->_modeMap))
			{
				$this->_curMode = 0;
			}
		}
		
		return $str;
	}
}