CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.10 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.10
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper
  • None

Classes

  • CakeNumber
  • CakeText
  • CakeTime
  • ClassRegistry
  • Debugger
  • File
  • Folder
  • Hash
  • Inflector
  • ObjectCollection
  • Sanitize
  • Security
  • Set
  • String
  • Validation
  • Xml
   1: <?php
   2: /**
   3:  * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
   4:  * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
   5:  *
   6:  * Licensed under The MIT License
   7:  * For full copyright and license information, please see the LICENSE.txt
   8:  * Redistributions of files must retain the above copyright notice.
   9:  *
  10:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11:  * @link          https://cakephp.org CakePHP(tm) Project
  12:  * @package       Cake.Utility
  13:  * @since         CakePHP(tm) v 2.2.0
  14:  * @license       https://opensource.org/licenses/mit-license.php MIT License
  15:  */
  16: 
  17: App::uses('CakeText', 'Utility');
  18: 
  19: /**
  20:  * Library of array functions for manipulating and extracting data
  21:  * from arrays or 'sets' of data.
  22:  *
  23:  * `Hash` provides an improved interface, more consistent and
  24:  * predictable set of features over `Set`. While it lacks the spotty
  25:  * support for pseudo Xpath, its more fully featured dot notation provides
  26:  * similar features in a more consistent implementation.
  27:  *
  28:  * @package       Cake.Utility
  29:  */
  30: class Hash {
  31: 
  32: /**
  33:  * Get a single value specified by $path out of $data.
  34:  * Does not support the full dot notation feature set,
  35:  * but is faster for simple read operations.
  36:  *
  37:  * @param array $data Array of data to operate on.
  38:  * @param string|array $path The path being searched for. Either a dot
  39:  *   separated string, or an array of path segments.
  40:  * @param mixed $default The return value when the path does not exist
  41:  * @throws InvalidArgumentException
  42:  * @return mixed The value fetched from the array, or null.
  43:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::get
  44:  */
  45:     public static function get(array $data, $path, $default = null) {
  46:         if (empty($data) || $path === null) {
  47:             return $default;
  48:         }
  49:         if (is_string($path) || is_numeric($path)) {
  50:             $parts = explode('.', $path);
  51:         } elseif (is_bool($path) || $path === null) {
  52:             $parts = array($path);
  53:         } else {
  54:             if (!is_array($path)) {
  55:                 throw new InvalidArgumentException(__d('cake_dev',
  56:                     'Invalid path parameter: %s, should be dot separated path or array.',
  57:                     var_export($path, true)
  58:                 ));
  59:             }
  60:             $parts = $path;
  61:         }
  62: 
  63:         foreach ($parts as $key) {
  64:             if (is_array($data) && isset($data[$key])) {
  65:                 $data =& $data[$key];
  66:             } else {
  67:                 return $default;
  68:             }
  69:         }
  70: 
  71:         return $data;
  72:     }
  73: 
  74: /**
  75:  * Gets the values from an array matching the $path expression.
  76:  * The path expression is a dot separated expression, that can contain a set
  77:  * of patterns and expressions:
  78:  *
  79:  * - `{n}` Matches any numeric key, or integer.
  80:  * - `{s}` Matches any string key.
  81:  * - `{*}` Matches any value.
  82:  * - `Foo` Matches any key with the exact same value.
  83:  *
  84:  * There are a number of attribute operators:
  85:  *
  86:  *  - `=`, `!=` Equality.
  87:  *  - `>`, `<`, `>=`, `<=` Value comparison.
  88:  *  - `=/.../` Regular expression pattern match.
  89:  *
  90:  * Given a set of User array data, from a `$User->find('all')` call:
  91:  *
  92:  * - `1.User.name` Get the name of the user at index 1.
  93:  * - `{n}.User.name` Get the name of every user in the set of users.
  94:  * - `{n}.User[id].name` Get the name of every user with an id key.
  95:  * - `{n}.User[id>=2].name` Get the name of every user with an id key greater than or equal to 2.
  96:  * - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`.
  97:  *
  98:  * @param array $data The data to extract from.
  99:  * @param string $path The path to extract.
 100:  * @return array An array of the extracted values. Returns an empty array
 101:  *   if there are no matches.
 102:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::extract
 103:  */
 104:     public static function extract(array $data, $path) {
 105:         if (empty($path)) {
 106:             return $data;
 107:         }
 108: 
 109:         // Simple paths.
 110:         if (!preg_match('/[{\[]/', $path)) {
 111:             return (array)static::get($data, $path);
 112:         }
 113: 
 114:         if (strpos($path, '[') === false) {
 115:             $tokens = explode('.', $path);
 116:         } else {
 117:             $tokens = CakeText::tokenize($path, '.', '[', ']');
 118:         }
 119: 
 120:         $_key = '__set_item__';
 121: 
 122:         $context = array($_key => array($data));
 123: 
 124:         foreach ($tokens as $token) {
 125:             $next = array();
 126: 
 127:             list($token, $conditions) = static::_splitConditions($token);
 128: 
 129:             foreach ($context[$_key] as $item) {
 130:                 foreach ((array)$item as $k => $v) {
 131:                     if (static::_matchToken($k, $token)) {
 132:                         $next[] = $v;
 133:                     }
 134:                 }
 135:             }
 136: 
 137:             // Filter for attributes.
 138:             if ($conditions) {
 139:                 $filter = array();
 140:                 foreach ($next as $item) {
 141:                     if (is_array($item) && static::_matches($item, $conditions)) {
 142:                         $filter[] = $item;
 143:                     }
 144:                 }
 145:                 $next = $filter;
 146:             }
 147:             $context = array($_key => $next);
 148: 
 149:         }
 150:         return $context[$_key];
 151:     }
 152: 
 153: /**
 154:  * Split token conditions
 155:  *
 156:  * @param string $token the token being splitted.
 157:  * @return array array(token, conditions) with token splitted
 158:  */
 159:     protected static function _splitConditions($token) {
 160:         $conditions = false;
 161:         $position = strpos($token, '[');
 162:         if ($position !== false) {
 163:             $conditions = substr($token, $position);
 164:             $token = substr($token, 0, $position);
 165:         }
 166: 
 167:         return array($token, $conditions);
 168:     }
 169: 
 170: /**
 171:  * Check a key against a token.
 172:  *
 173:  * @param string $key The key in the array being searched.
 174:  * @param string $token The token being matched.
 175:  * @return bool
 176:  */
 177:     protected static function _matchToken($key, $token) {
 178:         switch ($token) {
 179:             case '{n}':
 180:                 return is_numeric($key);
 181:             case '{s}':
 182:                 return is_string($key);
 183:             case '{*}':
 184:                 return true;
 185:             default:
 186:                 return is_numeric($token) ? ($key == $token) : $key === $token;
 187:         }
 188:     }
 189: 
 190: /**
 191:  * Checks whether or not $data matches the attribute patterns
 192:  *
 193:  * @param array $data Array of data to match.
 194:  * @param string $selector The patterns to match.
 195:  * @return bool Fitness of expression.
 196:  */
 197:     protected static function _matches(array $data, $selector) {
 198:         preg_match_all(
 199:             '/(\[ (?P<attr>[^=><!]+?) (\s* (?P<op>[><!]?[=]|[><]) \s* (?P<val>(?:\/.*?\/ | [^\]]+)) )? \])/x',
 200:             $selector,
 201:             $conditions,
 202:             PREG_SET_ORDER
 203:         );
 204: 
 205:         foreach ($conditions as $cond) {
 206:             $attr = $cond['attr'];
 207:             $op = isset($cond['op']) ? $cond['op'] : null;
 208:             $val = isset($cond['val']) ? $cond['val'] : null;
 209: 
 210:             // Presence test.
 211:             if (empty($op) && empty($val) && !isset($data[$attr])) {
 212:                 return false;
 213:             }
 214: 
 215:             // Empty attribute = fail.
 216:             if (!(isset($data[$attr]) || array_key_exists($attr, $data))) {
 217:                 return false;
 218:             }
 219: 
 220:             $prop = null;
 221:             if (isset($data[$attr])) {
 222:                 $prop = $data[$attr];
 223:             }
 224:             $isBool = is_bool($prop);
 225:             if ($isBool && is_numeric($val)) {
 226:                 $prop = $prop ? '1' : '0';
 227:             } elseif ($isBool) {
 228:                 $prop = $prop ? 'true' : 'false';
 229:             }
 230: 
 231:             // Pattern matches and other operators.
 232:             if ($op === '=' && $val && $val[0] === '/') {
 233:                 if (!preg_match($val, $prop)) {
 234:                     return false;
 235:                 }
 236:             } elseif (($op === '=' && $prop != $val) ||
 237:                 ($op === '!=' && $prop == $val) ||
 238:                 ($op === '>' && $prop <= $val) ||
 239:                 ($op === '<' && $prop >= $val) ||
 240:                 ($op === '>=' && $prop < $val) ||
 241:                 ($op === '<=' && $prop > $val)
 242:             ) {
 243:                 return false;
 244:             }
 245: 
 246:         }
 247:         return true;
 248:     }
 249: 
 250: /**
 251:  * Insert $values into an array with the given $path. You can use
 252:  * `{n}` and `{s}` elements to insert $data multiple times.
 253:  *
 254:  * @param array $data The data to insert into.
 255:  * @param string $path The path to insert at.
 256:  * @param mixed $values The values to insert.
 257:  * @return array The data with $values inserted.
 258:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::insert
 259:  */
 260:     public static function insert(array $data, $path, $values = null) {
 261:         if (strpos($path, '[') === false) {
 262:             $tokens = explode('.', $path);
 263:         } else {
 264:             $tokens = CakeText::tokenize($path, '.', '[', ']');
 265:         }
 266: 
 267:         if (strpos($path, '{') === false && strpos($path, '[') === false) {
 268:             return static::_simpleOp('insert', $data, $tokens, $values);
 269:         }
 270: 
 271:         $token = array_shift($tokens);
 272:         $nextPath = implode('.', $tokens);
 273: 
 274:         list($token, $conditions) = static::_splitConditions($token);
 275: 
 276:         foreach ($data as $k => $v) {
 277:             if (static::_matchToken($k, $token)) {
 278:                 if (!$conditions || static::_matches($v, $conditions)) {
 279:                     $data[$k] = $nextPath
 280:                         ? static::insert($v, $nextPath, $values)
 281:                         : array_merge($v, (array)$values);
 282:                 }
 283:             }
 284:         }
 285:         return $data;
 286:     }
 287: 
 288: /**
 289:  * Perform a simple insert/remove operation.
 290:  *
 291:  * @param string $op The operation to do.
 292:  * @param array $data The data to operate on.
 293:  * @param array $path The path to work on.
 294:  * @param mixed $values The values to insert when doing inserts.
 295:  * @return array data.
 296:  */
 297:     protected static function _simpleOp($op, $data, $path, $values = null) {
 298:         $_list =& $data;
 299: 
 300:         $count = count($path);
 301:         $last = $count - 1;
 302:         foreach ($path as $i => $key) {
 303:             if ($op === 'insert') {
 304:                 if ($i === $last) {
 305:                     $_list[$key] = $values;
 306:                     return $data;
 307:                 }
 308:                 if (!isset($_list[$key])) {
 309:                     $_list[$key] = array();
 310:                 }
 311:                 $_list =& $_list[$key];
 312:                 if (!is_array($_list)) {
 313:                     $_list = array();
 314:                 }
 315:             } elseif ($op === 'remove') {
 316:                 if ($i === $last) {
 317:                     if (is_array($_list)) {
 318:                         unset($_list[$key]);
 319:                     }
 320:                     return $data;
 321:                 }
 322:                 if (!isset($_list[$key])) {
 323:                     return $data;
 324:                 }
 325:                 $_list =& $_list[$key];
 326:             }
 327:         }
 328:     }
 329: 
 330: /**
 331:  * Remove data matching $path from the $data array.
 332:  * You can use `{n}` and `{s}` to remove multiple elements
 333:  * from $data.
 334:  *
 335:  * @param array $data The data to operate on
 336:  * @param string $path A path expression to use to remove.
 337:  * @return array The modified array.
 338:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::remove
 339:  */
 340:     public static function remove(array $data, $path) {
 341:         if (strpos($path, '[') === false) {
 342:             $tokens = explode('.', $path);
 343:         } else {
 344:             $tokens = CakeText::tokenize($path, '.', '[', ']');
 345:         }
 346: 
 347:         if (strpos($path, '{') === false && strpos($path, '[') === false) {
 348:             return static::_simpleOp('remove', $data, $tokens);
 349:         }
 350: 
 351:         $token = array_shift($tokens);
 352:         $nextPath = implode('.', $tokens);
 353: 
 354:         list($token, $conditions) = static::_splitConditions($token);
 355: 
 356:         foreach ($data as $k => $v) {
 357:             $match = static::_matchToken($k, $token);
 358:             if ($match && is_array($v)) {
 359:                 if ($conditions) {
 360:                     if (static::_matches($v, $conditions)) {
 361:                         if ($nextPath !== '') {
 362:                             $data[$k] = static::remove($v, $nextPath);
 363:                         } else {
 364:                             unset($data[$k]);
 365:                         }
 366:                     }
 367:                 } else {
 368:                     $data[$k] = static::remove($v, $nextPath);
 369:                 }
 370:                 if (empty($data[$k])) {
 371:                     unset($data[$k]);
 372:                 }
 373:             } elseif ($match && $nextPath === '') {
 374:                 unset($data[$k]);
 375:             }
 376:         }
 377:         return $data;
 378:     }
 379: 
 380: /**
 381:  * Creates an associative array using `$keyPath` as the path to build its keys, and optionally
 382:  * `$valuePath` as path to get the values. If `$valuePath` is not specified, all values will be initialized
 383:  * to null (useful for Hash::merge). You can optionally group the values by what is obtained when
 384:  * following the path specified in `$groupPath`.
 385:  *
 386:  * @param array $data Array from where to extract keys and values
 387:  * @param array|string $keyPath A dot-separated string or array for formatting rules.
 388:  * @param array|string $valuePath A dot-separated string or array for formatting rules.
 389:  * @param string $groupPath A dot-separated string.
 390:  * @return array Combined array
 391:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::combine
 392:  * @throws CakeException CakeException When keys and values count is unequal.
 393:  */
 394:     public static function combine(array $data, $keyPath, $valuePath = null, $groupPath = null) {
 395:         if (empty($data)) {
 396:             return array();
 397:         }
 398: 
 399:         if (is_array($keyPath)) {
 400:             $format = array_shift($keyPath);
 401:             $keys = static::format($data, $keyPath, $format);
 402:         } else {
 403:             $keys = static::extract($data, $keyPath);
 404:         }
 405:         if (empty($keys)) {
 406:             return array();
 407:         }
 408: 
 409:         if (!empty($valuePath) && is_array($valuePath)) {
 410:             $format = array_shift($valuePath);
 411:             $vals = static::format($data, $valuePath, $format);
 412:         } elseif (!empty($valuePath)) {
 413:             $vals = static::extract($data, $valuePath);
 414:         }
 415:         if (empty($vals)) {
 416:             $vals = array_fill(0, count($keys), null);
 417:         }
 418: 
 419:         if (count($keys) !== count($vals)) {
 420:             throw new CakeException(__d(
 421:                 'cake_dev',
 422:                 'Hash::combine() needs an equal number of keys + values.'
 423:             ));
 424:         }
 425: 
 426:         if ($groupPath !== null) {
 427:             $group = static::extract($data, $groupPath);
 428:             if (!empty($group)) {
 429:                 $c = count($keys);
 430:                 for ($i = 0; $i < $c; $i++) {
 431:                     if (!isset($group[$i])) {
 432:                         $group[$i] = 0;
 433:                     }
 434:                     if (!isset($out[$group[$i]])) {
 435:                         $out[$group[$i]] = array();
 436:                     }
 437:                     $out[$group[$i]][$keys[$i]] = $vals[$i];
 438:                 }
 439:                 return $out;
 440:             }
 441:         }
 442:         if (empty($vals)) {
 443:             return array();
 444:         }
 445:         return array_combine($keys, $vals);
 446:     }
 447: 
 448: /**
 449:  * Returns a formatted series of values extracted from `$data`, using
 450:  * `$format` as the format and `$paths` as the values to extract.
 451:  *
 452:  * Usage:
 453:  *
 454:  * ```
 455:  * $result = Hash::format($users, array('{n}.User.id', '{n}.User.name'), '%s : %s');
 456:  * ```
 457:  *
 458:  * The `$format` string can use any format options that `vsprintf()` and `sprintf()` do.
 459:  *
 460:  * @param array $data Source array from which to extract the data
 461:  * @param array $paths An array containing one or more Hash::extract()-style key paths
 462:  * @param string $format Format string into which values will be inserted, see sprintf()
 463:  * @return array An array of strings extracted from `$path` and formatted with `$format`
 464:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format
 465:  * @see sprintf()
 466:  * @see Hash::extract()
 467:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format
 468:  */
 469:     public static function format(array $data, array $paths, $format) {
 470:         $extracted = array();
 471:         $count = count($paths);
 472: 
 473:         if (!$count) {
 474:             return null;
 475:         }
 476: 
 477:         for ($i = 0; $i < $count; $i++) {
 478:             $extracted[] = static::extract($data, $paths[$i]);
 479:         }
 480:         $out = array();
 481:         $data = $extracted;
 482:         $count = count($data[0]);
 483: 
 484:         $countTwo = count($data);
 485:         for ($j = 0; $j < $count; $j++) {
 486:             $args = array();
 487:             for ($i = 0; $i < $countTwo; $i++) {
 488:                 if (array_key_exists($j, $data[$i])) {
 489:                     $args[] = $data[$i][$j];
 490:                 }
 491:             }
 492:             $out[] = vsprintf($format, $args);
 493:         }
 494:         return $out;
 495:     }
 496: 
 497: /**
 498:  * Determines if one array contains the exact keys and values of another.
 499:  *
 500:  * @param array $data The data to search through.
 501:  * @param array $needle The values to file in $data
 502:  * @return bool true if $data contains $needle, false otherwise
 503:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::contains
 504:  */
 505:     public static function contains(array $data, array $needle) {
 506:         if (empty($data) || empty($needle)) {
 507:             return false;
 508:         }
 509:         $stack = array();
 510: 
 511:         while (!empty($needle)) {
 512:             $key = key($needle);
 513:             $val = $needle[$key];
 514:             unset($needle[$key]);
 515: 
 516:             if (array_key_exists($key, $data) && is_array($val)) {
 517:                 $next = $data[$key];
 518:                 unset($data[$key]);
 519: 
 520:                 if (!empty($val)) {
 521:                     $stack[] = array($val, $next);
 522:                 }
 523:             } elseif (!array_key_exists($key, $data) || $data[$key] != $val) {
 524:                 return false;
 525:             }
 526: 
 527:             if (empty($needle) && !empty($stack)) {
 528:                 list($needle, $data) = array_pop($stack);
 529:             }
 530:         }
 531:         return true;
 532:     }
 533: 
 534: /**
 535:  * Test whether or not a given path exists in $data.
 536:  * This method uses the same path syntax as Hash::extract()
 537:  *
 538:  * Checking for paths that could target more than one element will
 539:  * make sure that at least one matching element exists.
 540:  *
 541:  * @param array $data The data to check.
 542:  * @param string $path The path to check for.
 543:  * @return bool Existence of path.
 544:  * @see Hash::extract()
 545:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::check
 546:  */
 547:     public static function check(array $data, $path) {
 548:         $results = static::extract($data, $path);
 549:         if (!is_array($results)) {
 550:             return false;
 551:         }
 552:         return count($results) > 0;
 553:     }
 554: 
 555: /**
 556:  * Recursively filters a data set.
 557:  *
 558:  * @param array $data Either an array to filter, or value when in callback
 559:  * @param callable $callback A function to filter the data with. Defaults to
 560:  *   `static::_filter()` Which strips out all non-zero empty values.
 561:  * @return array Filtered array
 562:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::filter
 563:  */
 564:     public static function filter(array $data, $callback = array('self', '_filter')) {
 565:         foreach ($data as $k => $v) {
 566:             if (is_array($v)) {
 567:                 $data[$k] = static::filter($v, $callback);
 568:             }
 569:         }
 570:         return array_filter($data, $callback);
 571:     }
 572: 
 573: /**
 574:  * Callback function for filtering.
 575:  *
 576:  * @param array $var Array to filter.
 577:  * @return bool
 578:  */
 579:     protected static function _filter($var) {
 580:         if ($var === 0 || $var === 0.0 || $var === '0' || !empty($var)) {
 581:             return true;
 582:         }
 583:         return false;
 584:     }
 585: 
 586: /**
 587:  * Collapses a multi-dimensional array into a single dimension, using a delimited array path for
 588:  * each array element's key, i.e. array(array('Foo' => array('Bar' => 'Far'))) becomes
 589:  * array('0.Foo.Bar' => 'Far').)
 590:  *
 591:  * @param array $data Array to flatten
 592:  * @param string $separator String used to separate array key elements in a path, defaults to '.'
 593:  * @return array
 594:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::flatten
 595:  */
 596:     public static function flatten(array $data, $separator = '.') {
 597:         $result = array();
 598:         $stack = array();
 599:         $path = null;
 600: 
 601:         reset($data);
 602:         while (!empty($data)) {
 603:             $key = key($data);
 604:             $element = $data[$key];
 605:             unset($data[$key]);
 606: 
 607:             if (is_array($element) && !empty($element)) {
 608:                 if (!empty($data)) {
 609:                     $stack[] = array($data, $path);
 610:                 }
 611:                 $data = $element;
 612:                 reset($data);
 613:                 $path .= $key . $separator;
 614:             } else {
 615:                 $result[$path . $key] = $element;
 616:             }
 617: 
 618:             if (empty($data) && !empty($stack)) {
 619:                 list($data, $path) = array_pop($stack);
 620:                 reset($data);
 621:             }
 622:         }
 623:         return $result;
 624:     }
 625: 
 626: /**
 627:  * Expands a flat array to a nested array.
 628:  *
 629:  * For example, unflattens an array that was collapsed with `Hash::flatten()`
 630:  * into a multi-dimensional array. So, `array('0.Foo.Bar' => 'Far')` becomes
 631:  * `array(array('Foo' => array('Bar' => 'Far')))`.
 632:  *
 633:  * @param array $data Flattened array
 634:  * @param string $separator The delimiter used
 635:  * @return array
 636:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::expand
 637:  */
 638:     public static function expand($data, $separator = '.') {
 639:         $result = array();
 640: 
 641:         $stack = array();
 642: 
 643:         foreach ($data as $flat => $value) {
 644:             $keys = explode($separator, $flat);
 645:             $keys = array_reverse($keys);
 646:             $child = array(
 647:                 $keys[0] => $value
 648:             );
 649:             array_shift($keys);
 650:             foreach ($keys as $k) {
 651:                 $child = array(
 652:                     $k => $child
 653:                 );
 654:             }
 655: 
 656:             $stack[] = array($child, &$result);
 657: 
 658:             while (!empty($stack)) {
 659:                 foreach ($stack as $curKey => &$curMerge) {
 660:                     foreach ($curMerge[0] as $key => &$val) {
 661:                         if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) {
 662:                             $stack[] = array(&$val, &$curMerge[1][$key]);
 663:                         } elseif ((int)$key === $key && isset($curMerge[1][$key])) {
 664:                             $curMerge[1][] = $val;
 665:                         } else {
 666:                             $curMerge[1][$key] = $val;
 667:                         }
 668:                     }
 669:                     unset($stack[$curKey]);
 670:                 }
 671:                 unset($curMerge);
 672:             }
 673:         }
 674:         return $result;
 675:     }
 676: 
 677: /**
 678:  * This function can be thought of as a hybrid between PHP's `array_merge` and `array_merge_recursive`.
 679:  *
 680:  * The difference between this method and the built-in ones, is that if an array key contains another array, then
 681:  * Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for
 682:  * keys that contain scalar values (unlike `array_merge_recursive`).
 683:  *
 684:  * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays.
 685:  *
 686:  * @param array $data Array to be merged
 687:  * @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged
 688:  * @return array Merged array
 689:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::merge
 690:  */
 691:     public static function merge(array $data, $merge) {
 692:         $args = array_slice(func_get_args(), 1);
 693:         $return = $data;
 694: 
 695:         foreach ($args as &$curArg) {
 696:             $stack[] = array((array)$curArg, &$return);
 697:         }
 698:         unset($curArg);
 699: 
 700:         while (!empty($stack)) {
 701:             foreach ($stack as $curKey => &$curMerge) {
 702:                 foreach ($curMerge[0] as $key => &$val) {
 703:                     if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) {
 704:                         $stack[] = array(&$val, &$curMerge[1][$key]);
 705:                     } elseif ((int)$key === $key && isset($curMerge[1][$key])) {
 706:                         $curMerge[1][] = $val;
 707:                     } else {
 708:                         $curMerge[1][$key] = $val;
 709:                     }
 710:                 }
 711:                 unset($stack[$curKey]);
 712:             }
 713:             unset($curMerge);
 714:         }
 715:         return $return;
 716:     }
 717: 
 718: /**
 719:  * Checks to see if all the values in the array are numeric
 720:  *
 721:  * @param array $data The array to check.
 722:  * @return bool true if values are numeric, false otherwise
 723:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::numeric
 724:  */
 725:     public static function numeric(array $data) {
 726:         if (empty($data)) {
 727:             return false;
 728:         }
 729:         return $data === array_filter($data, 'is_numeric');
 730:     }
 731: 
 732: /**
 733:  * Counts the dimensions of an array.
 734:  * Only considers the dimension of the first element in the array.
 735:  *
 736:  * If you have an un-even or heterogeneous array, consider using Hash::maxDimensions()
 737:  * to get the dimensions of the array.
 738:  *
 739:  * @param array $data Array to count dimensions on
 740:  * @return int The number of dimensions in $data
 741:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::dimensions
 742:  */
 743:     public static function dimensions(array $data) {
 744:         if (empty($data)) {
 745:             return 0;
 746:         }
 747:         reset($data);
 748:         $depth = 1;
 749:         while ($elem = array_shift($data)) {
 750:             if (is_array($elem)) {
 751:                 $depth += 1;
 752:                 $data =& $elem;
 753:             } else {
 754:                 break;
 755:             }
 756:         }
 757:         return $depth;
 758:     }
 759: 
 760: /**
 761:  * Counts the dimensions of *all* array elements. Useful for finding the maximum
 762:  * number of dimensions in a mixed array.
 763:  *
 764:  * @param array $data Array to count dimensions on
 765:  * @return int The maximum number of dimensions in $data
 766:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::maxDimensions
 767:  */
 768:     public static function maxDimensions($data) {
 769:         $depth = array();
 770:         if (is_array($data) && reset($data) !== false) {
 771:             foreach ($data as $value) {
 772:                 $depth[] = static::maxDimensions($value) + 1;
 773:             }
 774:         }
 775:         return empty($depth) ? 0 : max($depth);
 776:     }
 777: 
 778: /**
 779:  * Map a callback across all elements in a set.
 780:  * Can be provided a path to only modify slices of the set.
 781:  *
 782:  * @param array $data The data to map over, and extract data out of.
 783:  * @param string $path The path to extract for mapping over.
 784:  * @param callable $function The function to call on each extracted value.
 785:  * @return array An array of the modified values.
 786:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::map
 787:  */
 788:     public static function map(array $data, $path, $function) {
 789:         $values = (array)static::extract($data, $path);
 790:         return array_map($function, $values);
 791:     }
 792: 
 793: /**
 794:  * Reduce a set of extracted values using `$function`.
 795:  *
 796:  * @param array $data The data to reduce.
 797:  * @param string $path The path to extract from $data.
 798:  * @param callable $function The function to call on each extracted value.
 799:  * @return mixed The reduced value.
 800:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::reduce
 801:  */
 802:     public static function reduce(array $data, $path, $function) {
 803:         $values = (array)static::extract($data, $path);
 804:         return array_reduce($values, $function);
 805:     }
 806: 
 807: /**
 808:  * Apply a callback to a set of extracted values using `$function`.
 809:  * The function will get the extracted values as the first argument.
 810:  *
 811:  * ### Example
 812:  *
 813:  * You can easily count the results of an extract using apply().
 814:  * For example to count the comments on an Article:
 815:  *
 816:  * ```
 817:  * $count = Hash::apply($data, 'Article.Comment.{n}', 'count');
 818:  * ```
 819:  *
 820:  * You could also use a function like `array_sum` to sum the results.
 821:  *
 822:  * ```
 823:  * $total = Hash::apply($data, '{n}.Item.price', 'array_sum');
 824:  * ```
 825:  *
 826:  * @param array $data The data to reduce.
 827:  * @param string $path The path to extract from $data.
 828:  * @param callable $function The function to call on each extracted value.
 829:  * @return mixed The results of the applied method.
 830:  */
 831:     public static function apply(array $data, $path, $function) {
 832:         $values = (array)static::extract($data, $path);
 833:         return call_user_func($function, $values);
 834:     }
 835: 
 836: /**
 837:  * Sorts an array by any value, determined by a Hash-compatible path
 838:  *
 839:  * ### Sort directions
 840:  *
 841:  * - `asc` Sort ascending.
 842:  * - `desc` Sort descending.
 843:  *
 844:  * ### Sort types
 845:  *
 846:  * - `regular` For regular sorting (don't change types)
 847:  * - `numeric` Compare values numerically
 848:  * - `string` Compare values as strings
 849:  * - `locale` Compare items as strings, based on the current locale
 850:  * - `natural` Compare items as strings using "natural ordering" in a human friendly way.
 851:  *   Will sort foo10 below foo2 as an example. Requires PHP 5.4 or greater or it will fallback to 'regular'
 852:  *
 853:  * To do case insensitive sorting, pass the type as an array as follows:
 854:  *
 855:  * ```
 856:  * array('type' => 'regular', 'ignoreCase' => true)
 857:  * ```
 858:  *
 859:  * When using the array form, `type` defaults to 'regular'. The `ignoreCase` option
 860:  * defaults to `false`.
 861:  *
 862:  * @param array $data An array of data to sort
 863:  * @param string $path A Hash-compatible path to the array value
 864:  * @param string $dir See directions above. Defaults to 'asc'.
 865:  * @param array|string $type See direction types above. Defaults to 'regular'.
 866:  * @return array Sorted array of data
 867:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::sort
 868:  */
 869:     public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') {
 870:         if (empty($data)) {
 871:             return array();
 872:         }
 873:         $originalKeys = array_keys($data);
 874:         $numeric = is_numeric(implode('', $originalKeys));
 875:         if ($numeric) {
 876:             $data = array_values($data);
 877:         }
 878:         $sortValues = static::extract($data, $path);
 879:         $dataCount = count($data);
 880: 
 881:         // Make sortValues match the data length, as some keys could be missing
 882:         // the sorted value path.
 883:         $missingData = count($sortValues) < $dataCount;
 884:         if ($missingData && $numeric) {
 885:             // Get the path without the leading '{n}.'
 886:             $itemPath = substr($path, 4);
 887:             foreach ($data as $key => $value) {
 888:                 $sortValues[$key] = static::get($value, $itemPath);
 889:             }
 890:         } elseif ($missingData) {
 891:             $sortValues = array_pad($sortValues, $dataCount, null);
 892:         }
 893:         $result = static::_squash($sortValues);
 894:         $keys = static::extract($result, '{n}.id');
 895:         $values = static::extract($result, '{n}.value');
 896: 
 897:         $dir = strtolower($dir);
 898:         $ignoreCase = false;
 899: 
 900:         // $type can be overloaded for case insensitive sort
 901:         if (is_array($type)) {
 902:             $type += array('ignoreCase' => false, 'type' => 'regular');
 903:             $ignoreCase = $type['ignoreCase'];
 904:             $type = $type['type'];
 905:         }
 906:         $type = strtolower($type);
 907: 
 908:         if ($type === 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) {
 909:             $type = 'regular';
 910:         }
 911: 
 912:         if ($dir === 'asc') {
 913:             $dir = SORT_ASC;
 914:         } else {
 915:             $dir = SORT_DESC;
 916:         }
 917:         if ($type === 'numeric') {
 918:             $type = SORT_NUMERIC;
 919:         } elseif ($type === 'string') {
 920:             $type = SORT_STRING;
 921:         } elseif ($type === 'natural') {
 922:             $type = SORT_NATURAL;
 923:         } elseif ($type === 'locale') {
 924:             $type = SORT_LOCALE_STRING;
 925:         } else {
 926:             $type = SORT_REGULAR;
 927:         }
 928: 
 929:         if ($ignoreCase) {
 930:             $values = array_map('mb_strtolower', $values);
 931:         }
 932:         array_multisort($values, $dir, $type, $keys, $dir);
 933: 
 934:         $sorted = array();
 935:         $keys = array_unique($keys);
 936: 
 937:         foreach ($keys as $k) {
 938:             if ($numeric) {
 939:                 $sorted[] = $data[$k];
 940:                 continue;
 941:             }
 942:             if (isset($originalKeys[$k])) {
 943:                 $sorted[$originalKeys[$k]] = $data[$originalKeys[$k]];
 944:             } else {
 945:                 $sorted[$k] = $data[$k];
 946:             }
 947:         }
 948:         return $sorted;
 949:     }
 950: 
 951: /**
 952:  * Helper method for sort()
 953:  * Squashes an array to a single hash so it can be sorted.
 954:  *
 955:  * @param array $data The data to squash.
 956:  * @param string $key The key for the data.
 957:  * @return array
 958:  */
 959:     protected static function _squash($data, $key = null) {
 960:         $stack = array();
 961:         foreach ($data as $k => $r) {
 962:             $id = $k;
 963:             if ($key !== null) {
 964:                 $id = $key;
 965:             }
 966:             if (is_array($r) && !empty($r)) {
 967:                 $stack = array_merge($stack, static::_squash($r, $id));
 968:             } else {
 969:                 $stack[] = array('id' => $id, 'value' => $r);
 970:             }
 971:         }
 972:         return $stack;
 973:     }
 974: 
 975: /**
 976:  * Computes the difference between two complex arrays.
 977:  * This method differs from the built-in array_diff() in that it will preserve keys
 978:  * and work on multi-dimensional arrays.
 979:  *
 980:  * @param array $data First value
 981:  * @param array $compare Second value
 982:  * @return array Returns the key => value pairs that are not common in $data and $compare
 983:  *    The expression for this function is ($data - $compare) + ($compare - ($data - $compare))
 984:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::diff
 985:  */
 986:     public static function diff(array $data, $compare) {
 987:         if (empty($data)) {
 988:             return (array)$compare;
 989:         }
 990:         if (empty($compare)) {
 991:             return (array)$data;
 992:         }
 993:         $intersection = array_intersect_key($data, $compare);
 994:         while (($key = key($intersection)) !== null) {
 995:             if ($data[$key] == $compare[$key]) {
 996:                 unset($data[$key]);
 997:                 unset($compare[$key]);
 998:             }
 999:             next($intersection);
1000:         }
1001:         return $data + $compare;
1002:     }
1003: 
1004: /**
1005:  * Merges the difference between $data and $compare onto $data.
1006:  *
1007:  * @param array $data The data to append onto.
1008:  * @param array $compare The data to compare and append onto.
1009:  * @return array The merged array.
1010:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::mergeDiff
1011:  */
1012:     public static function mergeDiff(array $data, $compare) {
1013:         if (empty($data) && !empty($compare)) {
1014:             return $compare;
1015:         }
1016:         if (empty($compare)) {
1017:             return $data;
1018:         }
1019:         foreach ($compare as $key => $value) {
1020:             if (!array_key_exists($key, $data)) {
1021:                 $data[$key] = $value;
1022:             } elseif (is_array($value)) {
1023:                 $data[$key] = static::mergeDiff($data[$key], $compare[$key]);
1024:             }
1025:         }
1026:         return $data;
1027:     }
1028: 
1029: /**
1030:  * Normalizes an array, and converts it to a standard format.
1031:  *
1032:  * @param array $data List to normalize
1033:  * @param bool $assoc If true, $data will be converted to an associative array.
1034:  * @return array
1035:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::normalize
1036:  */
1037:     public static function normalize(array $data, $assoc = true) {
1038:         $keys = array_keys($data);
1039:         $count = count($keys);
1040:         $numeric = true;
1041: 
1042:         if (!$assoc) {
1043:             for ($i = 0; $i < $count; $i++) {
1044:                 if (!is_int($keys[$i])) {
1045:                     $numeric = false;
1046:                     break;
1047:                 }
1048:             }
1049:         }
1050:         if (!$numeric || $assoc) {
1051:             $newList = array();
1052:             for ($i = 0; $i < $count; $i++) {
1053:                 if (is_int($keys[$i])) {
1054:                     $newList[$data[$keys[$i]]] = null;
1055:                 } else {
1056:                     $newList[$keys[$i]] = $data[$keys[$i]];
1057:                 }
1058:             }
1059:             $data = $newList;
1060:         }
1061:         return $data;
1062:     }
1063: 
1064: /**
1065:  * Takes in a flat array and returns a nested array
1066:  *
1067:  * ### Options:
1068:  *
1069:  * - `children` The key name to use in the resultset for children.
1070:  * - `idPath` The path to a key that identifies each entry. Should be
1071:  *   compatible with Hash::extract(). Defaults to `{n}.$alias.id`
1072:  * - `parentPath` The path to a key that identifies the parent of each entry.
1073:  *   Should be compatible with Hash::extract(). Defaults to `{n}.$alias.parent_id`
1074:  * - `root` The id of the desired top-most result.
1075:  *
1076:  * @param array $data The data to nest.
1077:  * @param array $options Options are:
1078:  * @return array of results, nested
1079:  * @see Hash::extract()
1080:  * @throws InvalidArgumentException When providing invalid data.
1081:  * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::nest
1082:  */
1083:     public static function nest(array $data, $options = array()) {
1084:         if (!$data) {
1085:             return $data;
1086:         }
1087: 
1088:         $alias = key(current($data));
1089:         $options += array(
1090:             'idPath' => "{n}.$alias.id",
1091:             'parentPath' => "{n}.$alias.parent_id",
1092:             'children' => 'children',
1093:             'root' => null
1094:         );
1095: 
1096:         $return = $idMap = array();
1097:         $ids = static::extract($data, $options['idPath']);
1098: 
1099:         $idKeys = explode('.', $options['idPath']);
1100:         array_shift($idKeys);
1101: 
1102:         $parentKeys = explode('.', $options['parentPath']);
1103:         array_shift($parentKeys);
1104: 
1105:         foreach ($data as $result) {
1106:             $result[$options['children']] = array();
1107: 
1108:             $id = static::get($result, $idKeys);
1109:             $parentId = static::get($result, $parentKeys);
1110: 
1111:             if (isset($idMap[$id][$options['children']])) {
1112:                 $idMap[$id] = array_merge($result, (array)$idMap[$id]);
1113:             } else {
1114:                 $idMap[$id] = array_merge($result, array($options['children'] => array()));
1115:             }
1116:             if (!$parentId || !in_array($parentId, $ids)) {
1117:                 $return[] =& $idMap[$id];
1118:             } else {
1119:                 $idMap[$parentId][$options['children']][] =& $idMap[$id];
1120:             }
1121:         }
1122: 
1123:         if (!$return) {
1124:             throw new InvalidArgumentException(__d('cake_dev',
1125:                 'Invalid data array to nest.'
1126:             ));
1127:         }
1128: 
1129:         if ($options['root']) {
1130:             $root = $options['root'];
1131:         } else {
1132:             $root = static::get($return[0], $parentKeys);
1133:         }
1134: 
1135:         foreach ($return as $i => $result) {
1136:             $id = static::get($result, $idKeys);
1137:             $parentId = static::get($result, $parentKeys);
1138:             if ($id !== $root && $parentId != $root) {
1139:                 unset($return[$i]);
1140:             }
1141:         }
1142:         return array_values($return);
1143:     }
1144: 
1145: }
1146: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs