Created
May 25, 2012 17:27
-
-
Save kensnyder/2789336 to your computer and use it in GitHub Desktop.
CakePHP Behavior to add Find Plugins to your models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Add custom find types to your model (CakePHP 1.3) | |
*/ | |
class BetterFindBehavior extends ModelBehavior { | |
/** | |
* Trick to register methods in this class as custom find types | |
* | |
* @var array | |
*/ | |
public $mapMethods = array( | |
'/\b_findAllIndexed\b/' => '_findAllIndexed', | |
'/\b_findAllGrouped\b/' => '_findAllGrouped', | |
'/\b_findAggregateBy\b/' => '_findAggregateBy', | |
'/\b_findCountBy\b/' => '_findCountBy', | |
'/\b_findValue\b/' => '_findValue', | |
); | |
/** | |
* Register find methods on Model | |
* | |
* @param Model $Model | |
* @param array $settings | |
*/ | |
public function setup($Model, $settings = array()) { | |
$Model->_findMethods['allIndexed'] = true; | |
$Model->_findMethods['allGrouped'] = true; | |
$Model->_findMethods['aggregateBy'] = true; | |
$Model->_findMethods['countBy'] = true; | |
$Model->_findMethods['value'] = true; | |
} | |
/** | |
* Index results by the given field | |
* | |
* @example | |
* $Model->find('allIndexed', array('index_field'=>'Post.id', ...); | |
* Results will be indexed like this: | |
* array( | |
* [1001] => array( | |
* [Post] => array( | |
* [id] => 1001, | |
* [title] => 'CakePHP Behaviors', | |
* ... | |
* ) | |
* ), | |
* [1002] => array( | |
* [Post] => array( | |
* [id] => 1002, | |
* [title] => 'CakePHP find plugins', | |
* ... | |
* ) | |
* ) | |
* ) | |
* index_field defaults to "$Model->name.id" | |
* Note that if the index_field is not unique, some values will get overwritten | |
* | |
* @param Model $Model | |
* @param string $method The find method name (which should always be 'allIndexed') | |
* @param string $state 'before' for before a query, 'after' for after results | |
* @param array $query The find query | |
* @param array $results The query results | |
* @return array The altered results | |
*/ | |
public function _findAllIndexed($Model, $method, $state, $query = array(), $results = array()) { | |
if ($state == 'before') { | |
return $query; | |
} | |
elseif ($state == 'after') { | |
$indexField = isset($query['index_field']) ? $query['index_field'] : "$Model->name.id"; | |
if (strpos($indexField, '.')) { | |
list ($Table, $indexField) = explode('.', $indexField); | |
} | |
else { | |
$Table = $Model->name; | |
} | |
$indexed = array(); | |
foreach ($results as $r) { | |
$indexed[ @$r[ $Table ][ $indexField ] ] = $r; | |
} | |
return $indexed; | |
} | |
} | |
/** | |
* Group results by the given field | |
* | |
* @example | |
* $Model->find('allGrouped', array('group_field'=>'Post.type', ...); | |
* Results will be indexed like this: | |
* array( | |
* [article] => array( | |
* [0] => array( | |
* [Post] => array( | |
* [id] => 1001, | |
* [title] => 'CakePHP Behaviors', | |
* [type] => 'article' | |
* ... | |
* ) | |
* ), | |
* [1] => array( | |
* [Post] => array( | |
* [id] => 1002, | |
* [title] => 'CakePHP find plugins', | |
* [type] => 'article' | |
* ... | |
* ) | |
* ) | |
* ), | |
* [screencast] => array( | |
* [0] => array( | |
* [Post] => array( | |
* [id] => 1003, | |
* [title] => 'Getting started with CakePHP', | |
* [type] => 'screencast' | |
* ... | |
* ) | |
* ) | |
* ), | |
* ) | |
* group_field defaults to $query['fields'][0] (which may not work) | |
* | |
* @param Model $Model | |
* @param string $method The find method name (which should always be 'allGrouped') | |
* @param string $state 'before' for before a query, 'after' for after results | |
* @param array $query The find query | |
* @param array $results The query results | |
* @return array The altered results | |
*/ | |
public function _findAllGrouped($Model, $method, $state, $query = array(), $results = array()) { | |
if ($state == 'before') { | |
return $query; | |
} | |
elseif ($state == 'after') { | |
// get our group by table and field | |
$groupField = isset($query['group_field']) ? $query['group_field'] : $query['fields'][0]; | |
if (strpos($groupField, '.')) { | |
list ($GroupTable, $groupField) = explode('.', $groupField); | |
} | |
else { | |
$GroupTable = $Model->name; | |
} | |
$grouped = array(); | |
foreach ($results as $r) { | |
$groupValue = @$r[ $GroupTable ][ $groupField ]; | |
if (!isset($grouped[$groupValue])) { | |
$grouped[$groupValue] = array(); | |
} | |
$grouped[$groupValue][] = $r; | |
} | |
return $grouped; | |
} | |
} | |
/** | |
* Get a list of counts or other aggregate data | |
* | |
* @example | |
* $Model->find('aggregateBy', array('by'=>'Post.type', 'aggregate_function'=>'COUNT', 'aggregate_column'=>'*'...); | |
* Results will be indexed like this: | |
* array( | |
* [article] => 2, | |
* [screencast] => 1 | |
* ) | |
* by defaults to $query['fields'][0] (which should usually work) | |
* aggregate_function defaults to COUNT but could be MAX, AVG, etc. | |
* aggregate_column defaults to * but could be any column such as Post.created | |
* | |
* @param Model $Model | |
* @param string $method The find method name (which should always be 'aggregateBy') | |
* @param string $state 'before' for before a query, 'after' for after results | |
* @param array $query The find query | |
* @param array $results The query results | |
* @return array The altered results | |
*/ | |
public function _findAggregateBy($Model, $method, $state, $query = array(), $results = array()) { | |
if ($state == 'before') { | |
if (isset($query['aggregate'])) { | |
$aggregate = $query['aggregate']; | |
} | |
else { | |
$aggregator = isset($query['aggregate_function']) ? $query['aggregate_function'] : 'COUNT'; | |
$col = isset($query['aggregate_column']) ? $query['aggregate_column'] : '*'; | |
if (is_array($col)) { | |
$col = join(", ", $col); | |
} | |
$aggregate = "$aggregator($col)"; | |
} | |
if (!isset($query['by'])) { | |
$query['by'] = $query['fields'][0]; | |
} | |
$query['fields'] = array($query['by'], "$aggregate as _findAggregateBy"); | |
$query['group'] = $query['by']; | |
return $query; | |
} | |
elseif ($state == 'after') { | |
$by = isset($query['by']) ? $query['by'] : "$Model->name.id"; | |
if (strpos($by, '.')) { | |
list ($Table, $field) = explode('.', $by); | |
} | |
else { | |
$Table = $Model->name; | |
$field = $by; | |
} | |
return Set::combine($results, "/$Table/$field", '/0/_findAggregateBy'); | |
} | |
} | |
/** | |
* Get a list of counts (internally uses _findAggregateBy() above | |
* | |
* @example | |
* $Model->find('countBy', array('by'=>'Post.type', ...); | |
* Results will be indexed like this: | |
* array( | |
* [article] => 2, | |
* [screencast] => 1 | |
* ) | |
* by defaults to $query['fields'][0] (which should usually work) | |
* | |
* @param Model $Model | |
* @param string $method The find method name (which should always be 'countBy') | |
* @param string $state 'before' for before a query, 'after' for after results | |
* @param array $query The find query | |
* @param array $results The query results | |
* @return array The altered results | |
*/ | |
public function _findCountBy($Model, $method, $state, $query = array(), $results = array()) { | |
$query['aggregate_function'] = 'COUNT'; | |
$query['aggregate_column'] = '*'; | |
return $this->_findAggregateBy($Model, $method, $state, $query, $results); | |
} | |
/** | |
* Get the value in the first column of the first result | |
* | |
* @example | |
* $Model->find('value', array('fields'=>array('type'), 'conditions'=>array('id'=>1001))); | |
* Results will be a single value such as "article" | |
* When no result is found, will return false | |
* | |
* @param Model $Model | |
* @param string $method The find method name (which should always be 'value') | |
* @param string $state 'before' for before a query, 'after' for after results | |
* @param array $query The find query | |
* @param array $results The query results | |
* @return array The single value or false if nothing is found | |
*/ | |
public function _findValue($Model, $method, $state, $query = array(), $results = array()) { | |
if ($state == 'before') { | |
$field = isset($query['fields']) && !empty($query['fields'][0]) ? $query['fields'][0] : "$Model->name.id"; | |
$query['limit'] = 1; | |
$query['fields'] = array($field); | |
return $query; | |
} | |
elseif ($state == 'after') { | |
if (empty($results[0]) || !is_array($results[0])) { | |
return false; | |
} | |
if (isset($results[0][$Model->name]) && is_array($results[0][$Model->name])) { | |
// first field under $Model->name | |
$value = reset($results[0][$Model->name]); | |
} | |
elseif (isset($results[0][0]) && is_array($results[0][0])) { | |
// special field like `MAX(field) AS max_field` | |
$value = reset($results[0][0]); | |
} | |
else { | |
// something wonky | |
$value = reset($results[0]); | |
} | |
return $value; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment