Perforce Chronicle 2012.2/486814
API Documentation
|
Provides a common container for a set of models. More...
Public Member Functions | |
filter ($fields, $values, $options=array()) | |
Filter items of this instance. | |
search ($fields, $query, array $options=null) | |
Search (filters) this iterator instance by user-provided query. | |
sortBy ($fields, $options=array()) | |
Reorder models by the given field(s). | |
sortByCallback ($callback) | |
Reorder models using a callback function for the comparison. | |
toArray ($shallow=false) | |
Get the iterator data as an array. | |
Static Public Member Functions | |
static | implodeValue ($value) |
Implodes arrays into comma-separated strings, returns non-arrays unmodified. | |
Public Attributes | |
const | FILTER_CONTAINS = 'CONTAINS' |
const | FILTER_IMPLODE = 'IMPLODE' |
const | FILTER_MATCH_ALL = 'MATCH_ALL' |
const | FILTER_NO_CASE = 'NO_CASE' |
const | FILTER_REGEX = 'REGEX' |
const | FILTER_STARTS_WITH = 'STARTS_WITH' |
const | SORT_ALPHA = 'ALPHA' |
const | SORT_ASCENDING = 'ASC' |
const | SORT_DESCENDING = 'DESC' |
const | SORT_FIXED = 'FIXED' |
const | SORT_NATURAL = 'NATURAL' |
const | SORT_NO_CASE = 'NO_CASE' |
const | SORT_NUMERIC = 'NUMERIC' |
Protected Member Functions | |
_getSortComparator ($options) | |
Return the appropriate comparison function to use for the given sort options. | |
_getValidSortOptions () | |
Get a list of the available sorting options. | |
_normalizeSortOptions (array $options) | |
Normalize sort options to ensure consistent structure and to catch invalid/malformed options. | |
_passesFilter ($model, $fields, $values, $options) | |
Check if model passes the given filter criteria. | |
_valueMatches ($value, $filter, $options) | |
Evaluate if value matches given filter with respect to options. | |
Protected Attributes | |
$_allowedModelClass = 'P4_ModelInterface' | |
Define the type of models we want to accept in this iterator. |
Provides a common container for a set of models.
Advantage of extending ArrayIterator is that php built-in array-walk functions reset(), next(), key(), current() can be replaced by class-implemented counterparts and vice versa. In other words, if $iterator is an instance of P4_Model_Iterator class then $iterator->next() and next($iterator) are equivalent and same for all other pairs.
P4_Model_Iterator::_getSortComparator | ( | $ | options | ) | [protected] |
Return the appropriate comparison function to use for the given sort options.
array | $options | sort options |
{ // ensure options are in an expected format. $options = $this->_normalizeSortOptions($options); // select the comparison function to use based on flags given. if ($options[static::SORT_FIXED]) { $order = array_flip((array) $options[static::SORT_FIXED]); $comparator = function ($a, $b) use ($order) { $c = isset($order[$a]) ? $order[$a] : PHP_INT_MAX; $d = isset($order[$b]) ? $order[$b] : PHP_INT_MAX; // fall back to default comparison if not all values specified if ($c === PHP_INT_MAX and $d === PHP_INT_MAX) { return strcmp($a, $b); } return $c - $d; }; } else if ($options[static::SORT_NUMERIC]) { $comparator = function ($a, $b) { // for float numbers comparison. // round() function does not work here since round(-0.01) = -0, // but array.sort() expects -1. $c = $a - $b; if ($c < 0) { return -1; } else if ($c > 0) { return 1; } return 0; }; } else if ($options[static::SORT_NATURAL] && $options[static::SORT_NO_CASE]) { $comparator = 'strnatcasecmp'; } else if ($options[static::SORT_NATURAL]) { $comparator = 'strnatcmp'; } else if ($options[static::SORT_NO_CASE]) { $comparator = 'strcasecmp'; } else { $comparator = 'strcmp'; } // optionally reverse the sort order by // inverting result of comparison function. if ($options[static::SORT_DESCENDING]) { return function($a, $b) use ($comparator) { return call_user_func($comparator, $b, $a); }; } return $comparator; }
P4_Model_Iterator::_getValidSortOptions | ( | ) | [protected] |
Get a list of the available sorting options.
{
return array(
static::SORT_ALPHA,
static::SORT_ASCENDING,
static::SORT_DESCENDING,
static::SORT_FIXED,
static::SORT_NATURAL,
static::SORT_NO_CASE,
static::SORT_NUMERIC
);
}
P4_Model_Iterator::_normalizeSortOptions | ( | array $ | options | ) | [protected] |
Normalize sort options to ensure consistent structure and to catch invalid/malformed options.
array | $options | sort options |
InvalidArgumentException | if invalid/malformed options are found. |
{ // ensure options are specified as option => value // instead of having the option name as the value // (value can be true/false or an array in the case // of sort fixed). $validSortOptions = $this->_getValidSortOptions(); $normalizedOptions = array_fill_keys($validSortOptions, false); foreach ($options as $key => $value) { // check if the key is a valid sort option. // if not, the value must be the sort option // otherwise, it's invalid. if (in_array($key, $validSortOptions, true)) { $normalizedOptions[$key] = $value; } else if (in_array($value, $validSortOptions, true)) { $normalizedOptions[$value] = true; } else { throw new InvalidArgumentException( "Unexpected sort option(s) encountered." ); } } return $normalizedOptions; }
P4_Model_Iterator::_passesFilter | ( | $ | model, |
$ | fields, | ||
$ | values, | ||
$ | options | ||
) | [protected] |
Check if model passes the given filter criteria.
P4Cms_Model | $model | the model to test against filter. |
string | array | $fields | one or more fields to check for acceptable values. |
string | array | $values | one or more acceptable values/patterns |
string | array | $options | optional - one or more filtering options |
{ $fields = is_array($fields) ? array_intersect($fields, $model->getFields()) : $model->getFields(); $matches = array(); $matchAll = in_array(static::FILTER_MATCH_ALL, $options, true); foreach ($fields as $field) { $value = $model->getValue($field); foreach ($values as $filter) { if ($this->_valueMatches($value, $filter, $options)) { $matches[$filter] = true; // exit if we have satisfied match. if (!$matchAll || count($matches) == count($values)) { return true; } } } } return false; }
P4_Model_Iterator::_valueMatches | ( | $ | value, |
$ | filter, | ||
$ | options | ||
) | [protected] |
Evaluate if value matches given filter with respect to options.
string | $value | the value to test against filter/pattern |
string | $filter | the filter/pattern to match against |
array | $options | filter options |
{ // array comparisons require FILTER_IMPLODE so we can convert to a string if (is_array($value) && in_array(static::FILTER_IMPLODE, $options, true)) { $value = static::implodeValue($value); } // evaluate matching against null if (is_null($filter)) { return is_null($value); } // evaluate only string, numeric, and boolean values if (!is_string($value) && !is_numeric($value) && !is_bool($value)) { return false; } $noCase = in_array(static::FILTER_NO_CASE, $options, true); // perform 'contains' comparison. if (in_array(static::FILTER_CONTAINS, $options, true)) { return false !== ($noCase ? stripos($value, $filter) : strpos($value, $filter)); } // perform 'starts with' comparison. if (in_array(static::FILTER_STARTS_WITH, $options, true)) { return 0 === ($noCase ? stripos($value, $filter) : strpos($value, $filter)); } // perform 'regex' comparison. if (in_array(static::FILTER_REGEX, $options, true)) { // make pattern case insensitive if no-case set. if ($noCase) { $filter .= 'i'; } return preg_match($filter, $value); } // default literal/exact comparison. return 0 === ($noCase ? strcasecmp($value, $filter) : strcmp($value, $filter)); }
P4_Model_Iterator::filter | ( | $ | fields, |
$ | values, | ||
$ | options = array() |
||
) |
Filter items of this instance.
You may specify one or more fields to check for one or more acceptable values. Models that do not have acceptable values will be removed from the iterator.
Valid filter options are:
FILTER_NO_CASE - perform case insensitive comparisons FILTER_CONTAINS - fields only need to contain a value to match FILTER_STARTS_WITH - fields only need to start with a value to match FILTER_REGEX - value is a regular expression FILTER_INVERSE - inverse filtering behavior - items that match are removed FILTER_MATCH_ALL - require all values to match at least once per model FILTER_COPY - return a filtered copy without modifying original FILTER_IMPLODE - fields that contain arrays will be flattened prior to matching
string | array | $fields | one or more fields to check for acceptable values. |
string | array | $values | one or more acceptable values/patterns |
string | array | $options | optional - one or more filtering options |
{ // normalize arguments to arrays. $fields = is_null($fields) ? $fields : (array) $fields; $values = (array) $values; $options = (array) $options; $copy = new static; // remove items that don't pass the filter. foreach ($this->getArrayCopy() as $key => $model) { $passesFilter = $this->_passesFilter($model, $fields, $values, $options); // inverse behavior if FILTER_INVERSE option is set if (in_array(static::FILTER_INVERSE, $options, true)) { $passesFilter = !$passesFilter; } if (!$passesFilter && !in_array(static::FILTER_COPY, $options, true)) { $this->offsetUnset($key); } else if ($passesFilter && in_array(static::FILTER_COPY, $options, true)) { $copy[$key] = $model; } } return in_array(static::FILTER_COPY, $options, true) ? $copy : $this; }
static P4_Model_Iterator::implodeValue | ( | $ | value | ) | [static] |
Implodes arrays into comma-separated strings, returns non-arrays unmodified.
mixed | $value | A value to be imploded; only arrays will be modified. |
{ return is_array($value) ? implode(', ', $value) : $value; }
P4_Model_Iterator::search | ( | $ | fields, |
$ | query, | ||
array $ | options = null |
||
) |
Search (filters) this iterator instance by user-provided query.
Splits the given query string on whitespace and comma, then filters the iterator with the following options:
FILTER_NO_CASE - perform case insensitive comparisons FILTER_CONTAINS - fields only need to contain a value to match FILTER_IMPLODE - fields that contain arrays will be flattened prior to matching FILTER_MATCH_ALL - require all values to match at least once per model
The options can be overridden via the optional $options param.
array | string | $fields | the fields to match on |
string | $query | the user-supplied search string |
array | $options | optional - flags to pass to the filter. |
{ // normalize fields to array. $fields = (array) $fields; // split query into words. $query = preg_split('/[\s,]+/', trim($query)); // use default options if none provided. $options = $options !== null ? $options : array( P4_Model_Iterator::FILTER_CONTAINS, P4_Model_Iterator::FILTER_NO_CASE, P4_Model_Iterator::FILTER_IMPLODE, P4_Model_Iterator::FILTER_MATCH_ALL ); // remove models that don't match search query. return $this->filter($fields, $query, $options); }
P4_Model_Iterator::sortBy | ( | $ | fields, |
$ | options = array() |
||
) |
Reorder models by the given field(s).
Multiple fields can be specified to produce a nested sort. Comparison behavior defaults to alphabetical, ascending order. Use the options argument to produce a different order.
When sorting on multiple fields, separate options can be given for each field by setting the entry key to the field name and the value to the array of sort options to use for that field.
Alternatively, each entry in fields may be an array with two parts where the first part is the field name and the second is the array of sort options to use for that field (can be used to sort on the same field twice with different options).
Valid sorting options are:
SORT_ASCENDING - default direction SORT_DESCENDING - reverse direction SORT_ALPHA - default alphabetic order comparison SORT_NUMERIC - perform numeric comparison SORT_NATURAL - perform natural order comparison SORT_NO_CASE - perform case-insensitive comparison SORT_FIXED - put entries in a prescribed order e.g. SORT_FIXED => array(val, val, ...)
array | string | $fields | one or more fields to order by if multiple fields are specified, performs a nested sort. |
array | $options | optional - one or more sorting options |
{ if (!is_array($options)) { throw new InvalidArgumentException( "Cannot sort. Sort options must be an array." ); } // normalize fields to an array. $fields = (array) $fields; // determine comparison function to use for each field. $comparators = array(); foreach ($fields as $key => $value) { // three ways to specify fields + options: // - common case: key is an integer and value is a string, // takes value as field name and second func. param as options. // - if key is a string and value is an array, // takes key as field name and value as options. // - if key is an integer and value is an array with two parts, // takes first part as field name and second part as options. if (is_integer($key) && is_string($value)) { $comparators[] = array($value, $this->_getSortComparator($options)); } else if (is_string($key) && is_array($value)) { $comparators[] = array($key, $this->_getSortComparator($value)); } else if (is_integer($key) && is_array($value) && count($value) == 2) { $comparators[] = array($value[0], $this->_getSortComparator($value[1])); } else { throw new InvalidArgumentException("Cannot sort. Invalid sort field(s) given."); } } // perform sort. // uses '@' to silence warnings about array being modified by // comparison function - can occur due to lazy loading. @$this->uasort( function ($a, $b) use ($comparators) { foreach ($comparators as $comparator) { $result = call_user_func( $comparator[1], P4_Model_Iterator::implodeValue($a->getValue($comparator[0])), P4_Model_Iterator::implodeValue($b->getValue($comparator[0])) ); // if values are equal, compare the next field. if (!$result) { continue; } return $result; } return 0; } ); return $this; }
P4_Model_Iterator::sortByCallback | ( | $ | callback | ) |
Reorder models using a callback function for the comparison.
Effectively just a wrapper for the uasort() method.
callable | $callback | the function to pass to uasort(). |
{ if (!is_callable($callback)) { throw new InvalidArgumentException( "Cannot sort iterator. Given callback is not callable." ); } // perform sort. // uses '@' to silence warnings about array being modified by // comparison function - can occur due to lazy loading. @$this->uasort($callback); return $this; }
P4_Model_Iterator::toArray | ( | $ | shallow = false | ) |
Get the iterator data as an array.
Calls toArray() on all of the models unless 'shallow' is true.
bool | $shallow | optional - set shallow to true to avoid calling toArray() on each of the models - defaults to false. |
{ if ($shallow) { return $this->getArrayCopy(); } $data = array(); foreach ($this->getArrayCopy() as $key => $model) { $data[$key] = $model->toArray(); } return $data; }
P4_Model_Iterator::$_allowedModelClass = 'P4_ModelInterface' [protected] |
Define the type of models we want to accept in this iterator.
Reimplemented from P4_Iterator.
Reimplemented in P4Cms_Model_Iterator.
const P4_Model_Iterator::FILTER_CONTAINS = 'CONTAINS' |
const P4_Model_Iterator::FILTER_IMPLODE = 'IMPLODE' |
const P4_Model_Iterator::FILTER_MATCH_ALL = 'MATCH_ALL' |
const P4_Model_Iterator::FILTER_NO_CASE = 'NO_CASE' |
const P4_Model_Iterator::FILTER_REGEX = 'REGEX' |
const P4_Model_Iterator::FILTER_STARTS_WITH = 'STARTS_WITH' |
const P4_Model_Iterator::SORT_ALPHA = 'ALPHA' |
const P4_Model_Iterator::SORT_ASCENDING = 'ASC' |
const P4_Model_Iterator::SORT_DESCENDING = 'DESC' |
const P4_Model_Iterator::SORT_FIXED = 'FIXED' |
const P4_Model_Iterator::SORT_NATURAL = 'NATURAL' |
const P4_Model_Iterator::SORT_NO_CASE = 'NO_CASE' |
const P4_Model_Iterator::SORT_NUMERIC = 'NUMERIC' |