PageRenderTime 8ms CodeModel.GetById 33ms app.highlight 63ms RepoModel.GetById 1ms app.codeStats 0ms

/underscore.php

http://github.com/brianhaveri/Underscore.php
PHP | 1120 lines | 718 code | 289 blank | 113 comment | 121 complexity | 37edad5646a65baba696610efa18a35d MD5 | raw file
   1<?php
   2
   3/**
   4 * Underscore.php v1.3.1
   5 * Copyright (c) 2011 Brian Haveri
   6 * Underscore.php is licensed under the MIT license
   7 * Underscore.php was inspired by and borrowed from Underscore.js
   8 * For docs, license, tests, and downloads, see: http://brianhaveri.github.com/Underscore.php
   9 */
  10
  11// Returns an instance of __ for OO-style calls
  12function __($item=null) {
  13  $__ = new __;
  14  if(func_num_args() > 0) $__->_wrapped = $item;
  15  return $__;
  16}
  17
  18// Underscore.php
  19class __ {
  20  
  21  // Start the chain
  22  private $_chained = false; // Are we in a chain?
  23  public function chain($item=null) {
  24    list($item) = self::_wrapArgs(func_get_args(), 1);
  25    
  26    $__ = (isset($this) && isset($this->_chained) && $this->_chained) ? $this : __($item);
  27    $__->_chained = true;
  28    return $__;
  29  }
  30  
  31  
  32  // End the chain
  33  public function value() {
  34    return (isset($this)) ? $this->_wrapped : null;
  35  }
  36  
  37  
  38  // Invoke the iterator on each item in the collection
  39  public function each($collection=null, $iterator=null) {
  40    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  41    
  42    if(is_null($collection)) return self::_wrap(null);
  43    
  44    $collection = (array) self::_collection($collection);
  45    if(count($collection) === 0) return self::_wrap(null);
  46    
  47    foreach($collection as $k=>$v) {
  48      call_user_func($iterator, $v, $k, $collection);
  49    }
  50    return self::_wrap(null);
  51  }
  52  
  53  
  54  // Return an array of values by mapping each item through the iterator
  55  // map alias: collect
  56  public function collect($collection=null, $iterator=null) { return self::map($collection, $iterator); }
  57  public function map($collection=null, $iterator=null) {
  58    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
  59    
  60    if(is_null($collection)) return self::_wrap(array());
  61    
  62    $collection = (array) self::_collection($collection);
  63    if(count($collection) === 0) self::_wrap(array());
  64    
  65    $return = array();
  66    foreach($collection as $k=>$v) {
  67      $return[] = call_user_func($iterator, $v, $k, $collection);
  68    }
  69    return self::_wrap($return);
  70  }
  71  
  72  
  73  // Reduce a collection to a single value
  74  // reduce aliases: foldl, inject
  75  public function foldl($collection=null, $iterator=null, $memo=null) { return self::reduce($collection, $iterator, $memo); }
  76  public function inject($collection=null, $iterator=null, $memo=null) { return self::reduce($collection, $iterator, $memo); }
  77  public function reduce($collection=null, $iterator=null, $memo=null) {
  78    list($collection, $iterator, $memo) = self::_wrapArgs(func_get_args(), 3);
  79    
  80    if(!is_object($collection) && !is_array($collection)) {
  81      if(is_null($memo)) throw new Exception('Invalid object');
  82      else return self::_wrap($memo);
  83    }
  84    
  85    return self::_wrap(array_reduce($collection, $iterator, $memo));
  86  }
  87  
  88  
  89  // Right-associative version of reduce
  90  // reduceRight alias: foldr
  91  public function foldr($collection=null, $iterator=null, $memo=null) { return self::reduceRight($collection, $iterator, $memo); }
  92  public function reduceRight($collection=null, $iterator=null, $memo=null) {
  93    list($collection, $iterator, $memo) = self::_wrapArgs(func_get_args(), 3);
  94    
  95    if(!is_object($collection) && !is_array($collection)) {
  96      if(is_null($memo)) throw new Exception('Invalid object');
  97      else return self::_wrap($memo);
  98    }
  99    
 100    krsort($collection);
 101    
 102    $__ = new self;
 103    return self::_wrap($__->reduce($collection, $iterator, $memo));
 104  }
 105  
 106  
 107  // Extract an array of values for a given property
 108  public function pluck($collection=null, $key=null) {
 109    list($collection, $key) = self::_wrapArgs(func_get_args(), 2);
 110    
 111    $collection = (array) self::_collection($collection);
 112        
 113    $return = array();
 114    foreach($collection as $item) {
 115      foreach($item as $k=>$v) {
 116        if($k === $key) $return[] = $v;
 117      }
 118    }
 119    return self::_wrap($return);
 120  }
 121  
 122  
 123  // Does the collection contain this value?
 124  // includ alias: contains
 125  public function contains($collection=null, $val=null) { return self::includ($collection, $val); }
 126  public function includ($collection=null, $val=null) {
 127    list($collection, $val) = self::_wrapArgs(func_get_args(), 2);
 128    
 129    $collection = (array) self::_collection($collection);
 130    
 131    return self::_wrap((array_search($val, $collection, true) !== false));
 132  }
 133  
 134  
 135  // Invoke the named function over each item in the collection, optionally passing arguments to the function
 136  public function invoke($collection=null, $function_name=null, $arguments=null) {
 137    $args = self::_wrapArgs(func_get_args(), 2);
 138    $__ = new self;
 139    list($collection, $function_name) = $__->first($args, 2);
 140    $arguments = $__->rest(func_get_args(), 2);
 141    
 142    // If passed an array or string, return an array
 143    // If passed an object, return an object
 144    $is_obj = is_object($collection);
 145    $result = (empty($arguments)) ? array_map($function_name, (array) $collection) : array_map($function_name, (array) $collection, $arguments);
 146    if($is_obj) $result = (object) $result;
 147    
 148    return self::_wrap($result);
 149  }
 150  
 151  
 152  // Does any values in the collection meet the iterator's truth test?
 153  // any alias: some
 154  public function some($collection=null, $iterator=null) { return self::any($collection, $iterator); }
 155  public function any($collection=null, $iterator=null) {
 156    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 157    
 158    $collection = self::_collection($collection);
 159    
 160    $__ = new self;
 161    if(!is_null($iterator)) $collection = $__->map($collection, $iterator);
 162    if(count($collection) === 0) return self::_wrap(false);
 163    
 164    return self::_wrap(is_int(array_search(true, $collection, false)));
 165  }
 166  
 167  
 168  // Do all values in the collection meet the iterator's truth test?
 169  // all alias: every
 170  public function every($collection=null, $iterator=null) { return self::all($collection, $iterator); }
 171  public function all($collection=null, $iterator=null) {
 172    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 173    
 174    $collection = self::_collection($collection);
 175    
 176    $__ = new self;
 177    if(!is_null($iterator)) $collection = $__->map($collection, $iterator);
 178    $collection = (array) $collection;
 179    if(count($collection) === 0) return true;
 180    
 181    return self::_wrap(is_bool(array_search(false, $collection, false)));
 182  }
 183  
 184  
 185  // Return an array of values that pass the truth iterator test
 186  // filter alias: select
 187  public function select($collection=null, $iterator=null) { return self::filter($collection, $iterator); }
 188  public function filter($collection=null, $iterator=null) {
 189    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 190    
 191    $collection = self::_collection($collection);
 192    
 193    $return = array();
 194    foreach($collection as $val) {
 195      if(call_user_func($iterator, $val)) $return[] = $val;
 196    }
 197    return self::_wrap($return);
 198  }
 199  
 200  
 201  // Return an array where the items failing the truth test are removed
 202  public function reject($collection=null, $iterator=null) {
 203    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 204    
 205    $collection = self::_collection($collection);
 206    
 207    $return = array();
 208    foreach($collection as $val) {
 209      if(!call_user_func($iterator, $val)) $return[] = $val;
 210    }
 211    return self::_wrap($return);
 212  }
 213  
 214  
 215  // Return the value of the first item passing the truth iterator test
 216  // find alias: detect
 217  public function detect($collection=null, $iterator=null) { return self::find($collection, $iterator); }
 218  public function find($collection=null, $iterator=null) {
 219    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 220    
 221    $collection = self::_collection($collection);
 222    
 223    foreach($collection as $val) {
 224      if(call_user_func($iterator, $val)) return $val;
 225    }
 226    return self::_wrap(false);
 227  }
 228  
 229  
 230  // How many items are in this collection?
 231  public function size($collection=null) {
 232    list($collection) = self::_wrapArgs(func_get_args(), 1);
 233    
 234    $collection = self::_collection($collection);
 235    
 236    return self::_wrap(count((array) $collection));
 237  }
 238  
 239  
 240  // Get the first element of an array. Passing n returns the first n elements.
 241  // first alias: head
 242  public function head($collection=null, $n=null) { return self::first($collection, $n); }
 243  public function first($collection=null, $n=null) {
 244    list($collection, $n) = self::_wrapArgs(func_get_args(), 2);
 245    
 246    $collection = self::_collection($collection);
 247    
 248    if($n === 0) return self::_wrap(array());
 249    if(is_null($n)) return self::_wrap(current(array_splice($collection, 0, 1, true)));
 250    return self::_wrap(array_splice($collection, 0, $n, true));
 251  }
 252  
 253  
 254  // Get the rest of the array elements. Passing n returns from that index onward.
 255  public function tail($collection=null, $index=null) { return self::rest($collection, $index); }
 256  public function rest($collection=null, $index=null) {
 257    list($collection, $index) = self::_wrapArgs(func_get_args(), 2);
 258    if(is_null($index)) $index = 1;
 259    
 260    $collection = self::_collection($collection);
 261    
 262    return self::_wrap(array_splice($collection, $index));
 263  }
 264  
 265  
 266  // Return everything but the last array element. Passing n excludes the last n elements.
 267  public function initial($collection=null, $n=null) {
 268    list($collection, $n) = self::_wrapArgs(func_get_args(), 2);
 269    
 270    $collection = (array) self::_collection($collection);
 271    
 272    if(is_null($n)) $n = 1;
 273    $first_index = count($collection) - $n;
 274    $__ = new self;
 275    return self::_wrap($__->first($collection, $first_index));
 276  }
 277  
 278  
 279  // Get the last element from an array. Passing n returns the last n elements.
 280  public function last($collection=null, $n=null) {
 281    list($collection, $n) = self::_wrapArgs(func_get_args(), 2);
 282    $collection = self::_collection($collection);
 283    
 284    if($n === 0) $result = array();
 285    elseif($n === 1 || is_null($n)) $result = array_pop($collection);
 286    else {
 287      $__ = new self;
 288      $result = $__->rest($collection, -$n);
 289    }
 290    
 291    return self::_wrap($result);
 292  }
 293  
 294  
 295  // Return a copy of the array with falsy values removed
 296  public function compact($collection=null) {
 297    list($collection) = self::_wrapArgs(func_get_args(), 1);
 298    
 299    $collection = self::_collection($collection);
 300    
 301    $__ = new self;
 302    return self::_wrap($__->select($collection, function($val) {
 303      return (bool) $val;
 304    }));
 305  }
 306  
 307  
 308  // Flattens a multidimensional array
 309  public function flatten($collection=null, $shallow=null) {
 310    list($collection, $shallow) = self::_wrapArgs(func_get_args(), 2);
 311    
 312    $collection = self::_collection($collection);
 313    
 314    $return = array();
 315    if(count($collection) > 0) {
 316      foreach($collection as $item) {
 317        if(is_array($item)) {
 318          $__ = new self;
 319          $return = array_merge($return, ($shallow) ? $item : $__->flatten($item));
 320        }
 321        else $return[] = $item;
 322      }
 323    }
 324    return self::_wrap($return);
 325  }
 326  
 327  
 328  // Returns a copy of the array with all instances of val removed
 329  public function without($collection=null, $val=null) {
 330    $args = self::_wrapArgs(func_get_args(), 1);
 331    $collection = $args[0];
 332    $collection = self::_collection($collection);
 333    
 334    $num_args = count($args);
 335    if($num_args === 1) return self::_wrap($collection);
 336    if(count($collection) === 0) return self::_wrap($collection);
 337    
 338    $__ = new self;
 339    $removes = $__->rest($args);
 340    foreach($removes as $remove) {
 341      $remove_keys = array_keys($collection, $remove, true);
 342      if(count($remove_keys) > 0) {
 343        foreach($remove_keys as $key) {
 344          unset($collection[$key]);
 345        }
 346      }
 347    }
 348    return self::_wrap($collection);
 349  }
 350  
 351  
 352  // Return an array of the unique values
 353  // uniq alias: unique
 354  public function unique($collection=null, $is_sorted=null, $iterator=null) { return self::uniq($collection, $is_sorted, $iterator); }
 355  public function uniq($collection=null, $is_sorted=null, $iterator=null) {
 356    list($collection, $is_sorted, $iterator) = self::_wrapArgs(func_get_args(), 3);
 357    
 358    $collection = self::_collection($collection);
 359    
 360    $return = array();
 361    if(count($collection) === 0) return self::_wrap($return);
 362    
 363    $calculated = array();
 364    foreach($collection as $item) {
 365      $val = (!is_null($iterator)) ? $iterator($item) : $item;
 366      if(is_bool(array_search($val, $calculated, true))) {
 367        $calculated[] = $val;
 368        $return[] = $item;
 369      }
 370    }
 371    
 372    return self::_wrap($return);
 373  }
 374  
 375  
 376  // Returns an array containing the intersection of all the arrays
 377  public function intersection($array=null) {
 378    $arrays = self::_wrapArgs(func_get_args(), 1);
 379    
 380    if(count($arrays) === 1) return self::_wrap($array);
 381    
 382    $__ = new self;
 383    $return = $__->first($arrays);
 384    foreach($__->rest($arrays) as $next) {
 385      if(!$__->isArray($next)) $next = str_split((string) $next);
 386      
 387      $return = array_intersect($return, $next);
 388    }
 389    
 390    return self::_wrap(array_values($return));
 391  }
 392  
 393  
 394  // Merge together multiple arrays
 395  public function union($array=null) {
 396    $arrays = self::_wrapArgs(func_get_args(), 1);
 397    
 398    if(count($arrays) === 1) return self::_wrap($array);
 399    
 400    $__ = new self;
 401    return self::_wrap($__->flatten(array_values(array_unique(call_user_func_array('array_merge', $arrays)))));
 402  }
 403  
 404  
 405  // Get the difference between two arrays
 406  public function difference($array_one=null, $array_two=null) {
 407    $arrays = self::_wrapArgs(func_get_args(), 1);
 408    
 409    return self::_wrap(array_values(call_user_func_array('array_diff', $arrays)));
 410  }
 411  
 412  
 413  // Get the index of the first match
 414  public function indexOf($collection=null, $item=null) {
 415    list($collection, $item) = self::_wrapArgs(func_get_args(), 2);
 416    
 417    $collection = self::_collection($collection);
 418    
 419    $key = array_search($item, $collection, true);
 420    return self::_wrap((is_bool($key)) ? -1 : $key);
 421  }
 422  
 423  
 424  // Get the index of the last match
 425  public function lastIndexOf($collection=null, $item=null) {
 426    list($collection, $item) = self::_wrapArgs(func_get_args(), 2);
 427    
 428    $collection = self::_collection($collection);
 429    
 430    krsort($collection);
 431    $__ = new self;
 432    return self::_wrap($__->indexOf($collection, $item));
 433  }
 434  
 435  
 436  // Returns an array of integers from start to stop (exclusive) by step
 437  public function range($stop=null) {
 438    $args = self::_wrapArgs(func_get_args(), 1);
 439    
 440    $__ = new self;
 441    $args = $__->reject($args, function($val) {
 442      return is_null($val);
 443    });
 444    
 445    $num_args = count($args);
 446    switch($num_args) {
 447      case 1: 
 448        list($start, $stop, $step) = array(0, $args[0], 1);
 449        break;
 450      case 2:
 451        list($start, $stop, $step) = array($args[0], $args[1], 1);
 452        if($stop < $start) return self::_wrap(array());
 453        break;
 454      default:
 455        list($start, $stop, $step) = array($args[0], $args[1], $args[2]);
 456        if($step > 0 && $step > $stop) return self::_wrap(array($start));
 457    }
 458    $results = range($start, $stop, $step);
 459    
 460    // Switch inclusive to exclusive
 461    if($step > 0 && $__->last($results) >= $stop) array_pop($results);
 462    elseif($step < 0 && $__->last($results) <= $stop) array_pop($results);
 463    
 464    return self::_wrap($results);
 465  }
 466  
 467  
 468  // Merges arrays
 469  public function zip($array=null) {
 470    $arrays = self::_wrapArgs(func_get_args());
 471    $num_arrays = count($arrays);
 472    if($num_arrays === 1) return self::_wrap($array);
 473    
 474    $__ = new self;
 475    $num_return_arrays = $__->max($__->map($arrays, function($array) {
 476      return count($array);
 477    }));
 478    $return_arrays = $__->range($num_return_arrays);
 479    foreach($return_arrays as $k=>$v) {
 480      if(!is_array($return_arrays[$k])) $return_arrays[$k] = array();
 481      
 482      foreach($arrays as $a=>$array) {
 483        $return_arrays[$k][$a] = array_key_exists($k, $array) ? $array[$k] : null;
 484      }
 485    }
 486    
 487    return self::_wrap($return_arrays);
 488  }
 489  
 490  
 491  // Get the max value in the collection
 492  public function max($collection=null, $iterator=null) {
 493    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 494    
 495    if(is_null($iterator)) return self::_wrap(max($collection));
 496    
 497    $results = array();
 498    foreach($collection as $k=>$item) {
 499      $results[$k] = $iterator($item);
 500    }
 501    arsort($results);
 502    $__ = new self;
 503    $first_key = $__->first(array_keys($results));
 504    return $collection[$first_key];
 505  }
 506  
 507  
 508  // Get the min value in the collection
 509  public function min($collection=null, $iterator=null) {
 510    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 511    
 512    if(is_null($iterator)) return self::_wrap(min($collection));
 513    
 514    $results = array();
 515    foreach($collection as $k=>$item) {
 516      $results[$k] = $iterator($item);
 517    }
 518    asort($results);
 519    $__ = new self;
 520    $first_key = $__->first(array_keys($results));
 521    return self::_wrap($collection[$first_key]);
 522  }
 523  
 524  
 525  // Sort the collection by return values from the iterator
 526  public function sortBy($collection=null, $iterator=null) {
 527    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 528    
 529    $results = array();
 530    foreach($collection as $k=>$item) {
 531      $results[$k] = $iterator($item);
 532    }
 533    asort($results);
 534    foreach($results as $k=>$v) {
 535      $results[$k] = $collection[$k];
 536    }
 537    return self::_wrap(array_values($results));
 538  }
 539  
 540  
 541  // Group the collection by return values from the iterator
 542  public function groupBy($collection=null, $iterator=null) {
 543    list($collection, $iterator) = self::_wrapArgs(func_get_args(), 2);
 544    
 545    $result = array();
 546    $collection = (array) $collection;
 547    foreach($collection as $k=>$v) {
 548      $key = (is_callable($iterator)) ? $iterator($v, $k) : $v[$iterator];
 549      if(!array_key_exists($key, $result)) $result[$key] = array();
 550      $result[$key][] = $v;
 551    }
 552    return $result;
 553  }
 554  
 555  
 556  // Returns the index at which the value should be inserted into the sorted collection
 557  public function sortedIndex($collection=null, $value=null, $iterator=null) {
 558    list($collection, $value, $iterator) = self::_wrapArgs(func_get_args(), 3);
 559    
 560    $collection = (array) self::_collection($collection);
 561    $__ = new self;
 562    
 563    $calculated_value = (!is_null($iterator)) ? $iterator($value) : $value;
 564    
 565    while(count($collection) > 1) {
 566      $midpoint = floor(count($collection) / 2);
 567      $midpoint_values = array_slice($collection, $midpoint, 1);
 568      $midpoint_value = $midpoint_values[0];
 569      $midpoint_calculated_value = (!is_null($iterator)) ? $iterator($midpoint_value) : $midpoint_value;
 570      
 571      $collection = ($calculated_value < $midpoint_calculated_value) ? array_slice($collection, 0, $midpoint, true) : array_slice($collection, $midpoint, null, true);
 572    }
 573    $keys = array_keys($collection);
 574    
 575    return self::_wrap(current($keys) + 1);
 576  }
 577  
 578  // Shuffle the array
 579  public function shuffle($collection=null) {
 580    list($collection) = self::_wrapArgs(func_get_args(), 1);
 581    
 582    $collection = (array) self::_collection($collection);
 583    shuffle($collection);
 584     
 585    return self::_wrap($collection);
 586  }
 587  
 588  
 589  // Return the collection as an array
 590  public function toArray($collection=null) {
 591    return (array) $collection;
 592  }
 593  
 594  
 595  // Get the collection's keys
 596  public function keys($collection=null) {
 597    list($collection) = self::_wrapArgs(func_get_args(), 1);
 598    
 599    if(!is_object($collection) && !is_array($collection)) throw new Exception('Invalid object');
 600    
 601    return self::_wrap(array_keys((array) $collection));
 602  }
 603  
 604  
 605  // Get the collection's values
 606  public function values($collection=null) {
 607    list($collection) = self::_wrapArgs(func_get_args(), 1);
 608    
 609    return self::_wrap(array_values((array) $collection));
 610  }
 611  
 612  
 613  // Copy all properties from the source objects into the destination object 
 614  public function extend($object=null) {
 615    $args = self::_wrapArgs(func_get_args(), 1);
 616    
 617    $num_args = func_num_args();
 618    if($num_args === 1) return $object;
 619    
 620    $is_object = is_object($object);
 621    $array = (array) $object;
 622    $__ = new self;
 623    $extensions = $__->rest(func_get_args());
 624    foreach($extensions as $extension) {
 625      $extension = (array) $extension;
 626      $array = array_merge($array, $extension);
 627    }
 628    return self::_wrap(($is_object) ? (object) $array : $array);
 629  }
 630  
 631  
 632  // Returns the object with any missing values filled in using the defaults.
 633  public function defaults($object=null) {
 634    $args = self::_wrapArgs(func_get_args(), 1);
 635    list($object) = $args;
 636    
 637    $num_args = count($args);
 638    if($num_args === 1) return $object;
 639    
 640    $is_object = is_object($object);
 641    $array = (array) $object;
 642    $__ = new self;
 643    $extensions = $__->rest($args);
 644    foreach($extensions as $extension) {
 645      $extension = (array) $extension;
 646      $array = array_merge($extension, $array);
 647    }
 648    return self::_wrap(($is_object) ? (object) $array : $array);
 649  }
 650  
 651  
 652  // Get the names of functions available to the object
 653  // functions alias: methods
 654  public function methods($object=null) { return self::functions($object); }
 655  public function functions($object=null) {
 656    list($object) = self::_wrapArgs(func_get_args(), 1);
 657    
 658    return self::_wrap(get_class_methods(get_class($object)));
 659  }
 660  
 661  
 662  // Returns a shallow copy of the object
 663  public function clon(&$object=null) {
 664    list($object) = self::_wrapArgs(func_get_args(), 1);
 665    
 666    $clone = null;
 667    if(is_array($object)) $clone = (array) clone (object) $object;
 668    elseif(!is_object($object)) $clone = $object;
 669    elseif(!$clone) $clone = clone $object;
 670    
 671    // shallow copy object
 672    if(is_object($clone) && count($clone) > 0) {
 673      foreach($clone as $k=>$v) {
 674        if(is_array($v) || is_object($v)) $clone->$k =& $object->$k;
 675      }
 676    }
 677    
 678    // shallow copy array
 679    elseif(is_array($clone) && count($clone) > 0) {
 680      foreach($clone as $k=>$v) {
 681        if(is_array($v) || is_object($v)) $clone[$k] =& $object[$k];
 682      }
 683    }
 684    return self::_wrap($clone);
 685  }
 686  
 687  
 688  // Invokes the interceptor on the object, then returns the object
 689  public function tap($object=null, $interceptor=null) {
 690    list($object, $interceptor) = self::_wrapArgs(func_get_args(), 2);
 691    
 692    $interceptor($object);
 693    return self::_wrap($object);
 694  }
 695  
 696  
 697  // Does the given key exist?
 698  public function has($collection=null, $key=null) {
 699    list($collection, $key) = self::_wrapArgs(func_get_args(), 2);
 700    
 701    $collection = (array) self::_collection($collection);
 702    
 703    return self::_wrap(array_key_exists($key, $collection));
 704  }
 705  
 706  
 707  // Are these items equal?
 708  public function isEqual($a=null, $b=null) {
 709    list($a, $b) = self::_wrapArgs(func_get_args(), 2);
 710    
 711    if(isset($this) && isset($this->_chained) && $this->_chained) $a =& $this;
 712    
 713    if($a === $b) return self::_wrap(true);
 714    if(gettype($a) !== gettype($b)) return self::_wrap(false);
 715    if(is_callable($a) !== is_callable($b)) return self::_wrap(false);
 716    
 717    if($a == $b) return self::_wrap(true);
 718    
 719    // Objects and arrays compared by values
 720    if(is_object($a) || is_array($a)) {
 721      
 722      // Do either implement isEqual()?
 723      if(is_object($a) && isset($a->isEqual)) return self::_wrap($a->isEqual($b));
 724      if(is_object($b) && isset($b->isEqual)) return self::_wrap($b->isEqual($a));
 725      if(is_array($a) && array_key_exists('isEqual', $a)) return self::_wrap($a['isEqual']($b));
 726      if(is_array($b) && array_key_exists('isEqual', $b)) return self::_wrap($b['isEqual']($a));
 727      
 728      if(count($a) !== count($b)) return self::_wrap(false);
 729      
 730      $__ = new self;
 731      $keys_equal = $__->isEqual($__->keys($a), $__->keys($b));
 732      $values_equal = $__->isEqual($__->values($a), $__->values($b));
 733      return self::_wrap($keys_equal && $values_equal);
 734    }
 735    
 736    return self::_wrap(false);
 737  }
 738  
 739  
 740  // Is this item empty?
 741  public function isEmpty($item=null) {
 742    list($item) = self::_wrapArgs(func_get_args(), 1);
 743    
 744    return self::_wrap(is_array($item) || is_object($item)) ? !((bool) count((array) $item)) : (!(bool) $item);
 745  }
 746  
 747  
 748  // Is this item an object?
 749  public function isObject($item=null) {
 750    list($item) = self::_wrapArgs(func_get_args(), 1);
 751    return self::_wrap(is_object($item));
 752  }
 753  
 754  
 755  // Is this item an array?
 756  public function isArray($item=null) {
 757    list($item) = self::_wrapArgs(func_get_args(), 1);
 758    return self::_wrap(is_array($item));
 759  }
 760  
 761  
 762  // Is this item a string?
 763  public function isString($item=null) {
 764    list($item) = self::_wrapArgs(func_get_args(), 1);
 765    return self::_wrap(is_string($item));
 766  }
 767  
 768  
 769  // Is this item a number?
 770  public function isNumber($item=null) {
 771    list($item) = self::_wrapArgs(func_get_args(), 1);
 772    return self::_wrap((is_int($item) || is_float($item)) && !is_nan($item) && !is_infinite($item));
 773  }
 774  
 775  
 776  // Is this item a bool?
 777  public function isBoolean($item=null) {
 778    list($item) = self::_wrapArgs(func_get_args(), 1);
 779    return self::_wrap(is_bool($item));
 780  }
 781  
 782  
 783  // Is this item a function (by type, not by name)?
 784  public function isFunction($item=null) {
 785    list($item) = self::_wrapArgs(func_get_args(), 1);
 786    return self::_wrap(is_object($item) && is_callable($item));
 787  }
 788  
 789  
 790  // Is this item an instance of DateTime?
 791  public function isDate($item=null) {
 792    list($item) = self::_wrapArgs(func_get_args(), 1);
 793    return self::_wrap(is_object($item) && get_class($item) === 'DateTime');
 794  }
 795  
 796  
 797  // Is this item a NaN value?
 798  public function isNaN($item=null) {
 799    list($item) = self::_wrapArgs(func_get_args(), 1);
 800    return self::_wrap(is_nan($item));
 801  }
 802  
 803  
 804  // Returns the same value passed as the argument
 805  public function identity() {
 806    $args = self::_wrapArgs(func_get_args(), 1);
 807    
 808    if(is_array($args)) return self::_wrap($args[0]);
 809    
 810    return self::_wrap(function($x) {
 811      return $x;
 812    });
 813  }
 814  
 815  
 816  // Generate a globally unique id, optionally prefixed
 817  public $_uniqueId = -1;
 818  public function uniqueId($prefix=null) {
 819    list($prefix) = self::_wrapArgs(func_get_args(), 1);
 820    
 821    $_instance = self::getInstance();
 822    $_instance->_uniqueId++;
 823    
 824    return (is_null($prefix)) ? self::_wrap($_instance->_uniqueId) : self::_wrap($prefix . $_instance->_uniqueId);
 825  }
 826  
 827  
 828  // Invokes the iterator n times
 829  public function times($n=null, $iterator=null) {
 830    list($n, $iterator) = self::_wrapArgs(func_get_args(), 2);
 831    if(is_null($n)) $n = 0;
 832    
 833    for($i=0; $i<$n; $i++) $iterator($i);
 834    return self::_wrap(null);
 835  }
 836  
 837  
 838  // Extend the class with your own functions
 839  private $_mixins = array();
 840  public function mixin($functions=null) {
 841    list($functions) = self::_wrapArgs(func_get_args(), 1);
 842    
 843    $mixins =& self::getInstance()->_mixins;
 844    foreach($functions as $name=>$function) {
 845      $mixins[$name] = $function;
 846    }
 847    return self::_wrap(null);
 848  }
 849  
 850  
 851  // Allows extending methods in static context
 852  public static function __callStatic($name, $arguments) {
 853    $mixins =& self::getInstance()->_mixins;
 854    return call_user_func_array($mixins[$name], $arguments);
 855  }
 856  
 857  // Allows extending methods in non-static context
 858  public function __call($name, $arguments) {
 859    $mixins =& self::getInstance()->_mixins;
 860    $arguments = self::_wrapArgs($arguments);
 861    return call_user_func_array($mixins[$name], $arguments);
 862  }
 863  
 864  
 865  // Temporary PHP open and close tags used within templates
 866  // Allows for normal processing of templates even when
 867  // the developer uses PHP open or close tags for interpolation or evaluation
 868  const TEMPLATE_OPEN_TAG = '760e7dab2836853c63805033e514668301fa9c47';
 869  const TEMPLATE_CLOSE_TAG= 'd228a8fa36bd7db108b01eddfb03a30899987a2b';
 870  
 871  const TEMPLATE_DEFAULT_EVALUATE   = '/<%([\s\S]+?)%>/';
 872  const TEMPLATE_DEFAULT_INTERPOLATE= '/<%=([\s\S]+?)%>/';
 873  const TEMPLATE_DEFAULT_ESCAPE     = '/<%-([\s\S]+?)%>/';
 874  public $_template_settings = array(
 875    'evaluate'    => self::TEMPLATE_DEFAULT_EVALUATE,
 876    'interpolate' => self::TEMPLATE_DEFAULT_INTERPOLATE,
 877    'escape'      => self::TEMPLATE_DEFAULT_ESCAPE
 878  );
 879  
 880  // Set template settings
 881  public function templateSettings($settings=null) {
 882    $_template_settings =& self::getInstance()->_template_settings;
 883    
 884    if(is_null($settings)) {
 885      $_template_settings = array(
 886        'evaluate'    => self::TEMPLATE_DEFAULT_EVALUATE,
 887        'interpolate' => self::TEMPLATE_DEFAULT_INTERPOLATE,
 888        'escape'      => self::TEMPLATE_DEFAULT_ESCAPE
 889      );
 890      return true;
 891    }
 892    
 893    foreach($settings as $k=>$v) {
 894      if(!array_key_exists($k, $_template_settings)) continue;
 895      
 896      $_template_settings[$k] = $v;
 897    }
 898    return true;
 899  }
 900  
 901  
 902  // Compile templates into functions that can be evaluated for rendering
 903  public function template($code=null, $context=null) {
 904    list($code, $context) = self::_wrapArgs(func_get_args(), 2);
 905
 906    $class_name = __CLASS__;
 907    
 908    $return = self::_wrap(function($context=null) use ($code, $class_name) {
 909      $ts = $class_name::getInstance()->_template_settings;
 910      
 911      // Wrap escaped, interpolated, and evaluated blocks inside PHP tags
 912      extract((array) $context);
 913      preg_match_all($ts['escape'], $code, $vars, PREG_SET_ORDER);
 914      if(count($vars) > 0) {
 915        foreach($vars as $var) {
 916          $echo = $class_name::TEMPLATE_OPEN_TAG . ' echo htmlentities(' . trim($var[1]) . '); ' . $class_name::TEMPLATE_CLOSE_TAG;
 917          $code = str_replace($var[0], $echo, $code);
 918        }
 919      }
 920      preg_match_all($ts['interpolate'], $code, $vars, PREG_SET_ORDER);
 921      if(count($vars) > 0) {
 922        foreach($vars as $var) {
 923          $echo = $class_name::TEMPLATE_OPEN_TAG . ' echo ' . trim($var[1]) . '; ' . $class_name::TEMPLATE_CLOSE_TAG;
 924          $code = str_replace($var[0], $echo, $code);
 925        }
 926      }
 927      preg_match_all($ts['evaluate'], $code, $vars, PREG_SET_ORDER);
 928      if(count($vars) > 0) {
 929        foreach($vars as $var) {
 930          $echo = $class_name::TEMPLATE_OPEN_TAG . trim($var[1]) . $class_name::TEMPLATE_CLOSE_TAG;
 931          $code = str_replace($var[0], $echo, $code);
 932        }
 933      }
 934      $code = str_replace($class_name::TEMPLATE_OPEN_TAG, '<?php ', $code);
 935      $code = str_replace($class_name::TEMPLATE_CLOSE_TAG, '?>', $code);
 936      
 937      // Use the output buffer to grab the return value
 938      $code = 'ob_start(); extract($context); ?>' . $code . '<?php return ob_get_clean();';
 939      
 940      $func = create_function('$context', $code);
 941      return $func((array) $context);
 942    });
 943    
 944    // Return function or call function depending on context
 945    return self::_wrap(((isset($this) && isset($this->_wrapped) && $this->_wrapped) || !is_null($context)) ? $return($context) : $return);
 946  }
 947  
 948  // Escape
 949  public function escape($item=null) {
 950    list($item) = self::_wrapArgs(func_get_args(), 1);
 951    
 952    return self::_wrap(htmlentities($item));
 953  }
 954  
 955  
 956  // Memoizes a function by caching the computed result.
 957  public $_memoized = array();
 958  public function memoize($function=null, $hashFunction=null) {
 959    list($function, $hashFunction) = self::_wrapArgs(func_get_args(), 2);
 960    
 961    $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
 962    
 963    return self::_wrap(function() use ($function, &$_instance, $hashFunction) {
 964      
 965      // Generate a key based on hashFunction
 966      $args = func_get_args();
 967      if(is_null($hashFunction)) $hashFunction = function($function, $args) {
 968        
 969        // Try using var_export to identify the function
 970        return md5(join('_', array(
 971          var_export($function, true),
 972          var_export($args, true)
 973        )));
 974      };
 975      $key = $hashFunction($function, $args);
 976      
 977      if(!array_key_exists($key, $_instance->_memoized)) {
 978        $_instance->_memoized[$key] = call_user_func_array($function, $args);
 979      }
 980      return $_instance->_memoized[$key];
 981    });
 982  }
 983  
 984  
 985  // Throttles a function so that it can only be called once every wait milliseconds
 986  public $_throttled = array();
 987  public function throttle($function=null, $wait=null) {
 988    list($function, $wait) = self::_wrapArgs(func_get_args(), 2);
 989    
 990    $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
 991    
 992    return self::_wrap(function() use ($function, $wait, &$_instance) {
 993      
 994      // Try using var_export to identify the function
 995      $key = md5(join('', array(
 996        var_export($function, true),
 997        $wait
 998      )));
 999      
1000      $microtime = microtime(true);
1001      $ready_to_call = (!array_key_exists($key, $_instance->_throttled) || $microtime >= $_instance->_throttled[$key]);
1002      if($ready_to_call) {
1003        $next_callable_time = $microtime + ($wait / 1000);
1004        $_instance->_throttled[$key] = $next_callable_time;
1005        return call_user_func_array($function, func_get_args());
1006      }
1007    });
1008  }
1009  
1010  
1011  // Creates a version of the function that can only be called once
1012  public $_onced = array();
1013  public function once($function=null) {
1014    list($function) = self::_wrapArgs(func_get_args(), 1);
1015    
1016    $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
1017    
1018    return self::_wrap(function() use ($function, &$_instance) {
1019      
1020      // Try using var_export to identify the function
1021      $key = md5(var_export($function, true));
1022      if(!array_key_exists($key, $_instance->_onced)) {
1023        $_instance->_onced[$key] = call_user_func_array($function, func_get_args());
1024      }
1025      
1026      return $_instance->_onced[$key];
1027    });
1028  }
1029  
1030  
1031  // Wraps the function inside the wrapper function, passing it as the first argument
1032  public function wrap($function=null, $wrapper=null) {
1033    list($function, $wrapper) = self::_wrapArgs(func_get_args(), 2);
1034    
1035    return self::_wrap(function() use ($wrapper, $function) {
1036      $args = array_merge(array($function), func_get_args());
1037      return call_user_func_array($wrapper, $args);
1038    });
1039  }
1040  
1041  
1042  // Returns the composition of the functions
1043  public function compose() {
1044    $functions = self::_wrapArgs(func_get_args(), 1);
1045    
1046    return self::_wrap(function() use ($functions) {
1047      $args = func_get_args();
1048      foreach($functions as $function) {
1049        $args[0] = call_user_func_array($function, $args);
1050      }
1051      return $args[0];
1052    });
1053  }
1054  
1055  
1056  // Creates a version of the function that will only run after being called count times
1057  public $_aftered = array();
1058  public function after($count=null, $function=null) {
1059    list($count, $function) = self::_wrapArgs(func_get_args(), 2);
1060    
1061    $_instance = (isset($this) && isset($this->_wrapped)) ? $this : self::getInstance();
1062    $key = md5(mt_rand());
1063    
1064    $func = function() use ($function, &$_instance, $count, $key) {
1065      if(!array_key_exists($key, $_instance->_aftered)) $_instance->_aftered[$key] = 0;
1066      $_instance->_aftered[$key] += 1;
1067      
1068      if($_instance->_aftered[$key] >= $count) return call_user_func_array($function, func_get_args());
1069    };
1070    return self::_wrap(($count) ? $func : $func());
1071  }
1072  
1073  
1074  // Singleton
1075  private static $_instance;
1076  public function getInstance() {
1077    if(!isset(self::$_instance)) {
1078      $c = __CLASS__;
1079      self::$_instance = new $c;
1080    }
1081    return self::$_instance;
1082  }
1083  
1084  
1085  // All methods should wrap their returns within _wrap
1086  // because this function understands both OO-style and functional calls
1087  public $_wrapped; // Value passed from one chained method to the next
1088  private function _wrap($val) {
1089    if(isset($this) && isset($this->_chained) && $this->_chained) {
1090      $this->_wrapped = $val;
1091      return $this;
1092    }
1093    return $val;
1094  }
1095  
1096  
1097  // All methods should get their arguments from _wrapArgs
1098  // because this function understands both OO-style and functional calls
1099  private function _wrapArgs($caller_args, $num_args=null) {
1100    $num_args = (is_null($num_args)) ? count($caller_args) - 1 : $num_args;
1101    
1102    $filled_args = array();
1103    if(isset($this) && isset($this->_wrapped)) {
1104      $filled_args[] =& $this->_wrapped;
1105    }
1106    if(count($caller_args) > 0) {
1107      foreach($caller_args as $k=>$v) {
1108        $filled_args[] = $v;
1109      }
1110    }
1111    
1112    return array_pad($filled_args, $num_args, null);
1113  }
1114  
1115  
1116  // Get a collection in a way that supports strings
1117  private function _collection($collection) {
1118    return (!is_array($collection) && !is_object($collection)) ? str_split((string) $collection) : $collection;
1119  }
1120}