Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kensnyder/2789336 to your computer and use it in GitHub Desktop.
Save kensnyder/2789336 to your computer and use it in GitHub Desktop.
CakePHP Behavior to add Find Plugins to your models
<?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