Location: PHPKode > scripts > xArray > xarray/xarray.php
<?php
/**
 * Class xArray - eXtended arrays.
 * 
 * This class allows for a different way of array manipulation, 
 * especially facilitating the operations on the 
 * arrays of objects (this may come in handy when working with, 
 * say, database results). 
 * It is modeled after Enumerable/Array objects of
 * prototype.js javascript library, and incorporates most
 * of the functionallity found there. Plus, most methods accept 
 * a boolean $inPlace argument that replaces the working 
 * (current) xArray with the method result.
 * 
 * A note about the "lambda/callback" datatype that you'll find in 
 * the comments ($iterator arguments) - this is either a valid 
 * PHP callback, or a string containing valid PHP function body
 * (ie. can be passed as an argument to create_function). 
 * 
 * Created on 13-Nov-06
 *
 * @package xArray
 * @author Vladislav Bailovic <hide@address.com>
 */

class xArray {
	
	/**
	 * Internal storage.
	 * You shouldn't use it directly.
	 * 
	 * @access private
	 */
	var $_store = array();
	
	/**
	 * Constructor.
	 * 
	 * @param mixed $objectArray Either an array, or a list of arguments
	 */
	function xArray ($objectArray=false) {
		$passedArray = func_get_args();
		if (!is_array($objectArray)) {
			$objectArray = (count ($passedArray) > 1) ? $passedArray : array();
		} 
		$this->_store = $objectArray;  
		$this->reset();
	}
	
	/**
	 * Appends value/array of values to the end of the array.
	 * 
	 * @param mixed $what Will append it to the end of the xArray
	 */
	function append ($what) {
		if (is_array($what)) return $this->appendArray($what);
		else return $this->appendSingle($what);		
	}
	
	/**
	 * Appends array.
	 * You *could* use this method yourself, but there is no need
	 * to do so - append() will do the datatype guesswork for you.
	 * 
	 * @param array $which Array to append
	 */
	function appendArray ($which) {
		if (!is_array($which)) return false;
		foreach ($which as $ind=>$obj) $this->append ($obj, $ind);		
	}
	
	/**
	 * Appends single value.
	 * You *could* use this method yourself, but there is no need
	 * to do so - append() will do the datatype guesswork for you.
	 * 
	 * @param scalar What What to append
	 * @param int $where Where to append
	 */
	function appendSingle ($what, $where=false) {
		if (is_array($what)) return false;
		if (false === $where) $this->_store[] = $what;
		else $this->_store[$where] = $what;
		return true;
	}
	
	/**
	 * Prepends value/array of values at the beginning of the array.
	 * 
	 * @param mixed $what What to prepend
	 */
	function prepend ($what) {
		if (is_array($what)) return $this->prependArray($what);
		else return $this->prependSingle($what);	
	}
	
	/**
	 * Prepends array.
	 * You *could* use this method yourself, but there is no need
	 * to do so - prepend() will do the datatype guesswork for you.
	 * 
	 * @param array $which Array to prepend
	 */
	function prependArray ($which) {
		if (!is_array($which)) return false;
		$l1 = $this->length();
		$l2 = count ($which);
		$this->_store = array_merge ($which, $this->_store);
		return ($this->length() == $l1+$l2) ? true : false;
	}
	
	/**
	 * Prepends single value.
	 * You *could* use this method yourself, but there is no need
	 * to do so - prepend() will do the datatype guesswork for you.
	 * 
	 * @param scalar $what What to prepend
	 */
	function prependSingle ($what) {
		if (is_array($what)) return false;
		return $this->prependArray (array($what));
	}
	
	/**
	 * Returns xArray length;
	 * 
	 * @return int xArray length
	 */
	function length () {
		return count($this->_store);
	}
	
	/**
	 * Returns reversed xArray.
	 * If passed $inPlace argument, reverses xArray itself.
	 * 
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Reversed xArray
	 */
	function reverse ($inPlace=false) {
		$return = $this->_store;
		$return = array_reverse($return);
		if ($inPlace !== false) {
			$this->_store = $return;
			$this->reset();
		}
		return new xArray($return);
	}
	
	/**
	 * Resets xArray iteration pointer.
	 */
	function reset () {
		reset ($this->_store);
	}
	
	/**
	 * Returns first element of the xArray.
	 */
	function first () {
		return reset ($this->_store);
	}
	
	/**
	 * Returns last element of the xArray.
	 * 
	 * @return mixed Last xArray element
	 */
	function last () {
		$item = end ($this->_store);
		reset ($this->_store);
		return $item;
	}
	
	/**
	 * Returns next element of the xArray.
	 * 
	 * @return mixed Value of the curent xArray member
	 */
	function fetch () {
		$item = each ($this->_store);
		if ($item) { 
			list($index, $value) = $item;
			return $value;	
		} else {
			$this->reset();
		}
		return false;
	}
	
