Location: PHPKode > scripts > Complete Array Object > complete-array-object/CompleteArrayObject.php
<?php
 /**
 * TypedArrayObject is an extension of the ArrayObject class 
 * that adds type checking for each element added into the list.
 * 
 * @package CompleteArrayObject
 * @author Omar Ramos
 * @version 1.0
 * @copyright    Copyright (C) 2009 Omar Ramos. All rights reserved.
 * @license      GNU Lesser General Public License, see http://www.gnu.org/licenses/lgpl.html
 */
class TypedArrayObject extends ArrayObject
{
    protected $_type;
    protected $_isBasicType;
    
    /**
    * Constructor Function
    * This constructor function is a little special since you 
    * can instantiate the class in four ways:
    * 
    * 1. Provide no arguments to the constructor and you can
    * create a new, untyped instance of the class which will
    * behave essentially the same as the default ArrayObject.
    * 
    * 2. Give the constructor a string for the first 
    * argument and it will automatically use that value as the type.
    * Useful for creating a new typed instance.
    * 
    * 3. Give the constructor an array and it will
    * use that array to start the list.
    * 
    * 4. Give the constructor an array and a type and it will
    * instantiate the class with that list and use that type when
    * adding new items to the list.
    * 
    * @param array $array
    * @param string $type
    */
    public function __construct($array = array(), $type = null)
    {
        $basicTypes = array('string', 'array', 'resource',
                            'float', 'double', 'real', 
                            'int', 'integer', 'long', 
                            'bool', 'boolean');
        if (is_string($array)) {
            $this->_type = $array;
            $type = $array;
            $this->_isBasicType = in_array(strtolower($type), $basicTypes);
            $array = array();
        }
        elseif (is_array($array)) {
            if (!empty($type))
            {
                $this->_type = (string) $type;
                $this->_isBasicType = in_array(strtolower($type), $basicTypes);
                foreach($array as $item)
                {
                    $this->typeCheck($item);
                }
            }
            else
            {
                $this->_type = null;
                $this->_isBasicType = null;
            }
        }
        
        parent::__construct($array);    
    }
    
    /**
    * @desc Overriding ArrayObject::append to add typechecking
    * @param mixed $value - a value to append to the array
    * 
    * @return int $count
    */
    public function append($value)
    {
        $this->typeCheck($value);
        parent::append($value);
        return $this->count();
    }

    /**
    * @desc Overriding ArrayObject::offsetSet to add typechecking; this method is called when values are set using the []
    * @param mixed $index - the index of the array element to set
    * @param mixed $value - the value to set
    * 
    * @return void
    */
    public function offsetSet($index, $value)
    {
        $this->typeCheck($value);
        parent::offsetSet($index, $value);
    }
    
    /**
    * @desc Overriding ArrayObject::exchangeArray to add typechecking
    * @param array $array
    */
    public function exchangeArray($array)
    {
        foreach($array as $value)
        {
          $this->typeCheck($value);
        }
        parent::exchangeArray($array);
    }

    /**
    * @desc Type checking method
    * @param mixed $val
    */
    protected function typeCheck($val)
    {
        $type = $this->_type;
        
        if (is_null($type))
        {
            return true;
        }
        
        if (!$this->_isBasicType)
        {
            // If it isn't a basic type then it should be an object
            if (is_object($val))
            {
                if (!($val instanceof $type))
                {
                    $this->typeExceptionMessage($val);
                    return;
                }
            }
            else
            {
                $this->typeExceptionMessage($val);
                return;
            }
        }
        else
        {
            switch(strtolower($type))
            {
                case 'string':
                    if (!is_string($val))
                    {
                        $this->typeExceptionMessage($val);
                    }
                    break;
                case 'array':
                    if (!is_array($val))
                    {
                        $this->typeExceptionMessage($val);
                    }
                    break;
                case 'resource':
                    if (!is_resource($val))
                    {
                        $this->typeExceptionMessage($val);    
                    }
                    break;
                case 'float':
                case 'double':
                case 'real':
                    if (!is_float($val))
                    {
                        $this->typeExceptionMessage($val);
                    }
                    break;
                case 'int':
                case 'integer':
                case 'long':
                    if (!is_int($val))
                    {
                        $this->typeExceptionMessage($val);
                    }
                    break;
                case 'bool':
                case 'boolean':
                    if (!is_bool($val))
                    {
                        $this->typeExceptionMessage($val);
                    }
                    break;
            }
        }
        
        return true;
    }
    
    /**
    * @desc Throws new InvalidArgumentException
    * 
    * @throws InvalidArgumentException
    */
    protected function typeExceptionMessage($val)
    {
         if (is_object($val))
         {
            throw new InvalidArgumentException("Value: " . get_class($val) . " supplied to " . get_class($this) . " is not of type: " . $this->_type);
         }
         else 
         {
             throw new InvalidArgumentException("Value: " . $val . " supplied to " . get_class($this) . " is not of type: " . $this->_type);
         }
    }
    
    /**
    * @desc ToString Method
    * 
    * @return string
    */
    public function __toString()
    {
        return "<pre>" . print_r($this, true) . "</pre>";
    }
}


/**
 * CompleteArrayObject is an extension of the TypedArrayObject class that 
 * introduces some useful methods.
 * 
 * @package CompleteArrayObject
 * @author Omar Ramos
 * @version 1.0
 * @copyright    Copyright (C) 2009 Omar Ramos. All rights reserved.
 * @license      GNU Lesser General Public License, see http://www.gnu.org/licenses/lgpl.html
 */
class CompleteArrayObject extends TypedArrayObject
{
    /**
     * @var string
     */
    protected $_sortField;
    /**
     * @var string
     */
    protected $_sortDirection;
    
    /**
    * Constructor Function
    * This constructor function is a little special since you 
    * can instantiate the class in four ways:
    * 
    * 1. Provide no arguments to the constructor and you can
    * create a new, untyped instance of the class which will
    * basically be an ArrayObject with all of the extra methods
    * in this class available to it.
    * 
    * 2. Give the constructor a string for the first 
    * argument and it will automatically use that value as the type.
    * Useful for creating a new typed instance.
    * 
    * 3. Give the constructor an array and it will
    * use that array to start the list.
    * 
    * 4. Give the constructor an array and a type and it will
    * instantiate the class with that list and use that type when
    * adding new items to the list.
    * 
    * @param array $array
    * @param string $type
    */
    public function __construct($array = array(), $type = null)
    {
        if (is_string($array)) {
            $type = $array;
            $array = array();
        }
        elseif (is_array($array)) {
            if (!empty($type)) {
                $type = (string) $type;
            } else {
                $type = null;
            }
        }
        
        parent::__construct($array, $type);    
    }
    
    /**
    * @desc Pops the last element off the end
    * of the list and returns it, shortening
    * the list by one element.
    * 
    * @return mixed $popped
    */
    public function pop()
    {
        $array = $this->getArrayCopy();
        $popped = array_pop($array);
        $this->exchangeArray($array);
        return $popped;
    }
    
    /**
    * @desc Pushes a new element onto the end
    * of the list. lengthening the list by 
    * one element.
    * 
    * @return int $count
    */
    public function push($value)
    {
        $this->append($value);
    }
    
    /**
    * @desc Shifts the first element off the 
    * beginning of the list and returns it, 
    * shortening the list by one element.
    * 
    * @return mixed $popped
    */
    public function shift()
    {
        $this->reverse();
        $shifted = $this->pop();
        $this->reverse();
        return $shifted;
    }
    
    /**
    * @desc Unshifts a new element onto the
    * beginning of the list. lengthening the list by 
    * one element.
    * 
    * @param mixed $value
    * @return int $count
    */
    public function unshift($value)
    {
        $this->reverse();
        $count = $this->append($value);
        $this->reverse();
        return $count;
    }
    
    /**
    * @desc Extracts a slice of the internal
    * lists and returns it as a new CompleteArrayObject
    * 
    * @param int $offset
    * @param int $length
    * @param boolean $preserve_keys
    * @return CompleteArrayObject
    */
    public function slice($offset, $length = 0, $preserve_keys = false)
    {
        $array = $this->getArrayCopy();
        $slice = array_slice($array, (int) $offset, (int) $length, (bool) $preserve_keys);
        return new self($slice);
    }
    
    /**
    * @desc Removes a portion of the internal list
    * and replaces it with something else. To add multiple
    * values at the same time wrap your values in an array.
    * 
    * @param int $offset
    * @param int $length
    * @param mixed $replacement
    * @return CompleteArrayObject - containing the removed/replaced values
    */
    public function splice($offset, $length = 0, $replacement = null)
    {
        $array = $this->getArrayCopy();
        if (!empty($replacement)) {
            if (is_array($replacement)) {
                $spliced = array_splice($array, (int) $offset, (int) $length, $replacement);
            } else {
                $spliced = array_splice($array, (int) $offset, (int) $length, array($replacement));
            }
            
        } else {
            $spliced = array_splice($array, (int) $offset, (int) $length);
        }
        
        $this->exchangeArray($array);
        return new self($spliced);
    }
    
    /**
    * @desc Shuffles the internal list
    * 
    * @return boolean
    */
    public function shuffle()
    {
        $array = $this->getArrayCopy();
        $boolean = shuffle($array);
        $this->exchangeArray($array);
        return $boolean;
    }
    
    /**
    * @desc Sort an array in reverse order and 
    * maintain index association.
    * 
    * @return void
    */
    public function arsort()
    {
        $array = $this->getArrayCopy();
        arsort($array);
        $this->exchangeArray($array);
    }
    
    /**
    * @desc Sort an array by key in reverse order.
    * 
    * @return void
    */
    public function krsort()
    {
        $array = $this->getArrayCopy();
        krsort($array);
        $this->exchangeArray($array);
    }
    
    /**
    * Convenience method for clearing
    * the contents of the internal list.
    * 
    * @return void
    */
    public function clear()
    {
        $this->exchangeArray(array());
    }
    
    /**
    * @desc Convenience method for checking
    * whether the internal list is currently
    * empty or not.
    * 
    * @return boolean
    */
    public function cleared()
    {
        return $this->count() == 0;
    }
    
    /**
    * @desc Rearrange the list in reverse order
    * 
    * @return void
    */
    public function reverse()
    {
        $array = $this->getArrayCopy();
        $array = array_reverse($array, true);
        $this->exchangeArray($array);
    }
    
    /**
    * This method takes in a field name and a sort
    * direction and sorts array or object values
    * using uasort.
    * 
    * @param string $fieldName
    * @param string $sortDirection - Valid values are 'asc', 'desc', 'ascending', and 'descending'
    * 
    */
    public function sortByField($fieldName, $sortDirection = 'asc')
    {
        $this->_sortField = $fieldName;
        
        $validDirections = array('asc', 'desc', 'ascending', 'descending');
        if (in_array(strtolower($sortDirection), $validDirections)) {
            $this->_sortDirection = substr($sortDirection, 0, 4);
        }
        else {
            throw new InvalidArgumentException("Value: " . $sortDirection . " supplied to " . get_class($this) ."::sortByField is invalid. Expected: 'asc', 'desc', 'ascending', or 'descending'");
        }
        
        $array = $this->getArrayCopy();
        uasort($array, array(&$this, '_fieldCompare'));
        $this->exchangeArray($array);
    }
    
    /**
    * This is the method that does the field comparing
    * for objects and arrays
    */
    protected function _fieldCompare($a, $b)
    {
        $sortField = $this->_sortField;
        if (is_array($a) && is_array($b))
        {
            $al = $a[$sortField];
            $bl = $b[$sortField];
        }
        elseif (is_object($a) && is_object($b))
        {
            $avars = get_object_vars($a);
            $bvars = get_object_vars($b);
            $ain = array_key_exists($sortField, get_object_vars($a));
            $bin = array_key_exists($sortField, get_object_vars($b));
            if ($ain && $bin)
            {
                $al = $a->$sortField;
                $bl = $b->$sortField;
            }
            else
            {
                $get = 'get' . ucfirst($sortField);
                if (method_exists($a, $get) && method_exists($b, $get))
                {
                    $al = $a->$get();
                    $bl = $b->$get();
                }
                else
                {
                    throw new InvalidArgumentException("Method: " . $get . "() supplied does not exist in ". get_class($a) ." or " . get_class($b) . ".");
                }
            }
        }
        else
        {
            $al = $a;
            $al = $b;
        }
        
        if ($al == $bl) {
            return 0;
        }
        if ($this->_sortDirection == 'asc') {
            return ($al > $bl) ? -1 : +1;
        }
        
        if ($this->_sortDirection == 'desc') {
            return ($al > $bl) ? +1 : -1;
        }
        
    }
    
    /**
    * @desc Creates a filtered key/values list determined by
    * the field name given. Then this list is filtered for 
    * unique values and a new CompleteArrayObject instance
    * is created.
    * 
    * If field name is not provided, then the method will
    * assume that the value is scalar and should be
    * added to the list. 
    * 
    * @param string $fieldName
    * @return CompleteArrayObject
    */
    public function uniqueByField($fieldName = null)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $array = array_unique($fieldValuesArray);
        return new self($array);
    }
    
    /**
    * @desc Convenience method for uniqueByField
    * for CompleteArrayObjects that do not contain
    * array or object values.
    * 
    * @return CompleteArrayObject
    */
    public function unique()
    {
        return $this->uniqueByField(null);
    }
    
    /**
    * @desc Calculates the product of all the values in
    * a particular array or object field
    * 
    * @param string $fieldName
    * @return float $product
    */
    public function productByField($fieldName = null)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $product = array_product($fieldValuesArray);
        return $product;
    }
    
    /**
    * @desc Convenience method for productByField
    * for CompleteArrayObjects that do not contain
    * array or object values.
    * 
    * @return float $product
    */
    public function product()
    {
        return $this->productByField(null);
    }
    
    /**
    * @desc Calculates the sum of all the values in
    * a particular array or object field
    * 
    * @param string $fieldName
    * @return float $sum
    */
    public function sumByField($fieldName = null)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $sum = array_sum($fieldValuesArray);
        return $sum;
    }
    
    /**
    * @desc Convenience method for sumByField
    * for CompleteArrayObjects that do not contain
    * array or object values.
    * 
    * @return float $sum
    */
    public function sum()
    {
        return $this->sumByField(null);
    }
    
    /**
    * @desc Calculates the average of all the values in
    * a particular array or object field
    * 
    * @param string $fieldName
    * @param integer $precision
    * @return float $average
    */
    public function avgByField($fieldName = null, $precision = 2)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $count = count($fieldValuesArray);
        $sum = array_sum($fieldValuesArray);
        $average = round($sum / $count, (int) $precision);
        return $average;
    }
    
    /**
    * @desc Convenience method for avgByField
    * for CompleteArrayObjects that do not contain
    * array or object values.
    * 
    * @param integer $precision
    * @return float $average
    */
    public function avg($precision = 2)
    {
        return $this->avgByField(null, $precision);
    }
    
    /**
    * @desc Returns the maximum of all the values in
    * a particular array or object field
    * 
    * @param string $fieldName
    * @return float $max
    */
    public function maxByField($fieldName = null)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $max = max($fieldValuesArray);
        return $max;
    }
    
    /**
    * @desc Convenience method for maxByField
    * for CompleteArrayObjects that do not contain
    * array or object values.
    * 
    * @return float $max
    */
    public function max()
    {
        return $this->maxByField(null);
    }
    
    /**
    * @desc Returns the minimum of all the values in
    * a particular array or object field
    * 
    * @param string $fieldName
    * @return float $min
    */
    public function minByField($fieldName = null)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $min = min($fieldValuesArray);
        return $min;
    }
    
    /**
    * @desc Convenience method for minByField
    * for CompleteArrayObjects that do not contain
    * array or object values.
    * 
    * @return float $min
    */
    public function min()
    {
        return $this->minByField(null);
    }   
    
    /**
    * @desc Returns the count of all occurrences of a 
    * particular value in a particular array or 
    * object field
    * 
    * @param mixed $value
    * @param string $fieldName
    * @return integer $occurrences
    */
    public function occurrencesByField($value, $fieldName = null)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $occurrences = 0;
        foreach($fieldValuesArray as $fieldValue)
        {
            if ($value == $fieldValue)
            {
                $occurrences++;
            }
        }
        return $occurrences; 
    }
    
    /**
    * @desc Convenience method for CompleteArrayObjects
    * that do not contain array or object values
    * 
    * @param mixed $value
    * @return integer $occurrences
    */
    public function occurrences($value)
    {
        return $this->occurrencesByField($value, null);
    }
    
    /**
    * @desc Given a field name, this method
    * will return the value of the mode for that field wrapped in a
    * CompleteArrayObject (this is because multiple values can be 
    * valid modes in a list). If return_count is true, then only
    * the number of occurrences for the mode will be returned.
    * If return_count is null, then this method will return
    * the entire list of values grouped by occurrences and sorted
    * in reverse key order (most occurrences first).
    * 
    * The return_count flag switches between returning
    * the value of the mode, or the number of occurrences
    * of the mode.
    * 
    * @param string $fieldName
    * @param boolean $return_count
    * @return mixed 
    */
    public function modeByField($fieldName = null, $return_count = false)
    {
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $uniqueValues = array_unique($fieldValuesArray);
        $occurrencesList = array();
        foreach($uniqueValues as $value)
        {
            // This initializes each possible value
            // with a starting value of 0
            $occurrencesList[$value] = 0;
        }
        
        foreach($fieldValuesArray as $value)
        {
            $occurrencesList[$value]++;    
        }
        
        if (is_null($return_count))
        {
            $occurrencesList = $this->group($occurrencesList);
            $occurrencesList->krsort();
            return $occurrencesList;
        }
        
        $modeCount = max($occurrencesList);
        $modeValues = array_flip(array_keys($occurrencesList, $modeCount));
        $modeValuesByOccurrence = array();
        $modeValuesByOccurrence[$modeCount] = new self();
        foreach($modeValues as $key => $value)
        {
            $modeValuesByOccurrence[$modeCount][] = $key;
        }
        
        if ($return_count)
        {
            return $modeCount;
        }
        if ($modeValuesByOccurrence[$modeCount]->count() == 1)
        {
            return $modeValuesByOccurrence[$modeCount]->shift();
        }
        return new self($modeValuesByOccurrence);
    }
    
    /**
    * @desc Convenience method for modeByField method
    * above. Useful only for CompleteArrayObjects
    * where the values are not arrays or objects so there is
    * no need to provide a field name.
    * 
    * The return_count flag switches between returning
    * the value of the mode, or the number of occurrences
    * of the mode.
    * 
    * @param boolean $return_count
    * @return mixed
    */
    public function mode($return_count = false)
    {
        return $this->modeByField(null, $return_count);
    }
    
    /**
    * @desc Given a field name, this method
    * calculates the minimum and maximum values
    * for that field and then computes the difference
    * (range) and returns the result.
    * 
    * @return float
    */
    public function rangeByField($fieldName = null)
    {
        $max = $this->maxByField($fieldName);
        $min = $this->minByField($fieldName);
        $range = $max - $min;
        return $range;
    }
    
    /**
    * @desc Convenience method for rangeByField
    * above. Useful only for CompleteArrayObjects
    * where the values are all numeric so there is
    * no need to provide a field name.
    * 
    * @return float
    */
    public function range()
    {
        return $this->rangeByField(null);
    }
    
    /**
    * @desc Filters all of the values in the current list
    * according to the $value, $operator  and $fieldName provided.
    * 
    * Keys are preserved and if $exchange_array is set to true
    * then the filtered list will replace the current list within
    * the object.
    * 
    * The method attempts to compare $value numerically if a 
    * numeric value is given and by string if a string is given.
    * The string comparison is not case-sensitive.
    * 
    * @param string $pattern
    * @param string $operator
    * @param string $fieldName
    * @param boolean $exchange_array
    * @return CompleteArrayObject
    */
    public function filterByValue($value, $operator = '==', $fieldName = null, $exchange_array = false)
    {
        $array = $this->getArrayCopy();
        $validOperators = array('==', '===', '!=', '!==', '<>', '<', '>', '<=', '>=');
        $validOperator = in_array($operator, $validOperators);
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        
        if ($validOperator)
        {
            $matchingKeys = array();
            foreach($fieldValuesArray as $fieldKey => $fieldValue)
            {
                if (is_string($value))
                {
                    $compared = strcasecmp($value, $fieldValue);
                }
                switch($operator)
                {
                    case '==':
                        if (is_string($value))
                        {
                            if ($compared === 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue == $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '===':
                        if (is_string($value))
                        {
                            if ($compared === 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue === $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '!=':
                    case '<>':
                        if (is_string($value))
                        {
                            if ($compared != 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue != $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '!==':
                        if (is_string($value))
                        {
                            if ($compared !== 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue !== $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '<':
                        if (is_string($value))
                        {
                            if ($compared > 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue < $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '>':
                        if (is_string($value))
                        {
                            if ($compared < 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue > $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '<=':
                        if (is_string($value))
                        {
                            if ($compared > 0 || $compared === 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue <= $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                    case '>=':
                        if (is_string($value))
                        {
                            if ($compared < 0 || $compared === 0)
                            {
                                $matchingKeys[] = $fieldKey;
                            }
                        }
                        elseif($fieldValue >= $value)
                        {
                            $matchingKeys[] = $fieldKey;
                        }
                        break;
                        
                }        
            }
            
            $filteredArray = array();
            foreach($matchingKeys as $matchingKey)
            {
                $filteredArray[$matchingKey] = $array[$matchingKey];
            }
            
            if ($exchange_array)
            {
                $this->exchangeArray($filteredArray);
                return $this;
            }
            
            return new self($filteredArray, $this->getType());
        }
        throw new InvalidArgumentException("Operator Value: " . $operator . " supplied to " . get_class($this) ."::filterByValue is invalid. Expected: '==', '===', '!=', '!==', '<>', '<', '>', '<=', or '>='");
    }
    
    /**
    * @desc Filters all of the values in the current list
    * according to the $pattern  and $fieldName provided.
    * If $invert is true the returned object will contain
    * all of the values that do not match the pattern.
    * Keys are preserved and if $exchange_array is set to true
    * then the filtered list will replace the current list within
    * the object.
    * 
    * @param string $pattern
    * @param string $fieldName
    * @param boolean $invert
    * @param boolean $exchange_array
    * @return CompleteArrayObject
    */
    public function filterByRegex($pattern, $fieldName = null, $invert = false, $exchange_array = false)
    {
        $array = $this->getArrayCopy();
        $fieldValuesArray = $this->_fieldValuesArray($fieldName);
        $matchingKeys = preg_grep($pattern, $fieldValuesArray, $invert);
        
        $filteredArray = array();
        foreach($matchingKeys as $matchingKey => $matchingValue)
        {
            $filteredArray[$matchingKey] = $array[$matchingKey];
        }
        
        if ($exchange_array)
        {
            $this->exchangeArray($filteredArray);
            return $this;
        }
        
        return new self($filteredArray, $this->getType());
    }
    
    /**
    * @desc Groups all values in the list by the field name
    * Each field name contains a list of all items that had that
    * value.
    * 
    * @param string $fieldName
    * @param string $key
    * @return CompleteArrayObject
    */
    public function groupByField($fieldName = null, $key = null, $array = null)
    {
        if (is_null($array))
        {
            $array = $this->getArrayCopy();
        }
        if ($array instanceof ArrayObject)
        {
            $array = $array->getArrayCopy();
        }
        if (!is_array($array))
        {
            $array = (array) $array;
        }

        $children = array();
       
        // first pass - collect children
        foreach ($array as $itemkey => $item)
        {
            // Assigns the item's field name value to $pt
            if (is_object($item))
            {
                if (array_key_exists($fieldName, get_object_vars($item)))
                {
                    $pt = $item->$fieldName;
                }
                else
                {
                    $get = 'get' . ucfirst($fieldName);
                    $pt = $item->$get();
                }
                
            }
            if (is_array($item))
            {
                $pt = $item[$fieldName];
            }
            if (is_null($fieldName))
            {
                $pt = $item;
            }
            
            // Checks to see if the field name's value already exists
            // in $children array. If so, then $list is 
            // set to that array, else a new array is created.
            $list = @$children[$pt] ? $children[$pt] : new self();
            // Add the current item to the end of the list
            // array.
            if (is_null($key))
            {
                $list[] = $item;
            }
            elseif ($key === true)
            {
                $list[] = $itemkey;
            }
            elseif (is_object($item))
            {
                if (array_key_exists($key, get_object_vars($item)))
                {
                    $list[$item->$key] = $item;
                }
                else
                {
                    $get = 'get' . ucfirst($key);
                    $list[$item->$get()] = $item;
                }
            }
            elseif (is_array($item))
            {
                $list[$item[$key]] = $item;
            }
            
            
            // Assign $list as the value for when the key is
            // equal to the current parent id.
            $children[$pt] = $list;
        }
        
        return new self($children);
    }
    
    /**
    * @desc Convenience method for groupByField
    * above. Mostly useful for CompleteArrayObjects
    * or arrays that are associative where you would
    * like the keys grouped by the values.
    * 
    * @param array $array
    * @return CompleteArrayObject
    */
    public function group($array = null)
    {
        return $this->groupByField(null, true, $array);
    }
    
    /**
    * @desc Returns a new CompleteArrayObject containing
    * the keys associated with a particular
    * field name and value
    * 
    * @param string $fieldName
    * @param mixed $value
    * @param boolean $allow_nulls
    * @return CompleteArrayObject
    */
    public function keys($fieldName = null, $value = null, $allow_nulls = false)
    {
        if (is_null($fieldName))
        {
            $array = $this->getArrayCopy();
        }
        else
        {
            $array = $this->_fieldValuesArray($fieldName);
        }
        
        if (!$allow_nulls)
        {
            if (is_null($value))
            {
                return new self(array_keys($array)); 
            }
        }
        
        return new self(array_keys($array, $value));    
    }
    
    /**
    * @desc Creates a new CompleteArrayObject
    * from all the values contained in the field
    * name.
    * 
    * @return CompleteArrayObject
    */
    public function values($fieldName = null)
    {
        if (is_null($fieldName))
        {
            $array = $this->getArrayCopy();
        }
        else
        {
            $array = $this->_fieldValuesArray($fieldName);
        }
        
        return new self(array_values($array));
    }
    
    /**
    * @desc Performs a natsort on the
    * internal list and then reverses the list
    * 
    * @return void
    */
    public function natrsort()
    {
        $this->natsort();
        $this->reverse();
    }
    
    /**
    * @desc Performs a natcasesort on the
    * internal list and then reverses the list
    * 
    * @return void
    */
    public function natcasersort()
    {
        $this->natcasesort();
        $this->reverse();
    }
    
    /**
    * @desc Changes the casing for the keys
    * used in the list to either upper or lowercase.
    * Use the CASE_UPPER or CASE_LOWER constants for
    * the value of $case
    * 
    * @param const $case
    * @return void
    */
    public function changeKeyCase($case = CASE_LOWER)
    {
        $array = $this->getArrayCopy();
        $this->exchangeArray(array_change_key_case($array, $case));    
    }
    
    /**
    * @desc Returns the internal list with the 
    * result of the function provided mapped to
    * each value for the field name provided.
    * 
    * @param string $function
    * @param string $fieldName
    * @return void
    */
    public function map($function, $fieldName = null)
    {  
        $array = $this->getArrayCopy();
        foreach($array as $key => $item)
        {
            if (is_object($item))
            {
                if (array_key_exists($fieldName, get_object_vars($item)))
                {
                    $item->$fieldName = $function($item->$fieldName);
                }
                else
                {
                    $get = 'get' . ucfirst($fieldName);
                    $set = 'set' . ucfirst($fieldName);
                    $item->$set($function($item->$get()));
                }
                continue;
            }
            if (is_array($item))
            {
                $array[$key] = $function($item[$fieldName]);
                continue;
            }
            
            if (is_null($fieldName))
            {
                $array = array_map($function, $array);        
                continue;
            }            
        }
        
        return $this->exchangeArray($array);
    }
    
    /**
    * @desc Sets the type for the class
    * Exchanges the current array after changing
    * the type so that an exception will be thrown
    * if the new type is incompatible with any
    * items already in the internal list.
    * 
    * @param string $type
    * @return void
    */
    public function setType($type)
    {
        
        if (is_string($type)) {
            $array = $this->getArrayCopy();
            $this->_type = $type;
            $this->exchangeArray($array);
        }
    }
    
    /**
    * @desc Gets the type for the class
    * 
    * @return string
    */
    public function getType()
    {
        return $this->_type;
    }
    
    /**
    * @desc Java-like convenience method for append
    * 
    * @param mixed $value
    * @return void
    */
    public function add($value)
    {
        $this->append($value);
    }
    
    /**
    * @desc Java-like convenience method for offsetGet
    * 
    * @param string $key
    * @return mixed
    */
    public function get($key)
    {
        return $this->offsetGet($key);
    }
    
    /**
    * @desc Java-like convenience method for offsetSet
    * 
    * @param string $key
    * @param mixed $value
    * @return void
    */
    public function set($key, $value)
    {
        $this->offsetSet($key, $value);
    }
    
    /**
    * @desc Java-like convenience method for offsetSet
    * 
    * @param string $key
    * @param mixed $value
    * @return void
    */
    public function put($key, $value)
    {
        $this->offsetSet($key, $value);
    }
    
    /**
    * @desc Java-like convenience method for offsetExists
    * 
    * @param string $key
    * @return boolean
    */
    public function exists($key)
    {
        return $this->offsetExists($key);
    }
    
    /**
    * @desc Java-like convenience method for offsetUnset
    * 
    * @param string $key
    * @return void
    */
    public function remove($key)
    {
        $this->offsetUnset($key);
    }
    
    /**
    * @desc Java-like convenience method for getArrayCopy
    * 
    * @return array
    */
    public function toArray()
    {
        return $this->getArrayCopy();
    }
    
    /**
    * @desc Returns the JSON String representation
    * of the current list.
    * 
    * @return string
    */
    public function toJson()
    {
        return json_encode($this->getArrayCopy());
    }
    
    /**
    * @desc Returns a CompleteArrayObject
    * from a JSON string
    * 
    * @param string $json
    * @return CompleteArrayObject
    */
    public static function fromJson($json)
    {
        $array = json_decode($json, true);
        foreach($array as $key => $item)
        {
            if (is_array($item))
            {
                $array[$key] = new self($item);
            }
        }
        return new self($array);
    }
    
    /**
    * @desc Iterates through the list and creates
    * a new array of values using the field name
    * as a guide.
    * 
    * @param string $fieldName
    * @return array
    */
    protected function _fieldValuesArray($fieldName = null)
    {
        $array = $this->getArrayCopy();
        $fieldValuesArray = array();
        foreach($array as $key => $item)
        {
            if (is_object($item))
            {
                if (empty($fieldName))
                {
                    $this->typeExceptionMessage($item);
                }
                if (array_key_exists($fieldName, get_object_vars($item)))
                {
                    $fieldValuesArray[$key] = $item->$fieldName;
                }
                else
                {
                    $get = 'get' . ucfirst($fieldName);
                    $fieldValuesArray[$key] = $item->$get();
                }
                continue;
            }
            if (is_array($item))
            {
                $fieldValuesArray[$key] = $item[$fieldName];
                continue;
            }
            if (is_null($fieldName))
            {
                $fieldValuesArray[$key] = $item;
                continue;
            }            
        }
        
        return $fieldValuesArray;
    }
    
    /**
    * @desc ToString Method
    * 
    * @return string
    */
    public function __toString()
    {
        return "<pre>" . print_r($this, true) . "</pre>";
    }
    
}
Return current item: Complete Array Object