Iterator.php #1

  • //
  • guest/
  • thomas_gray/
  • jambox/
  • main/
  • swarm/
  • library/
  • P4/
  • Model/
  • Connected/
  • Iterator.php
  • View
  • Commits
  • Open Download .zip Download (11 KB)
<?php
/**
 * Provide 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\Iterator class then $iterator->next()
 * and next($iterator) are equivalent and same for all other pairs.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */

namespace P4\Model\Connected;

class Iterator extends \ArrayIterator
{
    const FILTER_INVERSE            = 'INVERSE';
    const FILTER_COPY               = 'COPY';

    /**
     * Define the type of models we want to accept in this iterator.
     */
    protected $allowedModelClass    = 'P4\Model\Connected\ConnectedInterface';

    /**
     * Store custom iterator properties
     */
    protected $properties           = array();

    /**
     * Create a new model iterator.
     * If an array of models is given, populate from the array.
     *
     * @param   array   $models  optional - the set of models to contain.
     */
    public function __construct($models = null)
    {
        if (isset($models) && is_array($models)) {
            foreach ($models as $model) {
                if (!$model instanceof $this->allowedModelClass) {
                    throw new \InvalidArgumentException("Models array contains one or more invalid elements.");
                }
            }
            parent::__construct($models);
        } else {
            parent::__construct(array());
        }
    }

    /**
     * Set the model under the given key
     *
     * @param string|integer    $key    the key to store the model under.
     * @param Model             $model  the model to store.
     * @return void
     */
    public function offsetSet($key, $model)
    {
        if (!$model instanceof $this->allowedModelClass) {
            throw new \InvalidArgumentException("Invalid model supplied.");
        }
        return parent::offsetSet($key, $model);
    }

    /**
     * Extend offsetExists to handle null case without warnings.
     * This provides parity with isset() against standard arrays.
     *
     * @param   string|integer  $key    the key to check for.
     * @return  bool            true if the offset exists, false otherwise.
     */
    public function offsetExists($key)
    {
        if ($key === null) {
            return false;
        }

        return parent::offsetExists($key);
    }

    /**
     * Seek to an absolute position.
     *
     * @param   integer     $position   the numeric position to seek to.
     */
    public function seek($position)
    {
        if (!is_integer($position)) {
            throw new \OutOfBoundsException('Invalid seek position.');
        }
        $this->rewind();
        $current = 0;
        while ($current < $position && $this->valid()) {
            $this->next();
            $current++;
        }
        if (!$this->valid()) {
            throw new \OutOfBoundsException('Invalid seek position.');
        }
    }

    /**
     *  Overwrite next() method to return current value or false.
     *
     *  If php built-in next() function is not called then array
     *  pointer is not advanced and other php array-walk functions
     *  like current() or key() won't work.
     *
     *  @return false | mixed
     */
    public function next()
    {
        parent::next();
        next($this);
        return $this->valid() ? $this->current() : false;
    }

    /**
     *  Overwrite rewind() method to reset array pointer.
     *
     *  If php built-in reset() function is not called then array
     *  pointer is not advanced and other php array-walk functions
     *  like current() or key() won't work.
     */
    public function rewind()
    {
        parent::rewind();
        reset($this);
    }

    /**
     * Return the key of the array element that's currently being
     * pointed to by the internal pointer.
     * It does not move the pointer in any way.
     *
     * @return string|integer|null
     */
    public function key()
    {
        return key($this);
    }

    /**
     * Get all of the keys for all of the entries in this iterator.
     *
     * @return  array   a list of the keys in the iterator.
     */
    public function keys()
    {
        return array_keys($this->getArrayCopy());
    }

    /**
     * Return the value of the array element that's currently being
     * pointed to by the internal pointer.
     * It does not move the pointer in any way.
     *
     * @return Model
     */
    public function current()
    {
        return current($this);
    }

    /**
     * Get the value of the first element.
     *
     * @return ModelInterface
     */
    public function first()
    {
        $this->rewind();
        return $this->current();
    }

    /**
     * Get the value of the last element.
     *
     * @return  ModelInterface
     */
    public function last()
    {
        end($this);
        return $this->current();
    }

    /**
     * Get the value of the Nth element.
     *
     * @param   integer     $position   the numeric position to seek to.
     * @return  ModelInterface
     */
    public function nth($position)
    {
        $this->seek($position);
        return $this->current();
    }

    /**
     * Will run the specified function on each entry in the iterator, optionally
     * passing arguments.
     *
     * An array of function return values will be returned.
     *
     * @param   string  $functionName   The name of the function to execute
     * @param   array   $params         Optional array of paramaters to pass the function
     * @returns array                   Array of return values
     * @throws  \InvalidArgumentException   If any entry lacks the specified function
     */
    public function invoke($functionName, $params = array())
    {
        $results = array();

        foreach ($this as $entry) {
            if (!is_object($entry) || !method_exists($entry, $functionName)) {
                throw new \InvalidArgumentException(
                    'One or more entries lack the specified function'
                );
            }

            $results[] = call_user_func_array(array($entry, $functionName), $params);
        }

        return $results;
    }

    /**
     * Filter items of this instance by callback function.
     *
     * Callback must be callable function with at least one
     * parameter represents the allowed model class instance.
     *
     * Additional parameters can be set in params, in this
     * case callback will be called with model instance
     * parameter followed by params.
     *
     * Item (model) is acceptable if and only if callback
     * function with item passed as first parameter returns
     * true.
     *
     * Valid filter options are:
     *
     *   FILTER_INVERSE - inverse filtering behavior - acceptable items are removed
     *      FILTER_COPY - return a filtered copy without modifying original
     *
     * @param  callback     $callback   callback function to determine if item is acceptable
     * @param  mixed        $params     optional additional callback parameters
     * @param  string|array $options    optional - one or more filtering options
     * @return Iterator     provides fluent interface
     */
    public function filterByCallback($callback, $params = null, $options = array())
    {
        if (!is_callable($callback)) {
            throw new \Exception('Callback for P4\Iterator must be callable function.');
        }

        $copy = new static;

        // remove items where callback returns false
        foreach ($this->getArrayCopy() as $key => $model) {

            $passesFilter = call_user_func_array($callback, array($model, $params));

            // inverse behavior if FILTER_INVERSE option is set
            if (in_array(self::FILTER_INVERSE, $options)) {
                $passesFilter = !$passesFilter;
            }

            if (!$passesFilter && !in_array(static::FILTER_COPY, $options, true)) {
                $this->offsetUnset($key);
            } elseif ($passesFilter && in_array(static::FILTER_COPY, $options, true)) {
                $copy[$key] = $model;
            }
        }

        return in_array(static::FILTER_COPY, $options, true) ? $copy : $this;
    }

    /**
     * Merges the passed iterator's values into this iterator.
     *
     * If the input iterator has the same string keys, then the later value for
     * that key will overwrite the previous one. If, however, the key are numeric,
     * the later value will not overwrite the original value, but will be appended.
     *
     * @param   Iterator        $iterator   The new values to merge in
     * @return  Model\Iterator              provides fluent interface
     */
    public function merge(Iterator $iterator)
    {
        foreach ($iterator as $key => $value) {
            if (is_int($key)) {
                $this[] = $value;
            } else {
                $this[$key] = $value;
            }
        }

        return $this;
    }

    /**
     * Check if iterator has a particular property.
     *
     * @param   string  $name   the property name to check for the existence of
     * @return  boolean         true if the iterator has the named property, false otherwise.
     */
    public function hasProperty($name)
    {
        return array_key_exists($name, $this->properties);
    }

    /**
     * Get a particular property value of this iterator.
     *
     * @param   string  $name               name of the property to get the value of
     * @return  mixed                       the value of the property name
     * @throws  \InvalidArgumentException   if the property name does not exist
     */
    public function getProperty($name)
    {
        // return property value if it was set, otherwise throw an exception
        if ($this->hasProperty($name)) {
            return $this->properties[$name];
        }

        throw new \InvalidArgumentException(
            "Cannot find iterator property '$name'. Property was not set."
        );
    }

    /**
     * Get all properties of this iterator.
     *
     * @return  array   all properties set to this iterator
     */
    public function getProperties()
    {
        return $this->properties;
    }

    /**
     * Set a particular property of this iterator.
     *
     * @param   string          $name   name of the property to set the value of
     * @param   mixed           $value  value to set
     * @return  Iterator        provides a fluent interface
     */
    public function setProperty($name, $value)
    {
        $this->properties[$name] = $value;

        return $this;
    }

    /**
     * Set iterator properties.
     *
     * @param   array   $properties     array with properties to set
     * @return  Iterator                provides a fluent interface
     */
    public function setProperties(array $properties)
    {
        $this->properties = $properties;

        return $this;
    }
}
# Change User Description Committed
#1 18334 Liz Lam initial add of jambox