Creating dynamic finders for Zend Db Table

I don't have to tell you that programmers are lazy.
That's why we love DRY (Don't Repeat Yourself).
If you find yourself creating lots of methods like findByName or findByTitle etc., you're going love what I'm about to show you.
With the dynamic finders you can use the following methods out of the box (ofcourse you'll have to extend your DbTable class to it).
- findByX('valueX')
- findLikeX('ValueX')
- findByXAndY('ValueX', 'ValueY')
- findLikeXAndY('ValueX', 'ValueY')
- findAllByX('valueX')
- findAllLikeX('valueX')
- findAllByXAndY('ValueX', 'ValueY')
- findAllLikeXAndY('ValueX', 'ValueY')

Where X is a column name of your table and Y is a second column name. You can chain it infinitely with 'And'.
For example findByNameAndIsActive('Mpie', 1);
This would fire up a fetchRow query: "where name='Mpie' And is_active = 1" wich returns only 1 rowset
FindLike will ofcourse do a Like query and findAll will return more than 1 rowset by using a fetchAll.
As you can see the camelcased letter gets an underscore as a sperator. This is all done in the code I'm going to show you.

<?php

abstract class My_Db_Table extends Zend_Db_Table_Abstract {

/**
* Call method used to implement the dynamic finders
*
* @param string
* @param array
* @return function || void
*/
public function __call($method, $args)
{
$watch = array('findBy','findAllBy', 'findLike', 'findAllLike');
foreach ($watch as $found)
{
if (stristr($method, $found))
{
$fields = str_replace($found, '', $method);
return $this->{'_' . $found}($fields, $args);
}
}

$watch = array('paginateBy','paginateAllBy', 'paginateLike', 'paginateAllLike');
foreach ($watch as $found)
{
if (stristr($method, $found))
{
$fields = str_replace($found, '', $method);
return $this->{'_' . str_replace ('paginate', 'find', $found)}($fields, $args, false);
}
}
throw new Exception("Call to undefined method IDG_Db_Table::{$method}()");
}

/**
* Find By
*
* This method only ever returns the first record it finds!
*
* @param string
* @param array
* @return object|false
*/
protected function _findBy($columns, $args, $rowset = true)
{
$stmt = $this->_buildQuery($columns, $args);
if ($rowset)
{
return $this->fetchRow($stmt);
} else {
return new Zend_Paginator(new Zend_Paginator_Adapter_DbTableSelect($stmt));
}

}

/**
* Find All By
*
* @param string
* @param array
* @return object|false
*/
protected function _findAllBy($columns, $args, $rowset = true)
{
$stmt = $this->_buildQuery($columns, $args);
if ($rowset)
{
return $this->fetchAll($stmt);
} else {
return new Zend_Paginator(new Zend_Paginator_Adapter_DbTableSelect($stmt));
}
}

/**
* Find Like
*
* This method only ever returns the first record it finds!
*
* @param string
* @param array
* @return object|false
*/
protected function _findLike($columns, $args, $rowset = true)
{
$stmt = $this->_buildQuery($columns, $args, $like = true);
if ($rowset)
{
return $this->fetchRow($stmt);
} else {
return new Zend_Paginator(new Zend_Paginator_Adapter_DbTableSelect($stmt));
}
}

/**
* Find All Like
*
* @param string
* @param array
* @return object|false
*/
protected function _findAllLike($columns, $args, $rowset = true)
{
$stmt = $this->_buildQuery($columns, $args, $like = true);
if ($rowset)
{
return $this->fetchAll($stmt);
} else {
return new Zend_Paginator(new Zend_Paginator_Adapter_DbTableSelect($stmt));
}
}

/**
* Builds the query for the findBy methods
*
* @param string
* @param array
* @return Zend_Db_Select
*/
protected function _buildQuery($columns, $args, $like = false)
{
$fields = explode('And', $columns);
$count = count($fields);

if ($like === true)
{
$where = "{$this->_filterField($fields[0])} LIKE ?";
$where_args = '%' . str_replace(array(' ', '&', '-'), '_', $args[0]) . '%';
} else {
$where_args = $args[0];
if (is_array ($where_args))
{
$where = "{$this->_filterField($fields[0])} IN (?)";
} else {
$where = "{$this->_filterField($fields[0])} = ?";
}
}

unset($args[0]);

$select = $this->select();
$select->where($where, $where_args);

if ($count > 1)
{
array_shift($fields);
foreach ($fields as $field)
{
$where = "{$this->_filterField($field)} = ?";
$where_args = array_shift($args);
$select->where($where, $where_args);
}
}

return $select;
}

/**
* Converts a camelCased word into an underscored word
*
* @param string
* @return string
*/
protected function _underscore($word)
{
$word = preg_replace('/([A-Z]+)([A-Z])/', '\1_\2', $word);
return strtolower(preg_replace('/([a-z])([A-Z])/', '\1_\2', $word));
}

/**
* Converts field name to lowercase and if camelcased, converts to underscored
*
* @param string
* @return string
*/
protected function _filterField($item)
{
$item = $this->_underscore($item);
return strtolower($item);
}

}

As an extra, we have access to the following methods:
- paginateByX('valueX')
- paginateLikeX('ValueX')
- paginateAllByByX('valueX')
- paginateAllLikeX('valueX')
(And ofcourse the "And" chains)
These methods returns a Zend_Paginator object, so you can have a paginator right away without messy SQL codes in your Controller.

For those who didn't get how to use it:

// Default_Model_DbTable_Authors class
class Default_Model_DbTable_Authors extends My_Db_Table
{
protected $_name = 'authors';
}

// Any controller or Model or wherever you want to call the finders
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$authorTable = new Default_Model_DbTable_Authors;
$author = $authorTable->findByNameAndIsActive('Mpie', 1);
var_dump($author);die; // This should return the active author called Mpie else returns NULL
}

public function listAction()
{
$authorTable = new Default_Model_DbTable_Authors;
$current_page = (int) $this->getRequest()->getParam('page', 1);
$paginator = $authorTable->paginateAllByIsActive(1); // Returns a Zend_Paginator object
$paginator->setItemCountPerPage(20);
$paginator->setCurrentPageNumber($current_page);
}
}

Sorry for the crappy code formatting. I hate Drupal. Will have to setup a proper blogging software up soon ;)

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.