	/**
	 * Calls $iterator callback/lambda on every member of xArray.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 */
	function each ($iterator) {
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			call_user_func_array ($iterator, array($value, $index));
		}
	}
	
	/**
	 * True if all true.
	 * Returns true if all xArray members return true form supplied
	 * $iterator callback/lambda.
	 * Second parameter forces strict testing.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $strict Forces strict testing
	 * @return bool True if all true, false otherwise
	 */
	function all ($iterator, $strict=false) {
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$ret = call_user_func_array ($iterator, array($value, $index));
			if ($strict) {
				if ($ret === false) return false;
			} else {
				if (!$ret) return false;
			}
		}
		return true;
	}
	
	/**
	 * True if any is true.
	 * Returns true if any one of xArray members returns true form supplied
	 * $iterator callback/lambda.
	 * Second parameter forces strict testing.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $strict Forces strict testing
	 * @return bool True if any returns true, false otherwise
	 */
	function any ($iterator, $strict=false) {
		$returnValue = false;
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$ret = call_user_func_array ($iterator, array($value, $index));
			if ($strict) {
				if ($ret === true) $returnValue = true;
			} else {
				if ($ret) $returnValue = true;
			}
		}
		return $returnValue;
	}
	
	/**
	 * Calls $iterator callback/lambda on each xArray member
	 * and returns result as xArray.
	 * If passed $inPlace attribute, result replaces current xArray.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function collect ($iterator, $inPlace=false) {
		$result = array();
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$result[$index] = call_user_func_array ($iterator, array($this->_makeValueClone($value), $index));
		}
		if ($inPlace !== false) {
			$this->_store = $result;
			$this->reset();
		}
		return new xArray($result);
	}
	
	/**
	 * Same as collect().
	 */
	function map ($iterator, $inPlace=false) {return $this->collect($iterator, $inPlace);}
	
	/**
	 * Returns first xArray member that returns true from $iterator 
	 * callback/lambda. False if none found.
	 * Second parameter forces strict testing.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $strict Forces strict testing
	 * @return mixed First xArray member that returns true
	 */
	function detect ($iterator, $strict=false) {
		$returnValue = false;
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$ret = call_user_func_array ($iterator, array($value, $index));
			if ($strict) {
				if ($ret === true) return $value;
			} else {
				if ($ret) return $value;
			}
		}
		return false;
	}
	
	/**
	 * Same as detect().
	 */
	function find ($iterator, $strict=false) {return $this->detect($iterator,$strict);}
	
	/**
	 * Tests xArray members against supplied regular expression pattern.
	 * Returns matching results as xArray.
	 * If $iterator callback/lambda given, result contains return values
	 * of $iterator for each match.
	 * If given $inPlace attribute, will replace current xArray with result.
	 * 
	 * @param string $pattern Pattern to match against
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function grep ($pattern, $iterator=false, $inPlace=false) {
		$result = array();
		if ($iterator && !is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			if (is_string($value) && preg_match ($pattern, $value)) { 
				$result[$index] = ($iterator) ? 
					call_user_func_array ($iterator, array($this->_makeValueClone($value), $index))
					:
					$value;
			}
		}
		if ($inPlace !== false) {
			$this->_store = $result;
			$this->reset();
		}
		return new xArray($result);
	}
	
	/**
	 * Checks xArray members for a pattern.
	 * 
	 * @param string $pattern Pattern to check
	 * @return bool True if match found, false otherwise
	 */
	function has ($pattern) {
		$test = $this->grep($pattern);
		return ($test->length()>0) ? true : false;	
	}
	
	/**
	 * Tests xArray keys against supplied regular expression pattern.
	 * Returns matching results as xArray.
	 * If $iterator callback/lambda given, result contains return values
	 * of $iterator for each match.
	 * If given $inPlace attribute, will replace current xArray with result.
	 * 
	 * @param string $pattern Pattern to check keys against
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function grepKeys ($pattern, $iterator=false, $inPlace=false) {
		$result = array();
		if ($iterator && !is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			if (preg_match ($pattern, $index)) { 
				$result[$index] = ($iterator) ? 
					call_user_func_array ($iterator, array($this->_makeValueClone($value), $index))
					:
					$value;
			}
		}
		if ($inPlace !== false) {
			$this->_store = $result;
			$this->reset();
		}
		return new xArray($result);
	}
	
	/**
	 * Checks xArray members' keys for a pattern.
	 * 
	 * @param string $pattern Pattern to check
	 * @return bool True if match found, false otherwise
	 */
	function hasKey ($pattern) {
		$test = $this->grepKeys($pattern);
		return ($test->length()>0) ? true : false;
	}
	
	/**
	 * Calls $methodName method on each xArray object member.
	 * Returns results as xArray.
	 * Optional arguments array is passed to each method.
	 * 
	 * @param string $methodName Method name to invoke
	 * @param array $args Arguments to pass to the invoked method
	 * @return xArray Resulting xArray
	 */
	function invoke ($methodName, $args=array()) {
		$return = array(); 
		if (is_object($args) && get_class($this) == get_class($args)) $args = $args->toArray();
		foreach ($this->_store as $index=>$value) {
			if (is_object($value) && method_exists($value, $methodName))
				$return[$index] = call_user_func_array (array(&$value, $methodName), $args);
		}
		return new xArray($return);
	}
	
	/**
	 * Calls $methodName method of a particular xArray member.
	 * 
	 * @param string $methodName Method to invoke
	 * @param string/int $id xArray member identifier
	 * @param array $args Arguments to pass to the invoked method
	 * @return mixed Return value of the invoked method
	 */
	function invokeSingle ($methodName, $id, $args=array()) {
		$item = $this->get($id);
		if (is_object($item) && method_exists($item, $methodName)) 
			return call_user_func_array (array(&$item, $methodName), $args);
	}

	/**
	 * Returns the element with the greatest result of calling the $iterator
	 * callback/lambda for each xArray element, if given. Else returns element
	 * with greatest value.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @return mixed Member with maximum result
	 */
	function max ($iterator=false) {
		$return = array();
		if (!$iterator) $iterator = 'return $value;';
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$return[$index] = call_user_func_array ($iterator, array($value, $index));
		}
		@arsort($return);
		$key = key($return);
		return $this->get($key);
	}
	
	/**
	 * Returns the element with the smallest result of calling the $iterator
	 * callback/lambda for each xArray element, if given. Else returns element
	 * with smallest value.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @return mixed Member with minimum result
	 */
	function min ($iterator=false) {
		$return = array();
		if (!$iterator) $iterator = 'return $value;';
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$return[$index] = call_user_func_array ($iterator, array($value, $index));
		}
		@asort($return);
		$key = key($return);
		return $this->get($key);
	}
	
	/**
	 * Returns xArray with all elements that return true-a-like value
	 * from $iterator callback/lambda. Loose testing.
	 * If given $inPlace parameter, replaces current xArray.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function select ($iterator, $inPlace=false) {
		$return = array();
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$ret = call_user_func_array ($iterator, array($this->_makeValueClone($value), $index));
			if ($ret) $return[$index] = $value;
		}
		if ($inPlace !== false) {
			$this->_store = $return;
			$this->reset();
		}
		return new xArray($return);
	}
	
	/**
	 * Returns xArray with all elements that return false-a-like value
	 * from $iterator callback/lambda. Loose testing.
	 * If given $inPlace parameter, replaces current xArray.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function reject ($iterator, $inPlace=false) {
		$return = array();
		if (!is_callable($iterator)) $iterator = create_function ('$value, $index', $iterator);
		foreach ($this->_store as $index=>$value) {
			$ret = call_user_func_array ($iterator, array($this->_makeValueClone($value), $index));
			if (!$ret) $return[$index] = $value;
		}
		if ($inPlace !== false) {
			$this->_store = $return;
			$this->reset();
		}
		return new xArray($return);
	}
	
	/**
	 * Returns xArray with all elements' $propertyName properties. 
	 * If given $inPlace parameter, replaces current xArray.
	 * 
	 * @param string $propertyName Name of the members' property to pluck
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function pluck ($propertyName, $inPlace=false) {
		$return = array();
		foreach ($this->_store as $index=>$value) {
			if (is_object($value) && isset($value->$propertyName)) $return[$index] = $value->$propertyName;
		}
		if ($inPlace !== false) {
			$this->_store = $return;
			$this->reset();
		}
		return new xArray($return);
	}
	
	/**
	 * Returns a property of a single, particular member.
	 * 
	 * @param string $propertyName Name of the property to pluck
	 * @param string/int $id xArray member identifier
	 * @return mixed Property value
	 */
	function pluckSingle ($propertyName, $id) {
		$item = $this->get($id);
		if (is_object($item) && isset($item->$propertyName)) return $item->$propertyName;
		return false;
	}
	
	/**
	 * Returns xArray sorted by $iterator callback/lambda result.
	 * If given $inPlace parameter, replaces current xArray.
	 * 
	 * @param lambda/callback $iterator Lambda/callback function
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function sortBy ($iterator, $inPlace=false) {
		if (!is_callable($iterator)) $iterator = create_function ('$a, $b', $iterator);
		$return = $this->_store;
		usort ($return, $iterator);
		if ($inPlace !== false) {
			$this->_store = $return;
			$this->reset();
		}
		return new xArray($return);
	}
	
	/**
	 * Returns xArray as simple array.
	 * 
	 * @return array xArray array representation
	 */
	function toArray () {
		return $this->_store;
	}
	
	/**
	 * Returns xArray as string.
	 * 
	 * @return string xArray string representation
	 */
	function toString ($separator=', ') {
		return @implode ($separator, $this->_store);
	}
	
	/**
	 * Returns xArray $id member.
	 * 
	 * @param int/string $id xArray member identifier
	 * @return mixed xArray member
	 */
	function get ($id) {
		if (isset($this->_store[$id])) return $this->_store[$id];
		else return false;
	}
	
	/**
	 * Sets xArray $id member to $value.
	 * Optional third parameter forces overwrite (defaults to true).
	 * 
	 * @param string/int $id xArray member identifier
	 * @param mixed $value New value to set
	 * @param bool $force Force overwrite (defaults to true)
	 * @return bool True on success, false otherwise
	 */
	function set ($id, $value, $force=true) {
		if ((isset($this->_store[$id]) && $force) || !isset($this->_store[$id])) {
			$this->_store[$id] = $value;
			return true;
		} else if (isset($this->_store[$id]) && !$force) {
			return false;
		}
	}
	
	/**
	 * Unsets xArray $id member.
	 * 
	 * @param string/int xArray member identifier
	 * @return bool True on success, false otherwise
	 */
	function remove ($id) {
		if (false === $id) return false;
		if (isset($this->_store[$id])) {
			unset($this->_store[$id]);
			return true;
		}
		else return false;
	}
	
	/**
	 * Clears xArray members.
	 */
	function clear () {
		$this->_store = array();
		return true;
	}
	
	/**
	 * Returns xArray index for $value.
	 * Optional third parameter forces strict matching. 
	 * 
	 * @param mixed $val Value to find
	 * @param bool $strict Forces strict matching
	 * @return mixed Key value if found, (bool)false otherwise
	 */
	function indexOf ($val, $strict=false) {
		return @array_search($val, $this->_store, $strict);
		
	}
	
	/**
	 * Returns xArray reindexed and with null values removed.
	 * If given $inPlace parameter, result replaces current xArray.
	 * 
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 * @return xArray Resulting xArray
	 */
	function compact ($inPlace=false) {
		$result = array();
		foreach ($this->_store as $value) {
			if ($value !== null) $result[] = $value; 
		}
		if ($inPlace !== false) {
			$this->_store = $result;
			$this->reset();
		}
		return new xArray($result);
	}
	
	/**
	 * Returns current xArray without supplied values.
	 * 
	 * @param mixed $passedArray Either an array, xArray, or a list of values to exclude 
	 * @return xArray Resulting xArray
	 */
	function without ($passedArray) {
		if (!is_array($passedArray) && 
			(is_object($passedArray) && get_class($this)==get_class($passedArray))) 
			$passedArray = $passedArray->toArray();
		if (!is_array($passedArray)) $passedArray = func_get_args();
		$result = new xArray($this->toArray());
		foreach ($passedArray as $reject) {
			$result->remove($result->indexOf($reject));
		}
		return $result;
	}
	
	/**
	 * Returns current xArray without supplied keys.
	 * 
	 * @param mixed $passedArray Either an array, xArray, or a list of keys to exclude 
	 * @return xArray Resulting xArray
	 */
	function withoutKeys ($passedArray) {
		if (!is_array($passedArray) && 
			(is_object($passedArray) && get_class($this)==get_class($passedArray))) 
			$passedArray = $passedArray->toArray();
		if (!is_array($passedArray)) $passedArray = func_get_args();
		$result = new xArray($this->toArray());
		foreach ($passedArray as $reject) {
			$result->remove($reject);
		}
		return $result;
	}
	
	/**
	 * Extracts a portion of xArray.
	 * 
	 * @param int $start Where to start extracting
	 * @param int $len How many members to extract
	 * @param bool $inPlace If set, operation takes place on the xArray itself
	 */
	function extract ($start=0, $len=false, $inPlace=false) {
		if ($len === false) $len=$this->length() - $start;
		$result = array_slice($this->_store, $start, $len);
		if ($inPlace !== false) {
			$this->_store = $result;
			$this->reset();
		}
		return new xArray($result);
	}
	
	/* #################### PHP Compatibility Routines ################# */
	
	/**
	 * Makes object clones for PHP5. 
	 * @access private
	 */
	function _makeValueClone ($val) {
		if (phpversion() >= 5 && is_object($val)) {
			return clone($val);
		}
		return $val;
	}
}
?>
Return current item: xArray