Perforce Chronicle 2012.2/486814
API Documentation

P4_Model_Iterator Class Reference

Provides a common container for a set of models. More...

Inheritance diagram for P4_Model_Iterator:
P4_Iterator P4Cms_Model_Iterator

List of all members.

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.

Detailed Description

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.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

P4_Model_Iterator::_getSortComparator ( options) [protected]

Return the appropriate comparison function to use for the given sort options.

Parameters:
array$optionssort options
See also:
sortBy()
Returns:
mixed a callable comparison function.
    {
        // 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.

Returns:
array all valid sort 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.

Parameters:
array$optionssort options
See also:
sortBy()
Returns:
array the normalized options array.
Exceptions:
InvalidArgumentExceptionif 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.

Parameters:
P4Cms_Model$modelthe model to test against filter.
string | array$fieldsone or more fields to check for acceptable values.
string | array$valuesone or more acceptable values/patterns
string | array$optionsoptional - one or more filtering options
Returns:
bool true if the model passes filter; false otherwise.
    {
        $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.

Parameters:
string$valuethe value to test against filter/pattern
string$filterthe filter/pattern to match against
array$optionsfilter options
Returns:
bool true if the value matches the filter.
    {
        // 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

Parameters:
string | array$fieldsone or more fields to check for acceptable values.
string | array$valuesone or more acceptable values/patterns
string | array$optionsoptional - one or more filtering options
Returns:
P4_Model_Iterator provides fluent interface
    {
        // 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.

Parameters:
mixed$valueA value to be imploded; only arrays will be modified.
Returns:
mixed An imploded array, or unmodified value.
    {
        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.

Parameters:
array | string$fieldsthe fields to match on
string$querythe user-supplied search string
array$optionsoptional - flags to pass to the filter.
Returns:
P4_Model_Iterator provides fluent interface.
    {
        // 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, ...)

Parameters:
array | string$fieldsone or more fields to order by if multiple fields are specified, performs a nested sort.
array$optionsoptional - one or more sorting options
Returns:
P4_Model_Iterator provides fluent interface.
    {
        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.

Parameters:
callable$callbackthe function to pass to uasort().
Returns:
P4_Model_Iterator provides fluent interface.
    {
        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.

Parameters:
bool$shallowoptional - set shallow to true to avoid calling toArray() on each of the models - defaults to false.
Returns:
array all model data as an array.
    {
        if ($shallow) {
            return $this->getArrayCopy();
        }

        $data = array();
        foreach ($this->getArrayCopy() as $key => $model) {
            $data[$key] = $model->toArray();
        }
        return $data;
    }

Member Data Documentation

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.


The documentation for this class was generated from the following file: