diff options
Diffstat (limited to 'poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Utility/Hash.php')
-rw-r--r-- | poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Utility/Hash.php | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Utility/Hash.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Utility/Hash.php new file mode 100644 index 0000000..9514ccc --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Utility/Hash.php @@ -0,0 +1,974 @@ +<?php +/** + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package Cake.Utility + * @since CakePHP(tm) v 2.2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +App::uses('String', 'Utility'); + +/** + * Library of array functions for manipulating and extracting data + * from arrays or 'sets' of data. + * + * `Hash` provides an improved interface, more consistent and + * predictable set of features over `Set`. While it lacks the spotty + * support for pseudo Xpath, its more fully featured dot notation provides + * similar features in a more consistent implementation. + * + * @package Cake.Utility + */ +class Hash { + +/** + * Get a single value specified by $path out of $data. + * Does not support the full dot notation feature set, + * but is faster for simple read operations. + * + * @param array $data Array of data to operate on. + * @param string|array $path The path being searched for. Either a dot + * separated string, or an array of path segments. + * @return mixed The value fetched from the array, or null. + */ + public static function get(array $data, $path) { + if (empty($data) || empty($path)) { + return null; + } + if (is_string($path)) { + $parts = explode('.', $path); + } else { + $parts = $path; + } + foreach ($parts as $key) { + if (is_array($data) && isset($data[$key])) { + $data =& $data[$key]; + } else { + return null; + } + + } + return $data; + } + +/** + * Gets the values from an array matching the $path expression. + * The path expression is a dot separated expression, that can contain a set + * of patterns and expressions: + * + * - `{n}` Matches any numeric key, or integer. + * - `{s}` Matches any string key. + * - `Foo` Matches any key with the exact same value. + * + * There are a number of attribute operators: + * + * - `=`, `!=` Equality. + * - `>`, `<`, `>=`, `<=` Value comparison. + * - `=/.../` Regular expression pattern match. + * + * Given a set of User array data, from a `$User->find('all')` call: + * + * - `1.User.name` Get the name of the user at index 1. + * - `{n}.User.name` Get the name of every user in the set of users. + * - `{n}.User[id]` Get the name of every user with an id key. + * - `{n}.User[id>=2]` Get the name of every user with an id key greater than or equal to 2. + * - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`. + * + * @param array $data The data to extract from. + * @param string $path The path to extract. + * @return array An array of the extracted values. Returns an empty array + * if there are no matches. + */ + public static function extract(array $data, $path) { + if (empty($path)) { + return $data; + } + + // Simple paths. + if (!preg_match('/[{\[]/', $path)) { + return (array)self::get($data, $path); + } + + if (strpos('[', $path) === false) { + $tokens = explode('.', $path); + } else { + $tokens = String::tokenize($path, '.', '[', ']'); + } + + $_key = '__set_item__'; + + $context = array($_key => array($data)); + + foreach ($tokens as $token) { + $next = array(); + + $conditions = false; + $position = strpos($token, '['); + if ($position !== false) { + $conditions = substr($token, $position); + $token = substr($token, 0, $position); + } + + foreach ($context[$_key] as $item) { + foreach ($item as $k => $v) { + if (self::_matchToken($k, $token)) { + $next[] = $v; + } + } + } + + // Filter for attributes. + if ($conditions) { + $filter = array(); + foreach ($next as $item) { + if (self::_matches($item, $conditions)) { + $filter[] = $item; + } + } + $next = $filter; + } + $context = array($_key => $next); + + } + return $context[$_key]; + } + +/** + * Check a key against a token. + * + * @param string $key The key in the array being searched. + * @param string $token The token being matched. + * @return boolean + */ + protected static function _matchToken($key, $token) { + if ($token === '{n}') { + return is_numeric($key); + } + if ($token === '{s}') { + return is_string($key); + } + if (is_numeric($token)) { + return ($key == $token); + } + return ($key === $token); + } + +/** + * Checks whether or not $data matches the attribute patterns + * + * @param array $data Array of data to match. + * @param string $selector The patterns to match. + * @return boolean Fitness of expression. + */ + protected static function _matches(array $data, $selector) { + preg_match_all( + '/(\[ (?<attr>[^=><!]+?) (\s* (?<op>[><!]?[=]|[><]) \s* (?<val>[^\]]+) )? \])/x', + $selector, + $conditions, + PREG_SET_ORDER + ); + + foreach ($conditions as $cond) { + $attr = $cond['attr']; + $op = isset($cond['op']) ? $cond['op'] : null; + $val = isset($cond['val']) ? $cond['val'] : null; + + // Presence test. + if (empty($op) && empty($val) && !isset($data[$attr])) { + return false; + } + + // Empty attribute = fail. + if (!(isset($data[$attr]) || array_key_exists($attr, $data))) { + return false; + } + + $prop = isset($data[$attr]) ? $data[$attr] : null; + + // Pattern matches and other operators. + if ($op === '=' && $val && $val[0] === '/') { + if (!preg_match($val, $prop)) { + return false; + } + } elseif ( + ($op === '=' && $prop != $val) || + ($op === '!=' && $prop == $val) || + ($op === '>' && $prop <= $val) || + ($op === '<' && $prop >= $val) || + ($op === '>=' && $prop < $val) || + ($op === '<=' && $prop > $val) + ) { + return false; + } + + } + return true; + } + +/** + * Insert $values into an array with the given $path. You can use + * `{n}` and `{s}` elements to insert $data multiple times. + * + * @param array $data The data to insert into. + * @param string $path The path to insert at. + * @param array $values The values to insert. + * @return array The data with $values inserted. + */ + public static function insert(array $data, $path, $values = null) { + $tokens = explode('.', $path); + if (strpos($path, '{') === false) { + return self::_simpleOp('insert', $data, $tokens, $values); + } + + $token = array_shift($tokens); + $nextPath = implode('.', $tokens); + foreach ($data as $k => $v) { + if (self::_matchToken($k, $token)) { + $data[$k] = self::insert($v, $nextPath, $values); + } + } + return $data; + } + +/** + * Perform a simple insert/remove operation. + * + * @param string $op The operation to do. + * @param array $data The data to operate on. + * @param array $path The path to work on. + * @param mixed $values The values to insert when doing inserts. + * @return array $data. + */ + protected static function _simpleOp($op, $data, $path, $values = null) { + $_list =& $data; + + $count = count($path); + $last = $count - 1; + foreach ($path as $i => $key) { + if (is_numeric($key) && intval($key) > 0 || $key === '0') { + $key = intval($key); + } + if ($op === 'insert') { + if ($i === $last) { + $_list[$key] = $values; + return $data; + } + if (!isset($_list[$key])) { + $_list[$key] = array(); + } + $_list =& $_list[$key]; + if (!is_array($_list)) { + $_list = array(); + } + } elseif ($op === 'remove') { + if ($i === $last) { + unset($_list[$key]); + return $data; + } + if (!isset($_list[$key])) { + return $data; + } + $_list =& $_list[$key]; + } + } + } + +/** + * Remove data matching $path from the $data array. + * You can use `{n}` and `{s}` to remove multiple elements + * from $data. + * + * @param array $data The data to operate on + * @param string $path A path expression to use to remove. + * @return array The modified array. + */ + public static function remove(array $data, $path) { + $tokens = explode('.', $path); + if (strpos($path, '{') === false) { + return self::_simpleOp('remove', $data, $tokens); + } + + $token = array_shift($tokens); + $nextPath = implode('.', $tokens); + foreach ($data as $k => $v) { + $match = self::_matchToken($k, $token); + if ($match && is_array($v)) { + $data[$k] = self::remove($v, $nextPath); + } elseif ($match) { + unset($data[$k]); + } + } + return $data; + } + +/** + * Creates an associative array using `$keyPath` as the path to build its keys, and optionally + * `$valuePath` as path to get the values. If `$valuePath` is not specified, all values will be initialized + * to null (useful for Hash::merge). You can optionally group the values by what is obtained when + * following the path specified in `$groupPath`. + * + * @param array $data Array from where to extract keys and values + * @param string $keyPath A dot-separated string. + * @param string $valuePath A dot-separated string. + * @param string $groupPath A dot-separated string. + * @return array Combined array + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::combine + */ + public static function combine(array $data, $keyPath, $valuePath = null, $groupPath = null) { + if (empty($data)) { + return array(); + } + + if (is_array($keyPath)) { + $format = array_shift($keyPath); + $keys = self::format($data, $keyPath, $format); + } else { + $keys = self::extract($data, $keyPath); + } + if (empty($keys)) { + return array(); + } + + if (!empty($valuePath) && is_array($valuePath)) { + $format = array_shift($valuePath); + $vals = self::format($data, $valuePath, $format); + } elseif (!empty($valuePath)) { + $vals = self::extract($data, $valuePath); + } + + $count = count($keys); + for ($i = 0; $i < $count; $i++) { + $vals[$i] = isset($vals[$i]) ? $vals[$i] : null; + } + + if ($groupPath !== null) { + $group = self::extract($data, $groupPath); + if (!empty($group)) { + $c = count($keys); + for ($i = 0; $i < $c; $i++) { + if (!isset($group[$i])) { + $group[$i] = 0; + } + if (!isset($out[$group[$i]])) { + $out[$group[$i]] = array(); + } + $out[$group[$i]][$keys[$i]] = $vals[$i]; + } + return $out; + } + } + if (empty($vals)) { + return array(); + } + return array_combine($keys, $vals); + } + +/** + * Returns a formated series of values extracted from `$data`, using + * `$format` as the format and `$paths` as the values to extract. + * + * Usage: + * + * {{{ + * $result = Hash::format($users, array('{n}.User.id', '{n}.User.name'), '%s : %s'); + * }}} + * + * The `$format` string can use any format options that `vsprintf()` and `sprintf()` do. + * + * @param array $data Source array from which to extract the data + * @param string $paths An array containing one or more Hash::extract()-style key paths + * @param string $format Format string into which values will be inserted, see sprintf() + * @return array An array of strings extracted from `$path` and formatted with `$format` + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format + * @see sprintf() + * @see Hash::extract() + */ + public static function format(array $data, array $paths, $format) { + $extracted = array(); + $count = count($paths); + + if (!$count) { + return; + } + + for ($i = 0; $i < $count; $i++) { + $extracted[] = self::extract($data, $paths[$i]); + } + $out = array(); + $data = $extracted; + $count = count($data[0]); + + $countTwo = count($data); + for ($j = 0; $j < $count; $j++) { + $args = array(); + for ($i = 0; $i < $countTwo; $i++) { + if (array_key_exists($j, $data[$i])) { + $args[] = $data[$i][$j]; + } + } + $out[] = vsprintf($format, $args); + } + return $out; + } + +/** + * Determines if one array contains the exact keys and values of another. + * + * @param array $data The data to search through. + * @param array $needle The values to file in $data + * @return boolean true if $data contains $needle, false otherwise + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::contains + */ + public static function contains(array $data, array $needle) { + if (empty($data) || empty($needle)) { + return false; + } + $stack = array(); + + $i = 1; + while (!empty($needle)) { + $key = key($needle); + $val = $needle[$key]; + unset($needle[$key]); + + if (isset($data[$key]) && is_array($val)) { + $next = $data[$key]; + unset($data[$key]); + + if (!empty($val)) { + $stack[] = array($val, $next); + } + } elseif (!isset($data[$key]) || $data[$key] != $val) { + return false; + } + + if (empty($needle) && !empty($stack)) { + list($needle, $data) = array_pop($stack); + } + } + return true; + } + +/** + * Test whether or not a given path exists in $data. + * This method uses the same path syntax as Hash::extract() + * + * Checking for paths that could target more than one element will + * make sure that at least one matching element exists. + * + * @param array $data The data to check. + * @param string $path The path to check for. + * @return boolean Existence of path. + * @see Hash::extract() + */ + public static function check(array $data, $path) { + $results = self::extract($data, $path); + if (!is_array($results)) { + return false; + } + return count($results) > 0; + } + +/** + * Recursively filters a data set. + * + * @param array $data Either an array to filter, or value when in callback + * @param callable $callback A function to filter the data with. Defaults to + * `self::_filter()` Which strips out all non-zero empty values. + * @return array Filtered array + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::filter + */ + public static function filter(array $data, $callback = array('self', '_filter')) { + foreach ($data as $k => $v) { + if (is_array($v)) { + $data[$k] = self::filter($v, $callback); + } + } + return array_filter($data, $callback); + } + +/** + * Callback function for filtering. + * + * @param array $var Array to filter. + * @return boolean + */ + protected static function _filter($var) { + if ($var === 0 || $var === '0' || !empty($var)) { + return true; + } + return false; + } + +/** + * Collapses a multi-dimensional array into a single dimension, using a delimited array path for + * each array element's key, i.e. array(array('Foo' => array('Bar' => 'Far'))) becomes + * array('0.Foo.Bar' => 'Far').) + * + * @param array $data Array to flatten + * @param string $separator String used to separate array key elements in a path, defaults to '.' + * @return array + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::flatten + */ + public static function flatten(array $data, $separator = '.') { + $result = array(); + $stack = array(); + $path = null; + + reset($data); + while (!empty($data)) { + $key = key($data); + $element = $data[$key]; + unset($data[$key]); + + if (is_array($element)) { + if (!empty($data)) { + $stack[] = array($data, $path); + } + $data = $element; + $path .= $key . $separator; + } else { + $result[$path . $key] = $element; + } + + if (empty($data) && !empty($stack)) { + list($data, $path) = array_pop($stack); + } + } + return $result; + } + +/** + * Expand/unflattens an string to an array + * + * For example, unflattens an array that was collapsed with `Hash::flatten()` + * into a multi-dimensional array. So, `array('0.Foo.Bar' => 'Far')` becomes + * `array(array('Foo' => array('Bar' => 'Far')))`. + * + * @param array $data Flattened array + * @param string $separator The delimiter used + * @return array + */ + public static function expand($data, $separator = '.') { + $result = array(); + foreach ($data as $flat => $value) { + $keys = explode($separator, $flat); + $keys = array_reverse($keys); + $child = array( + $keys[0] => $value + ); + array_shift($keys); + foreach ($keys as $k) { + $child = array( + $k => $child + ); + } + $result = self::merge($result, $child); + } + return $result; + } + +/** + * This function can be thought of as a hybrid between PHP's `array_merge` and `array_merge_recursive`. + * + * The difference between this method and the built-in ones, is that if an array key contains another array, then + * Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for + * keys that contain scalar values (unlike `array_merge_recursive`). + * + * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays. + * + * @param array $data Array to be merged + * @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged + * @return array Merged array + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::merge + */ + public static function merge(array $data, $merge) { + $args = func_get_args(); + $return = current($args); + + while (($arg = next($args)) !== false) { + foreach ((array)$arg as $key => $val) { + if (!empty($return[$key]) && is_array($return[$key]) && is_array($val)) { + $return[$key] = self::merge($return[$key], $val); + } elseif (is_int($key)) { + $return[] = $val; + } else { + $return[$key] = $val; + } + } + } + return $return; + } + +/** + * Checks to see if all the values in the array are numeric + * + * @param array $array The array to check. + * @return boolean true if values are numeric, false otherwise + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::numeric + */ + public static function numeric(array $data) { + if (empty($data)) { + return false; + } + $values = array_values($data); + $str = implode('', $values); + return (bool)ctype_digit($str); + } + +/** + * Counts the dimensions of an array. + * Only considers the dimension of the first element in the array. + * + * If you have an un-even or hetrogenous array, consider using Hash::maxDimensions() + * to get the dimensions of the array. + * + * @param array $array Array to count dimensions on + * @return integer The number of dimensions in $data + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::dimensions + */ + public static function dimensions(array $data) { + if (empty($data)) { + return 0; + } + reset($data); + $depth = 1; + while ($elem = array_shift($data)) { + if (is_array($elem)) { + $depth += 1; + $data =& $elem; + } else { + break; + } + } + return $depth; + } + +/** + * Counts the dimensions of *all* array elements. Useful for finding the maximum + * number of dimensions in a mixed array. + * + * @param array $data Array to count dimensions on + * @return integer The maximum number of dimensions in $data + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::maxDimensions + */ + public static function maxDimensions(array $data) { + $depth = array(); + if (is_array($data) && reset($data) !== false) { + foreach ($data as $value) { + $depth[] = self::dimensions((array)$value) + 1; + } + } + return max($depth); + } + +/** + * Map a callback across all elements in a set. + * Can be provided a path to only modify slices of the set. + * + * @param array $data The data to map over, and extract data out of. + * @param string $path The path to extract for mapping over. + * @param callable $function The function to call on each extracted value. + * @return array An array of the modified values. + */ + public static function map(array $data, $path, $function) { + $values = (array)self::extract($data, $path); + return array_map($function, $values); + } + +/** + * Reduce a set of extracted values using `$function`. + * + * @param array $data The data to reduce. + * @param string $path The path to extract from $data. + * @return mixed The reduced value. + */ + public static function reduce(array $data, $path, $function) { + $values = (array)self::extract($data, $path); + return array_reduce($values, $function); + } + +/** + * Apply a callback to a set of extracted values using `$function`. + * The function will get the extracted values as the first argument. + * + * @param array $data The data to reduce. + * @param string $path The path to extract from $data. + * @return mixed The results of the applied method. + */ + public static function apply(array $data, $path, $function) { + $values = (array)self::extract($data, $path); + return call_user_func($function, $values); + } + +/** + * Sorts an array by any value, determined by a Set-compatible path + * + * ### Sort directions + * + * - `asc` Sort ascending. + * - `desc` Sort descending. + * + * ## Sort types + * + * - `numeric` Sort by numeric value. + * - `regular` Sort by numeric value. + * - `string` Sort by numeric value. + * - `natural` Sort by natural order. Requires PHP 5.4 or greater. + * + * @param array $data An array of data to sort + * @param string $path A Set-compatible path to the array value + * @param string $dir See directions above. + * @param string $type See direction types above. Defaults to 'regular'. + * @return array Sorted array of data + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::sort + */ + public static function sort(array $data, $path, $dir, $type = 'regular') { + $originalKeys = array_keys($data); + $numeric = is_numeric(implode('', $originalKeys)); + if ($numeric) { + $data = array_values($data); + } + $sortValues = self::extract($data, $path); + $sortCount = count($sortValues); + $dataCount = count($data); + + // Make sortValues match the data length, as some keys could be missing + // the sorted value path. + if ($sortCount < $dataCount) { + $sortValues = array_pad($sortValues, $dataCount, null); + } + $result = self::_squash($sortValues); + $keys = self::extract($result, '{n}.id'); + $values = self::extract($result, '{n}.value'); + + $dir = strtolower($dir); + $type = strtolower($type); + if ($type == 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) { + $type == 'regular'; + } + if ($dir === 'asc') { + $dir = SORT_ASC; + } else { + $dir = SORT_DESC; + } + if ($type === 'numeric') { + $type = SORT_NUMERIC; + } elseif ($type === 'string') { + $type = SORT_STRING; + } elseif ($type === 'natural') { + $type = SORT_NATURAL; + } else { + $type = SORT_REGULAR; + } + array_multisort($values, $dir, $type, $keys, $dir, $type); + $sorted = array(); + $keys = array_unique($keys); + + foreach ($keys as $k) { + if ($numeric) { + $sorted[] = $data[$k]; + continue; + } + if (isset($originalKeys[$k])) { + $sorted[$originalKeys[$k]] = $data[$originalKeys[$k]]; + } else { + $sorted[$k] = $data[$k]; + } + } + return $sorted; + } + +/** + * Helper method for sort() + * Sqaushes an array to a single hash so it can be sorted. + * + * @param array $data The data to squash. + * @param string $key The key for the data. + * @return array + */ + protected static function _squash($data, $key = null) { + $stack = array(); + foreach ($data as $k => $r) { + $id = $k; + if (!is_null($key)) { + $id = $key; + } + if (is_array($r) && !empty($r)) { + $stack = array_merge($stack, self::_squash($r, $id)); + } else { + $stack[] = array('id' => $id, 'value' => $r); + } + } + return $stack; + } + +/** + * Computes the difference between two complex arrays. + * This method differs from the built-in array_diff() in that it will preserve keys + * and work on multi-dimensional arrays. + * + * @param array $data First value + * @param array $compare Second value + * @return array Returns the key => value pairs that are not common in $data and $compare + * The expression for this function is ($data - $compare) + ($compare - ($data - $compare)) + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::diff + */ + public static function diff(array $data, $compare) { + if (empty($data)) { + return (array)$compare; + } + if (empty($compare)) { + return (array)$data; + } + $intersection = array_intersect_key($data, $compare); + while (($key = key($intersection)) !== null) { + if ($data[$key] == $compare[$key]) { + unset($data[$key]); + unset($compare[$key]); + } + next($intersection); + } + return $data + $compare; + } + +/** + * Merges the difference between $data and $push onto $data. + * + * @param array $data The data to append onto. + * @param array $compare The data to compare and append onto. + * @return array The merged array. + */ + public static function mergeDiff(array $data, $compare) { + if (empty($data) && !empty($compare)) { + return $compare; + } + if (empty($compare)) { + return $data; + } + foreach ($compare as $key => $value) { + if (!array_key_exists($key, $data)) { + $data[$key] = $value; + } elseif (is_array($value)) { + $data[$key] = self::mergeDiff($data[$key], $compare[$key]); + } + } + return $data; + } + +/** + * Normalizes an array, and converts it to a standard format. + * + * @param array $data List to normalize + * @param boolean $assoc If true, $data will be converted to an associative array. + * @return array + * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::normalize + */ + public static function normalize(array $data, $assoc = true) { + $keys = array_keys($data); + $count = count($keys); + $numeric = true; + + if (!$assoc) { + for ($i = 0; $i < $count; $i++) { + if (!is_int($keys[$i])) { + $numeric = false; + break; + } + } + } + if (!$numeric || $assoc) { + $newList = array(); + for ($i = 0; $i < $count; $i++) { + if (is_int($keys[$i])) { + $newList[$data[$keys[$i]]] = null; + } else { + $newList[$keys[$i]] = $data[$keys[$i]]; + } + } + $data = $newList; + } + return $data; + } + +/** + * Takes in a flat array and returns a nested array + * + * ### Options: + * + * - `children` The key name to use in the resultset for children. + * - `idPath` The path to a key that identifies each entry. Should be + * compatible with Hash::extract(). Defaults to `{n}.$alias.id` + * - `parentPath` The path to a key that identifies the parent of each entry. + * Should be compatible with Hash::extract(). Defaults to `{n}.$alias.parent_id` + * - `root` The id of the desired top-most result. + * + * @param array $data The data to nest. + * @param array $options Options are: + * @return array of results, nested + * @see Hash::extract() + */ + public static function nest(array $data, $options = array()) { + if (!$data) { + return $data; + } + + $alias = key(current($data)); + $options += array( + 'idPath' => "{n}.$alias.id", + 'parentPath' => "{n}.$alias.parent_id", + 'children' => 'children', + 'root' => null + ); + + $return = $idMap = array(); + $ids = self::extract($data, $options['idPath']); + + $idKeys = explode('.', $options['idPath']); + array_shift($idKeys); + + $parentKeys = explode('.', $options['parentPath']); + array_shift($parentKeys); + + foreach ($data as $result) { + $result[$options['children']] = array(); + + $id = self::get($result, $idKeys); + $parentId = self::get($result, $parentKeys); + + if (isset($idMap[$id][$options['children']])) { + $idMap[$id] = array_merge($result, (array)$idMap[$id]); + } else { + $idMap[$id] = array_merge($result, array($options['children'] => array())); + } + if (!$parentId || !in_array($parentId, $ids)) { + $return[] =& $idMap[$id]; + } else { + $idMap[$parentId][$options['children']][] =& $idMap[$id]; + } + } + + if ($options['root']) { + $root = $options['root']; + } else { + $root = self::get($return[0], $parentKeys); + } + + foreach ($return as $i => $result) { + $id = self::get($result, $idKeys); + $parentId = self::get($result, $parentKeys); + if ($id !== $root && $parentId != $root) { + unset($return[$i]); + } + } + return array_values($return); + } + +} |