PageRenderTime 94ms CodeModel.GetById 3ms app.highlight 83ms RepoModel.GetById 1ms app.codeStats 0ms

/underscore.php

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