adhocore / underscore
PHP underscore inspired &/or cloned from _.js
Fund package maintenance!
paypal.me/ji10
Installs: 5 126
Dependents: 0
Suggesters: 0
Security: 0
Stars: 44
Watchers: 2
Forks: 6
Open Issues: 0
Requires
- php: >=8.0
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-11-06 10:02:55 UTC
README
PHP underscore inspired &/or cloned from awesome _.js
. A set of utilities and data manipulation helpers providing convenience functionalites to deal with array, list, hash, functions and so on in a neat elegant and OOP way. Guaranteed to save you tons of boiler plate codes when churning complex data collection.
- Zero dependency (no vendor bloat).
Installation
Requires PHP5.6 or later.
composer require adhocore/underscore
Usage and API
Although all of them are available with helper function underscore($data)
or new Ahc\Underscore($data)
,
the methods are grouped and organized in different heriarchy and classes according as their scope.
This keeps it maintainable and saves from having a God class.
Contents
- Underscore
- UnderscoreFunction
- UnderscoreArray
- UnderscoreCollection
- UnderscoreBase
- HigherOrderMessage
- ArrayAccess
- Arrayizes
Underscore
constant(mixed $value): callable
Generates a function that always returns a constant value.
$fn = underscore()->constant([1, 2]); $fn(); // [1, 2]
noop(): void
No operation!
underscore()->noop(); // void/null
random(int $min, int $max): int
Return a random integer between min and max (inclusive).
$rand = underscore()->random(1, 10);
times(int $n, callable $fn): self
Run callable n times and create new collection.
$fn = function ($i) { return $i * 2; }; underscore()->times(5, $fn)->get(); // [0, 2, 4, 6, 8]
uniqueId(string $prefix): string
Generate unique ID (unique for current go/session).
$u = underscore()->uniqueId(); // '1' $u1 = underscore()->uniqueId(); // '2' $u3 = underscore()->uniqueId('id:'); // 'id:3'
UnderscoreFunction
compose(callable $fn1, callable $fn2, ...callable|null $fn3): mixed
Returns a function that is the composition of a list of functions, each consuming the return value of the function that follows.
$c = underscore()->compose('strlen', 'strtolower', 'strtoupper'); $c('aBc.xYz'); // ABC.XYZ => abc.xyz => 7
delay(callable $fn, int $wait): mixed
Cache the result of callback for given arguments and reuse that in subsequent call.
$cb = underscore()->delay(function () { echo 'OK'; }, 100); // waits 100ms $cb(); // 'OK'
memoize(callable $fn): mixed
Returns a callable which when invoked caches the result for given arguments and reuses that result in subsequent calls.
$sum = underscore()->memoize(function ($a, $b) { return $a + $b; }); $sum(4, 5); // 9 // Uses memo: $sum(4, 5); // 9
throttle(callable $fn, int $wait): mixed
Returns a callable that wraps given callable which can be only invoked at most once per given $wait threshold.
$fn = underscore()->throttle($callback, 100); while (...) { $fn(); // it will be constantly called but not executed more than one in 100ms if (...) break; }
UnderscoreArray
compact(): self
Get only the truthy items.
underscore($array)->compact()->get(); // [1 => 'a', 4 => 2, 5 => [1]
difference(array|mixed $data): self
Get the items whose value is not in given data.
underscore([1, 2, 1, 'a' => 3, 'b' => [4]])->difference([1, [4]])->get(); // [1 => 2, 'a' => 3]
findIndex(callable $fn): mixed|null
Find the first index that passes given truth test.
$u = underscore([[1, 2], 'a' => 3, 'x' => 4, 'y' => 2, 'b' => 'B']); $isEven = function ($i) { return is_numeric($i) && $i % 2 === 0; }; $u->findIndex(); // 0 $u->findIndex($isEven); // 'x'
findLastIndex(callable $fn): mixed|null
Find the last index that passes given truth test.
$u = underscore([[1, 2], 'a' => 3, 'x' => 4, 'y' => 2, 'b' => 'B']); $isEven = function ($i) { return is_numeric($i) && $i % 2 === 0; }; $u->findLastIndex(); // 'b' $u->findLastIndex($isEven); // 'y'
first(int $n): array|mixed
Get the first n items.
underscore([1, 2, 3])->first(); // 1 underscore([1, 2, 3])->first(2); // [1, 2]
flatten(): self
Gets the flattened version of multidimensional items.
$u = underscore([0, 'a', '', [[1, [2]]], 'b', [[[3]], 4, 'c', underscore([5, 'd'])]]); $u->flatten()->get(); // [0, 'a', '', 1, 2, 'b', 3, 4, 'c', 5, 'd']
indexOf(mixed $value): string|int|null
Find the first index of given value if available null otherwise.
$u = underscore([[1, 2], 'a' => 2, 'x' => 4]); $array->indexOf(2); // 'a'
intersection(array|mixed $data): self
Gets the items whose value is common with given data.
$u = underscore([1, 2, 'a' => 3]); $u->intersection([2, 'a' => 3, 3])->get(); // [1 => 2, 'a' => 3]
last(int $n): array|mixed
Get the last n items.
underscore([1, 2, 3])->last(); // 3 underscore([1, 2, 3])->last(2); // [2, 3]
lastIndexOf(mixed $value): string|int|null
Find the last index of given value if available null otherwise.
$u = underscore([[1, 2], 'a' => 2, 'x' => 4, 'y' => 2]); $array->lastIndexOf(2); // 'y'
object(string|null $className): self
Hydrate the items into given class or stdClass.
underscore(['a', 'b' => 2])->object(); // stdClass(0: 'a', 'b': 2)
range(int $start, int $stop, int $step): self
Creates a new range from start to stop with given step.
underscore()->range(4, 9)->get(); // [4, 5, 6, 7, 8, 9]
sortedIndex(mixed $object, callable|string $fn): string|int|null
Gets the smallest index at which an object should be inserted so as to maintain order.
underscore([1, 3, 5, 8, 11])->sortedIndex(9, null); // 4
union(array|mixed $data): self
Get the union/merger of items with given data.
$u = underscore([1, 2, 'a' => 3]); $u->union([3, 'a' => 4, 'b' => [5]])->get(); // [1, 2, 'a' => 4, 3, 'b' => [5]]
unique(callable|string $fn): self
Gets the unique items using the id resulted from callback.
$u = underscore([1, 2, 'a' => 3]); $u->union([3, 'a' => 4, 'b' => [5]])->get(); // [1, 2, 'a' => 4, 3, 'b' => [5]]
zip(array|mixed $data): self
Group the values from data and items having same indexes together.
$u = underscore([1, 2, 'a' => 3, 'b' => 'B']); $u->zip([2, 4, 'a' => 5])->get(); // [[1, 2], [2, 4], 'a' => [3, 5], 'b' => ['B', null]]
UnderscoreCollection
contains(mixed $item): bool
Check if the collection contains given item.
$u = underscore(['a' => 1, 'b' => 2, 'c' => 3, 5]); $u->contains(1); // true $u->contains('x'); // false
countBy(callable|string $fn): self
Count items in each group indexed by the result of callback.
$u = underscore([ ['a' => 0, 'b' => 1, 'c' => 1], ['a' => true, 'b' => false, 'c' => 'c'], ['a' => 2, 'b' => 1, 'c' => 2], ['a' => 1, 'b' => null, 'c' => 0], ]); // by key 'a' $u->countBy('a')->get(); // [0 => 1, 1 => 2, 2 => 1]
each(callable $fn): self
Apply given callback to each of the items in collection.
$answers = []; underscore([1, 2, 3])->each(function ($num) use (&$answers) { $answers[] = $num * 5; }); $answers; // [5, 10, 15]
every(callable $fn): bool
Tests if all the items pass given truth test.
$gt0 = underscore([1, 2, 3, 4])->every(function ($num) { return $num > 0; }); $gt0; // true
filter(callable|string|null $fn): self
Find and return all the items that passes given truth test.
$gt2 = underscore([1, 2, 4, 0, 3])->filter(function ($num) { return $num > 2; }); $gt2->values(); // [4, 3]
find(callable $fn, bool $useValue): mixed|null
Find the first item (or index) that passes given truth test.
$num = underscore([1, 2, 4, 3])->find(function ($num) { return $num > 2; }); $num; // 4 $idx = underscore([1, 2, 4, 3])->find(function ($num) { return $num > 2; }, false); $idx; // 2
findWhere(array $props): mixed
Get the first item that contains all the given props (matching both index and value).
$u = underscore([['a' => 1, 'b' => 2], ['a' => 2, 'b' => 2], ['a' => 1, 'b' => 3]]); $u->findWhere(['b' => 3]); // ['a' => 1, 'b' => 3]
groupBy(callable|string $fn): self
Group items by using the result of callback as index. The items in group will have original index intact.
$u = underscore([ ['a' => 0, 'b' => 1, 'c' => 1], ['a' => true, 'b' => false, 'c' => 'c'], ['a' => 2, 'b' => 1, 'c' => 2], ['a' => 1, 'b' => null, 'c' => 0], ]); // by key 'a' $u->groupBy('a')->get(); // [ // 0 => [0 => ['a' => 0, 'b' => 1, 'c' => 1]], // 1 => [1 => ['a' => true, 'b' => false, 'c' => 'c'], 3 => ['a' => 1, 'b' => null, 'c' => 0]], // 2 => [2 => ['a' => 2, 'b' => 1, 'c' => 2]], // ]
indexBy(callable|string $fn): self
Reindex items by using the result of callback as new index.
$u = underscore([ ['a' => 0, 'b' => 1, 'c' => 1], ['a' => true, 'b' => false, 'c' => 'c'], ['a' => 2, 'b' => 1, 'c' => 2], ['a' => 1, 'b' => null, 'c' => 0], ]); // by key 'a' $u->indexBy('a')->get(); // [ // 0 => ['a' => 0, 'b' => 1, 'c' => 1], // 1 => ['a' => 1, 'b' => null, 'c' => 0], // 2 => ['a' => 2, 'b' => 1, 'c' => 2], // ]
invoke(callable $fn): mixed
Invoke a callback using all of the items as arguments.
$sum = underscore([1, 2, 4])->invoke(function () { return array_sum(func_get_args()); }); $sum; // 7
map(callable $fn): self
Update the value of each items with the result of given callback.
$map = underscore([1, 2, 3])->map(function ($num) { return $num * 2; }); $map->get(); // [2, 4, 6]
max(callable|string|null $fn): mixed
Find the maximum value using given callback or just items.
underscore([1, 5, 4])->max(); // 5 $u = underscore([['a' => 1, 'b' => 2], ['a' => 2, 'b' => 3], ['a' => 0, 'b' => 1]]); $u->max(function ($i) { return $i['a'] + $i['b']; }); // 5
min(callable|string|null $fn): mixed
Find the minimum value using given callback or just items.
underscore([1, 5, 4])->min(); // 1 $u = underscore([['a' => 1, 'b' => 2], ['a' => 2, 'b' => 3], ['a' => 0, 'b' => 1]]); $u->min(function ($i) { return $i['a'] + $i['b']; }); // 1
partition(callable|string $fn): self
Separate the items into two groups: one passing given truth test and other failing.
$u = underscore(range(1, 10)); $oddEvn = $u->partition(function ($i) { return $i % 2; }); $oddEvn->get(0); // [1, 3, 5, 7, 9] $oddEvn->get(1); // [2, 4, 6, 8, 10]
pluck(string|int $columnKey, string|int $indexKey): self
Pluck given property from each of the items.
$u = underscore([['name' => 'moe', 'age' => 30], ['name' => 'curly']]); $u->pluck('name')->get(); // ['moe', 'curly']
reduce(callable $fn, mixed $memo): mixed
Iteratively reduce the array to a single value using a callback function.
$sum = underscore([1, 2, 3])->reduce(function ($sum, $num) { return $num + $sum; }, 0); $sum; // 6
reduceRight(callable $fn, mixed $memo): mixed
Same as reduce but applies the callback from right most item first.
$concat = underscore([1, 2, 3, 4])->reduceRight(function ($concat, $num) { return $concat . $num; }, ''); echo $concat; // '4321'
reject(callable $fn): self
Find and return all the items that fails given truth test.
$evens = underscore([1, 2, 3, 4, 5, 7, 6])->reject(function ($num) { return $num % 2 !== 0; }); $evens->values(); // [2, 4, 6]
sample(int $n): self
Get upto n items in random order.
$u = underscore([1, 2, 3, 4]); $u->sample(1)->count(); // 1 $u->sample(2)->count(); // 2
shuffle(): self
Randomize the items keeping the indexes intact.
underscore([1, 2, 3, 4])->shuffle()->get();
some(callable $fn): bool
Tests if some (at least one) of the items pass given truth test.
$some = underscore([1, 2, 0, 4, -1])->some(function ($num) { return $num > 0; }); $some; // true
sortBy(callable $fn): self
Sort items by given callback and maintain indexes.
$u = underscore(range(1, 15))->shuffle(); // randomize $u->sortBy(null)->get(); // [1, 2, ... 15] $u = underscore([['a' => 1, 'b' => 2], ['a' => 2, 'b' => 3], ['a' => 0, 'b' => 1]]); $u->sortBy('a')->get(); // [2 => ['a' => 0, 'b' => 1], 0 => ['a' => 1, 'b' => 2], 1 => ['a' => 2, 'b' => 3]] $u->sortBy(function ($i) { return $i['a'] + $i['b']; })->get(); // [2 => ['a' => 0, 'b' => 1], 0 => ['a' => 1, 'b' => 2], 1 => ['a' => 2, 'b' => 3]],
where(array $props): self
Filter only the items that contain all the given props (matching both index and value).
$u = underscore([['a' => 1, 'b' => 2], ['a' => 2, 'b' => 2], ['a' => 1, 'b' => 3, 'c']]); $u->where(['a' => 1, 'b' => 2])->get(); // [['a' => 1, 'b' => 2, 'c']]
UnderscoreBase
_
(array|mixed $data): self
A static shortcut to constructor.
$u = Ahc\Underscore\Underscore::_([1, 3, 7]);
__
toString(): string
Stringify the underscore instance as json string.
echo (string) underscore([1, 2, 3]); // [1, 2, 3] echo (string) underscore(['a', 2, 'c' => 3]); // {0: "a", 1: 2, "c":3}
asArray(mixed $data, bool $cast): array
Get data as array.
underscore()->asArray('one'); // ['one'] underscore()->asArray([1, 2]); // [1, 2] underscore()->asArray(underscore(['a', 1, 'c', 3])); // ['a', 1, 'c', 3] underscore()->asArray(new class { public function toArray() { return ['a', 'b', 'c']; } }); // ['a', 'b', 'c'] underscore()->asArray(new class implements \JsonSerializable { public function jsonSerialize() { return ['a' => 1, 'b' => 2, 'c' => 3]; } }); // ['a' => 1, 'b' => 2, 'c' => 3]
clon(): self
Creates a shallow copy of itself.
$u = underscore(['will', 'be', 'cloned']); $u->clon() == $u; // true $u->clon() === $u; // false
count(): int
Gets the count of items.
underscore([1, 2, [3, 4]])->count(); // 3 underscore()->count(); // 0
flat(array $array): array
Flatten a multi dimension array to 1 dimension.
underscore()->flat([1, 2, [3, 4, [5, 6]]]); // [1, 2, 3, 4, 5, 6]
get(string|int|null $index): mixed
Get the underlying array data by index.
$u = underscore([1, 2, 3]); $u->get(); // [1, 2, 3] $u->get(1); // 2 $u->get(3); // 3
getData(): array
Get data.
// same as `get()` without args: underscore([1, 2, 3])->getData(); // [1, 2, 3]
getIterator(): \ArrayIterator
Gets the iterator for looping.
$it = underscore([1, 2, 3])->getIterator(); while ($it->valid()) { echo $it->current(); }
invert(): self
Swap index and value of all the items. The values should be stringifiable.
$u = underscore(['a' => 1, 'b' => 2, 'c' => 3]); $u->invert()->get(); // [1 => 'a', 2 => 'b', 3 => 'c']
jsonSerialize(): array
Gets the data for json serialization.
$u = underscore(['a' => 1, 'b' => 2, 'c' => 3]); json_encode($u); // {"a":1,"b":2,"c":3}
keys(): self
Get all the keys.
$u = underscore(['a' => 1, 'b' => 2, 'c' => 3, 5]); $u->keys()->get(); // ['a', 'b', 'c', 0]
mixin(string $name, \Closure $fn): self
Adds a custom handler/method to instance. The handler is bound to this instance.
Ahc\Underscore\Underscore::mixin('square', function () { return $this->map(function ($v) { return $v * $v; }); }); underscore([1, 2, 3])->square()->get(); // [1, 4, 9]
now(): float
The current time in millisec.
underscore()->now(); // 1529996371081
omit(array|...string|...int $index): self
Omit the items having one of the blacklisted indexes.
$u = underscore(['a' => 3, 7, 'b' => 'B', 1 => ['c', 5]]); $u->omit('a', 0)->get(); // ['b' => 'B', 1 => ['c', 5]]
pairs(): self
Pair all items to use an array of index and value.
$u = ['a' => 3, 7, 'b' => 'B']; $u->pair(); // ['a' => ['a', 3], 0 => [0, 7], 'b' => ['b', 'B']
pick(array|...string|...int $index): self
Pick only the items having one of the whitelisted indexes.
$u = underscore(['a' => 3, 7, 'b' => 'B', 1 => ['c', 5]]); $u->pick(0, 1)->get(); // [7, 1 => ['c', 5]]
tap(callable $fn): self
Invokes callback fn with clone and returns original self.
$u = underscore([1, 2]); $tap = $u->tap(function ($_) { return $_->values(); }); $tap === $u; // true
toArray(): array
Convert the data items to array.
$u = underscore([1, 3, 5, 7]); $u->toArray(); // [1, 3, 5, 7]
valueOf(): string
Get string value (JSON representation) of this instance.
underscore(['a', 2, 'c' => 3])->valueOf(); // {0: "a", 1: 2, "c":3}
values(): self
Get all the values.
$u = underscore(['a' => 1, 'b' => 2, 'c' => 3, 5]); $u->values()->get(); // [1, 2, 3, 5]
UnderscoreAliases
collect(callable $fn): self
Alias of map().
detect(callable $fn, bool $useValue): mixed|null
Alias of find().
drop(int $n): array|mixed
Alias of last().
foldl(callable $fn, mixed $memo): mixed
Alias of reduce().
foldr(callable $fn, mixed $memo): mixed
Alias of reduceRight().
head(int $n): array|mixed
Alias of first().
includes(): void
Alias of contains().
inject(callable $fn, mixed $memo): mixed
Alias of reduce().
select(callable|string|null $fn): self
Alias of filter().
size(): int
Alias of count().
tail(int $n): array|mixed
Alias of last().
take(int $n): array|mixed
Alias of first().
uniq(callable|string $fn): self
Alias of unique().
without(array|mixed $data): self
Alias of difference().
HigherOrderMessage
A syntatic sugar to use elegant shorthand oneliner for complex logic often wrapped in closures. See example below:
// Higher Order Messaging class HOM { protected $n; public $square; public function __construct($n) { $this->n = $n; $this->square = $n * $n; } public function even() { return $this->n % 2 === 0; } } $u = [new HOM(1), new HOM(2), new HOM(3), new HOM(4)]; // Filter `even()` items $evens = $u->filter->even(); // 'even()' method of each items! // Map each evens to their squares $squares = $evens->map->square; // 'square' prop of each items! // Gives an Underscore instance // Get the data $squares->get(); // [1 => 4, 3 => 16]
Without higher order messaging that would look like:
$evens = $u->filter(function ($it) { return $it->even(); }); $squares = $evens->map(function ($it) { return $it->square; });
\ArrayAccess
Underscore instances can be treated as array:
$u = underscore([1, 2, 'a' => 3]); isset($u['a']); // true isset($u['b']); // false echo $u[1]; // 2 $u['b'] = 'B'; isset($u['b']); // true unset($u[1]);
Arrayizes
You can use this trait to arrayize all complex data.
use Ahc\Underscore\Arrayizes; class Any { use Arrayizes; public function name() { $this->asArray($data); } }
License
MIT | © 2017-2018 | Jitendra Adhikari