diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml new file mode 100644 index 0000000..a269994 --- /dev/null +++ b/.github/workflows/cs.yml @@ -0,0 +1,37 @@ +on: + pull_request: + +name: Coding Standards + +jobs: + phpstan: + name: PHP CS + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + tools: composer + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist + + - name: PHP-CS-Fixer + run: vendor/bin/php-cs-fixer fix --dry-run -v diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..31f3146 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,37 @@ +on: + pull_request: + +name: Static Analysis + +jobs: + phpstan: + name: PHPStan + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + tools: composer + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist + + - name: PHPStan + run: vendor/bin/phpstan diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..1e78057 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,90 @@ +name: Unit Tests + +on: + push: + branches: + - master + pull_request: ~ + +jobs: + unit-test: + name: Unit ( PHP ${{ matrix.php }} ) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: 8.0 + - php: 8.1 + - php: 8.2 + - php: 8.3 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: opcache + tools: composer + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json composer.lock') }} + restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer- + + - name: Install dependencies + run: composer update + + - name: Run unit tests + run: vendor/bin/phpunit + + lowest: + name: Unit ( PHP ${{ matrix.php }} + Lowest ) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - php: 7.2 + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: :xdebug + tools: composer + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json composer.lock') }} + restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer- + + - name: Install dependencies + run: composer update --prefer-lowest --prefer-stable + + - name: Run unit tests + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 3a9875b..ff6836c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ /vendor/ -composer.lock +/composer.lock +/vendor-bin/**/vendor +/.php_cs.cache +/src/compiled.php +.idea +.phpunit.result.cache diff --git a/.php-version b/.php-version new file mode 100644 index 0000000..5904f7a --- /dev/null +++ b/.php-version @@ -0,0 +1 @@ +7.2 diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..0208b81 --- /dev/null +++ b/.php_cs @@ -0,0 +1,18 @@ +in('src') + ->in('tests') + ->notName('compiled.php'); + +return PhpCsFixer\Config::create() + ->setRules( + [ + '@PSR1' => true, + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'single_import_per_statement' => false, + ] + ) + ->setFinder($finder) +; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 14ba5f4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: php - -sudo: false - -env: - - COMPOSER_OPTIONS=" - -php: - - 7.1 - -matrix: - fast_finish: true - include: - - php: 7.1 - env: - - COMPOSER_OPTIONS="--prefer-lowest" - - php: 7.1 - env: - - COMPOSER_OPTIONS="--prefer-stable" - - php: 7.2 - - php: nightly - allow_failures: - - php: nightly - -cache: - directories: - - $HOME/.composer/cache/files - -before_install: - - composer self-update - -install: - - composer update -n "$COMPOSER_OPTIONS" - -script: - - vendor/bin/phpunit diff --git a/README.md b/README.md index d048312..3d9cc8a 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,16 @@ Lodash-PHP is a port of the [Lodash JS library](https://lodash.com/) to PHP. It Lodash-PHP tries to mimick lodash.js as close as possible +# Requirements + +Lodash-PHP requires minimum PHP 7.2+, but the latest version of PHP is always recommended. + # Installation Install Lodash-PHP through composer: ```bash -$ composer require solidworx/lodash-php +$ composer require lodash-php/lodash-php ``` # Usage @@ -40,8 +44,12 @@ _::each([1, 2, 3], function (int $item) { - [Array](#array) - [Collection](#collection) - [Date](#date) +- [Function](#function) - [Lang](#lang) +- [Math](#math) - [Number](#number) +- [Object](#object) +- [Seq](#seq) - [String](#string) - [Util](#util) @@ -50,10 +58,12 @@ _::each([1, 2, 3], function (int $item) { ### chunk Creates an array of elements split into groups the length of `size`. - If `array` can't be split evenly, the final chunk will be the remaining elements. + + + **Arguments:** @param array $array array The array to process. @@ -85,6 +95,7 @@ Creates an array with all falsey values removed. The values `false`, `null`, + **Arguments:** @param array $array The array to compact. @@ -111,11 +122,13 @@ and/or values. + + **Arguments:** @param array $array The array to concatenate. -@param mixed $values The values to concatenate. +@param array $values The values to concatenate. @@ -131,10 +144,10 @@ Example: $array = [1]; $other = concat($array, 2, [3], [[4]]); -var_dump(other) +var_dump($other) // => [1, 2, 3, [4]] -var_dump(array) +var_dump($array) // => [1] ``` @@ -147,11 +160,15 @@ determined by the first array. **Note:** Unlike `pullAll`, this method returns a new array. + + + + **Arguments:** @param array $array The array to inspect. -@param array $values The values to exclude. +@param array ...$values The values to exclude. @@ -178,13 +195,17 @@ determined by the first array. The iteratee is invoked with one argument: **Note:** Unlike `pullAllBy`, this method returns a new array. + + + + **Arguments:** @param array $array The array to inspect. -@param array $values The values to exclude. +@param array ...$values The values to exclude. -@param callable $ iteratee The iteratee invoked per element. +@param callable $iteratee The iteratee invoked per element. @@ -196,8 +217,10 @@ Example: ```php [1.2] + ``` ### differenceWith @@ -208,11 +231,16 @@ is invoked with two arguments: (arrVal, othVal). **Note:** Unlike `pullAllWith`, this method returns a new array. + + + + + **Arguments:** -@param array $array The array to inspect. +@param array $array The array to inspect. -@param array[] $values The values to exclude. +@param array ...$values The values to exclude. @param callable $comparator The comparator invoked per element. @@ -239,6 +267,9 @@ Creates a slice of `array` with `n` elements dropped from the beginning. **NOTE:** This function will reorder and reset the array indices + + + **Arguments:** @param array $array The array to query. @@ -272,9 +303,11 @@ drop([1, 2, 3], 0) ### dropRight Creates a slice of `array` with `n` elements dropped from the end. - **NOTE:** This function will reorder and reset the array indices + + + **Arguments:** @param array $array The array to query. @@ -308,10 +341,12 @@ dropRight([1, 2, 3], 0) ### dropRightWhile Creates a slice of `array` excluding elements dropped from the end. - Elements are dropped until `predicate` returns falsey. The predicate is invoked with three arguments: (value, index, array). + + + **Arguments:** @param array $array The array to query. @@ -330,9 +365,9 @@ Example: use function _\dropRightWhile; $users = [ - [ 'user' => 'barney', 'active' => false ], - [ 'user' => 'fred', 'active' => true ], - [ 'user' => 'pebbles', 'active' => true ] +[ 'user' => 'barney', 'active' => false ], +[ 'user' => 'fred', 'active' => true ], +[ 'user' => 'pebbles', 'active' => true ] ] dropRightWhile($users, function($user) { return $user['active']; }) @@ -342,10 +377,12 @@ dropRightWhile($users, function($user) { return $user['active']; }) ### dropWhile Creates a slice of `array` excluding elements dropped from the beginning. - Elements are dropped until `predicate` returns falsey. The predicate is invoked with three arguments: (value, index, array). + + + **Arguments:** @param array $array The array to query. @@ -364,14 +401,67 @@ Example: use function _\dropWhile; $users = [ - [ 'user' => 'barney', 'active' => true ], - [ 'user' => 'fred', 'active' => true ], - [ 'user' => 'pebbles', 'active' => false ] +[ 'user' => 'barney', 'active' => true ], +[ 'user' => 'fred', 'active' => true ], +[ 'user' => 'pebbles', 'active' => false ] ] dropWhile($users, function($user) { return $user['active']; } ) // => objects for ['pebbles'] +``` +### every + +Checks if `predicate` returns truthy for **all** elements of `array`. +Iteration is stopped once `predicate` returns falsey. The predicate is +invoked with three arguments: (value, index, array). + +**Note:** This method returns `true` for +[empty arrays](https://en.wikipedia.org/wiki/Empty_set) because +[everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of +elements of empty arrays. + + + + +**Arguments:** + +@param iterable $collection The array to iterate over. + +@param callable $predicate The function invoked per iteration. + + + +**Return:** + +@return bool `true` if all elements pass the predicate check, else `false`. + +Example: +```php + false + +$users = [ +['user' => 'barney', 'age' => 36, 'active' => false], +['user' => 'fred', 'age' => 40, 'active' => false], +]; + +// The `matches` iteratee shorthand. +$this->assertFalse(every($users, ['user' => 'barney', 'active' => false])); +// false + +// The `matchesProperty` iteratee shorthand. +$this->assertTrue(every($users, ['active', false])); +// true + +// The `property` iteratee shorthand. +$this->assertFalse(every($users, 'active')); +//false + + ``` ### findIndex @@ -379,6 +469,7 @@ This method is like `find` except that it returns the index of the first element + **Arguments:** @param array $array The array to inspect. @@ -399,9 +490,9 @@ Example: use function _\findIndex; $users = [ - ['user' => 'barney', 'active' => false], - ['user' => 'fred', 'active' => false], - ['user' => 'pebbles', 'active' => true], +['user' => 'barney', 'active' => false], +['user' => 'fred', 'active' => false], +['user' => 'pebbles', 'active' => true], ]; findIndex($users, function($o) { return $o['user'] s== 'barney'; }); @@ -427,6 +518,7 @@ of `collection` from right to left. + **Arguments:** @param array $array The array to inspect. @@ -447,9 +539,9 @@ Example: use function _\findLastIndex; $users = [ - ['user' => 'barney', 'active' => true ], - ['user' => 'fred', 'active' => false ], - ['user' => 'pebbles', 'active' => false ] +['user' => 'barney', 'active' => true ], +['user' => 'fred', 'active' => false ], +['user' => 'pebbles', 'active' => false ] ] findLastIndex($users, function($user) { return $user['user'] === 'pebbles'; }) @@ -462,6 +554,7 @@ Flattens `array` a single level deep. + **Arguments:** @param array $array The array to flatten. @@ -476,8 +569,10 @@ Example: ```php [1, 2, [3, [4]], 5] + ``` ### flattenDeep @@ -485,6 +580,7 @@ Recursively flattens `array`. + **Arguments:** @param array $array The array to flatten. @@ -510,6 +606,7 @@ Recursively flatten `array` up to `depth` times. + **Arguments:** @param array $array The array to flatten. @@ -543,6 +640,8 @@ from key-value `pairs`. + + **Arguments:** @param array $pairs The key-value pairs. @@ -551,7 +650,7 @@ from key-value `pairs`. **Return:** -@return object the new object. +@return \stdClass the new object. Example: ```php @@ -571,6 +670,8 @@ Gets the first element of `array`. + + **Arguments:** @param array $array The array to query. @@ -602,6 +703,7 @@ offset from the end of `array`. + **Arguments:** @param array $array The array to inspect. @@ -635,6 +737,7 @@ Gets all but the last element of `array`. + **Arguments:** @param array $array The array to query. @@ -649,8 +752,10 @@ Example: ```php [1, 2] + ``` ### intersection @@ -661,9 +766,11 @@ determined by the first array. + + **Arguments:** -@param array[] $arrays +@param array ...$arrays @@ -675,8 +782,10 @@ Example: ```php [2] + ``` ### intersectionBy @@ -688,9 +797,10 @@ determined by the first array. The iteratee is invoked with one argument: + **Arguments:** -@param array[] $arrays +@param array ...$arrays @param callable $iteratee The iteratee invoked per element. @@ -722,9 +832,11 @@ invoked with two arguments: (arrVal, othVal). + + **Arguments:** -@param array[] $arrays +@param array ...$arrays @param callable $comparator The comparator invoked per element. @@ -752,6 +864,7 @@ Gets the last element of `array`. + **Arguments:** @param array $array The array to query. @@ -778,6 +891,7 @@ This method is like `indexOf` except that it iterates over elements of + **Arguments:** @param array $array The array to inspect. @@ -812,6 +926,7 @@ element from the end is returned. + **Arguments:** @param array $array The array to query. @@ -828,6 +943,7 @@ Example: ```php 'c' + ``` ### pull @@ -845,11 +962,15 @@ for equality comparisons. **Note:** Unlike `without`, this method mutates `array`. Use `remove` to remove elements from an array by predicate. + + + + **Arguments:** @param array $array The array to modify. -@param array $values The values to remove. +@param array $values The values to remove. @@ -875,6 +996,9 @@ This method is like `pull` except that it accepts an array of values to remove. **Note:** Unlike `difference`, this method mutates `array`. + + + **Arguments:** @param array $array The array to modify. @@ -907,6 +1031,9 @@ by which they're compared. The iteratee is invoked with one argument: (value). **Note:** Unlike `differenceBy`, this method mutates `array`. + + + **Arguments:** @param array $array The array to modify. @@ -925,11 +1052,13 @@ Example: ```php 1 ], [ 'x' => 2 ], [ 'x' => 3 ], [ 'x' => 1 ]] pullAllBy($array, [[ 'x' => 1 ], [ 'x' => 3 ]], 'x') var_dump($array) // => [[ 'x' => 2 ]] + ``` ### pullAllWith @@ -939,6 +1068,9 @@ invoked with two arguments: (arrVal, othVal). **Note:** Unlike `differenceWith`, this method mutates `array`. + + + **Arguments:** @param array $array The array to modify. @@ -972,11 +1104,14 @@ array of removed elements. **Note:** Unlike `at`, this method mutates `array`. + + + **Arguments:** @param array $array The array to modify. -@param int|int[] $indexes The indexes of elements to remove. +@param (int | int[]) $indexes The indexes of elements to remove. @@ -1008,6 +1143,9 @@ with three arguments: (value, index, array). **Note:** Unlike `filter`, this method mutates `array`. Use `pull` to pull elements from an array by value. + + + **Arguments:** @param array $array The array to modify. @@ -1024,6 +1162,7 @@ Example: ```php [2, 4] + ``` -### slice +### sample -Creates a slice of `array` from `start` up to, but not including, `end`. +Gets a random element from `array`. -**Arguments:** -@param array $array The array to slice. -@param int $start The start position. +**Arguments:** -@param int $end The end position. +@param array $array The array to sample. **Return:** -@return array the slice of `array`. +@return mixed Returns the random element. -## Collection +Example: +```php + 2 + +``` +### sampleSize + +Gets `n` random elements at unique keys from `array` up to the +size of `array`. -Iterates over elements of `collection` and invokes `iteratee` for each element. -The iteratee is invoked with three arguments: (value, index|key, collection). -Iteratee functions may exit iteration early by explicitly returning `false`. -**Note:** As with other "Collections" methods, objects with a "length" -property are iterated like arrays. To avoid this behavior use `forIn` -or `forOwn` for object iteration. **Arguments:** -@param array|object $collection The collection to iterate over. +@param array $array The array to sample. -@param callable $iteratee The function invoked per iteration. +@param int $n The number of elements to sample. **Return:** -@return array|object Returns `collection`. +@return array the random elements. Example: ```php Echoes `1` then `2`. +sampleSize([1, 2, 3], 2) +// => [3, 1] -each((object) ['a' => 1, 'b' => 2], function ($value, $key) { echo $key; }); -// => Echoes 'a' then 'b' (iteration order is not guaranteed). +sampleSize([1, 2, 3], 4) +// => [2, 3, 1] ``` -### map +### shuffle -Creates an array of values by running each element in `collection` through -`iteratee`. The iteratee is invoked with three arguments: -(value, index|key, collection). +Creates an array of shuffled values -Many lodash-php methods are guarded to work as iteratees for methods like -`_::every`, `_::filter`, `_::map`, `_::mapValues`, `_::reject`, and `_::some`. -The guarded methods are: -`ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, -`fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, -`sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, -`template`, `trim`, `trimEnd`, `trimStart`, and `words` -**Arguments:** -@param array|object $collection The collection to iterate over. +**Arguments:** -@param callable|string|array $iteratee The function invoked per iteration. +@param array $array The array to shuffle. **Return:** -@return array Returns the new mapped array. +@return array the new shuffled array. Example: ```php [4, 1, 3, 2] -map([4, 8], $square); -// => [16, 64] +``` +### slice -map((object) ['a' => 4, 'b' => 8], $square); -// => [16, 64] (iteration order is not guaranteed) +Creates a slice of `array` from `start` up to, but not including, `end`. -$users = [ - [ 'user' => 'barney' ], - [ 'user' => 'fred' ] -]; -// The `property` iteratee shorthand. -map($users, 'user'); -// => ['barney', 'fred'] + +**Arguments:** + +@param array $array The array to slice. + +@param int $start The start position. + +@param int $end The end position. + + + +**Return:** + +@return array the slice of `array`. + +### tail + +Gets all but the first element of `array`. + + + + + +**Arguments:** + +@param array $array The array to query. + + + +**Return:** + +@return array the slice of `array`. + +Example: +```php + [2, 3] ``` -### sortBy +### take + +Creates a slice of `array` with `n` elements taken from the beginning. -Creates an array of elements, sorted in ascending order by the results of -running each element in a collection through each iteratee. This method -performs a stable sort, that is, it preserves the original sort order of -equal elements. The iteratees are invoked with one argument: (value). **Arguments:** -@param array|object $collection The collection to iterate over. +@param array $array The array to query. -@param callable|callable[] $iteratees The iteratees to sort by. +@param int $n The number of elements to take. **Return:** -@return array Returns the new sorted array. +@return array the slice of `array`. Example: ```php 'fred', 'age' => 48 ], - [ 'user' => 'barney', 'age' => 36 ], - [ 'user' => 'fred', 'age' => 40 ], - [ 'user' => 'barney', 'age' => 34 ], -]; +take([1, 2, 3]) +// => [1] -sortBy($users, [function($o) { return $o['user']; }]); -// => [['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]] +take([1, 2, 3], 2) +// => [1, 2] -sortBy($users, ['user', 'age']); -// => [['user' => 'barney', 'age' => 34], ['user' => 'barney', 'age' => 36], ['user' => 'fred', 'age' => 40], ['user' => 'fred', 'age' => 48]] +take([1, 2, 3], 5) +// => [1, 2, 3] + +take([1, 2, 3], 0) +// => [] ``` -## Date +### takeRight -### now +Creates a slice of `array` with `n` elements taken from the end. -Gets the timestamp of the number of milliseconds that have elapsed since the Unix epoch (1 January 1970 00:00:00 UTC). **Arguments:** +@param array $array The array to query. + +@param int $n The number of elements to take. + **Return:** -@return int Returns the timestamp. +@return array the slice of `array`. + +Example: +```php + [3] + +takeRight([1, 2, 3], 2) +// => [2, 3] + +takeRight([1, 2, 3], 5) +// => [1, 2, 3] + +takeRight([1, 2, 3], 0) +// => [] + +``` +### takeRightWhile + +Creates a slice of `array` with elements taken from the end. Elements are +taken until `predicate` returns falsey. The predicate is invoked with +three arguments: (value, index, array). + + + + + +**Arguments:** + +@param array $array The array to query. + +@param callable $predicate The function invoked per iteration. + + + +**Return:** + +@return array the slice of `array`. + +Example: +```php + 'barney', 'active' => false ], +[ 'user' => 'fred', 'active' => true ], +[ 'user' => 'pebbles', 'active' => true ] +]; + +takeRightWhile($users, function($value) { return $value['active']; }) +// => objects for ['fred', 'pebbles'] + +``` +### takeWhile + +Creates a slice of `array` with elements taken from the beginning. Elements +are taken until `predicate` returns falsey. The predicate is invoked with +three arguments: (value, index, array). + + + + + +**Arguments:** + +@param array $array The array to query. + +@param mixed $predicate The function invoked per iteration. + + + +**Return:** + +@return array the slice of `array`. + +Example: +```php + 'barney', 'active' => true ], +[ 'user' => 'fred', 'active' => true ], +[ 'user' => 'pebbles', 'active' => false ] +] + +takeWhile($users, function($value) { return $value['active']; }) +// => objects for ['barney', 'fred'] + +``` +### union + +Creates an array of unique values, in order, from all given arrays using +[`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) +for equality comparisons. + + + + + +**Arguments:** + +@param array ...$arrays The arrays to inspect. + + + +**Return:** + +@return array the new array of combined values. + +Example: +```php + [2, 1] + +``` +### unionBy + +This method is like `union` except that it accepts `iteratee` which is +invoked for each element of each `arrays` to generate the criterion by +which uniqueness is computed. Result values are chosen from the first +array in which the value occurs. The iteratee is invoked with one argument: +(value). + + + + + +**Arguments:** + +@param array ...$arrays The arrays to inspect. + +@param callable $iteratee The iteratee invoked per element. + + + +**Return:** + +@return array the new array of combined values. + +Example: +```php + [2.1, 1.2] + +// The `_::property` iteratee shorthand. +unionBy([['x' => 1]], [['x' => 2], ['x' => 1]], 'x'); +// => [['x' => 1], ['x' => 2]] + +``` +### unionWith + +This method is like `union` except that it accepts `comparator` which +is invoked to compare elements of `arrays`. Result values are chosen from +the first array in which the value occurs. The comparator is invoked +with two arguments: (arrVal, othVal). + + + + + + +**Arguments:** + +@param array ...$arrays The arrays to inspect. + +@param callable $comparator The comparator invoked per element. + + + +**Return:** + +@return array the new array of combined values. + +Example: +```php + 1, 'y' => 2], ['x' => 2, 'y' => 1]] +$others = [['x' => 1, 'y' => 1], ['x' => 1, 'y' => 2]] + +unionWith($objects, $others, '_::isEqual') +// => [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1], ['x' => 1, 'y' => 1]] + +``` +### uniq + +Creates a duplicate-free version of an array, using +[`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) +for equality comparisons, in which only the first occurrence of each element +is kept. The order of result values is determined by the order they occur +in the array. + + + + +**Arguments:** + +@param array $array The array to inspect. + + + +**Return:** + +@return array the new duplicate free array. + +Example: +```php + [2, 1]s + +``` +### uniqBy + +This method is like `uniq` except that it accepts `iteratee` which is +invoked for each element in `array` to generate the criterion by which +uniqueness is computed. The order of result values is determined by the +order they occur in the array. The iteratee is invoked with one argument: +(value). + + + + +**Arguments:** + +@param array $array The array to inspect. + +@param mixed $iteratee The iteratee invoked per element. + + + +**Return:** + +@return array the new duplicate free array. + +Example: +```php + [2.1, 1.2] + +``` +### uniqWith + +This method is like `uniq` except that it accepts `comparator` which +is invoked to compare elements of `array`. The order of result values is +determined by the order they occur in the array.The comparator is invoked +with two arguments: (arrVal, othVal). + + + + +**Arguments:** + +@param array $array The array to inspect. + +@param callable $comparator The comparator invoked per element. + + + +**Return:** + +@return array the new duplicate free array. + +Example: +```php + 1, 'y' => 2], ['x' => 2, 'y' => 1], ['x' => 1, 'y' => 2]] + +uniqWith($objects, '_::isEqual') +// => [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1]] + +``` +### unzip + +This method is like `zip` except that it accepts an array of grouped +elements and creates an array regrouping the elements to their pre-zip +configuration. + + + + +**Arguments:** + +@param array $array The array of grouped elements to process. + + + +**Return:** + +@return array the new array of regrouped elements. + +Example: +```php + [['a', 1, true], ['b', 2, false]] + +unzip($zipped) +// => [['a', 'b'], [1, 2], [true, false]] + +``` +### unzipWith + +This method is like `unzip` except that it accepts `iteratee` to specify +how regrouped values should be combined. The iteratee is invoked with the +elements of each group: (...group). + + + + + +**Arguments:** + +@param array $array The array of grouped elements to process. + +@param (callable | null) $iteratee The function to combine regrouped values. + + + +**Return:** + +@return array the new array of regrouped elements. + +Example: +```php + [[1, 10, 100], [2, 20, 200]] + +unzipWith(zipped, '_::add') +// => [3, 30, 300] + +``` +### without + +Creates an array excluding all given values using +[`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) +for equality comparisons. + +**Note:** Unlike `pull`, this method returns a new array. + + + + +**Arguments:** + +@param array $array The array to inspect. + +@param array $values The values to exclude. + + + +**Return:** + +@return array the new array of filtered values. + +Example: +```php + [3] + +``` +### zip + +Creates an array of grouped elements, the first of which contains the +first elements of the given arrays, the second of which contains the +second elements of the given arrays, and so on. + + + + +**Arguments:** + +@param array ...$arrays The arrays to process. + + + +**Return:** + +@return array the new array of grouped elements. + +Example: +```php + [['a', 1, true], ['b', 2, false]] + +``` +### zipObject + +This method is like `fromPairs` except that it accepts two arrays, +one of property identifiers and one of corresponding values. + + + + + +**Arguments:** + +@param array $props The property identifiers. + +@param array $values The property values. + + + +**Return:** + +@return object the new object. + +Example: +```php + object(stdClass)#210 (2) { +["a"] => int(1) +["b"] => int(2) +} +*\/ + +``` +### zipObjectDeep + +This method is like `zipObject` except that it supports property paths. + + + + + +**Arguments:** + +@param array $props The property identifiers. + +@param array $values The property values. + + + +**Return:** + +@return \stdClass the new object. + +Example: +```php + class stdClass#20 (1) { +public $a => class stdClass#19 (1) { +public $b => +array(2) { +[0] => class stdClass#17 (1) { +public $c => int(1) +} +[1] => class stdClass#18 (1) { +public $d => int(2) +} +} +} +} +*\/ + +``` +### zipWith + +This method is like `zip` except that it accepts `iteratee` to specify +how grouped values should be combined. The iteratee is invoked with the +elements of each group: (...group). + + + + + +**Arguments:** + +@param array ...$arrays The arrays to process. + +@param callable $iteratee The function to combine grouped values. + + + +**Return:** + +@return array the new array of grouped elements. + +Example: +```php + [111, 222] + +``` +## Collection + +### countBy + +Creates an array composed of keys generated from the results of running +each element of `collection` through `iteratee`. The corresponding value of +each key is the number of times the key was returned by `iteratee`. The +iteratee is invoked with one argument: (value). + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $iteratee The iteratee to transform keys. + + + +**Return:** + +@return array Returns the composed aggregate object. + +Example: +```php + ['6' => 2, '4' => 1] + +// The `property` iteratee shorthand. +countBy(['one', 'two', 'three'], 'strlen'); +// => ['3' => 2, '5' => 1] + +``` +### each + +Iterates over elements of `collection` and invokes `iteratee` for each element. +The iteratee is invoked with three arguments: (value, index|key, collection). +Iteratee functions may exit iteration early by explicitly returning `false`. + +**Note:** As with other "Collections" methods, objects with a "length" +property are iterated like arrays. To avoid this behavior use `forIn` +or `forOwn` for object iteration. + + + + + +**Arguments:** + +@param (array | iterable | object) $collection The collection to iterate over. + +@param callable $iteratee The function invoked per iteration. + + + +**Return:** + +@return (array | object) Returns `collection`. + +Example: +```php + Echoes `1` then `2`. + +each((object) ['a' => 1, 'b' => 2], function ($value, $key) { echo $key; }); +// => Echoes 'a' then 'b' (iteration order is not guaranteed). + +``` +### eachRight + +This method is like `each` except that it iterates over elements of +`collection` from right to left. + + + + +**Arguments:** + +@param (array | iterable | object) $collection The collection to iterate over. + +@param callable $iteratee The function invoked per iteration. + + + +**Return:** + +@return (array | object) Returns `collection`. + +Example: +```php + Echoes `2` then `1`. + +``` +### filter + +Iterates over elements of `array`, returning an array of all elements +`predicate` returns truthy for. The predicate is invoked with three +arguments: (value, index, array). + +**Note:** Unlike `remove`, this method returns a new array. + + + + + +**Arguments:** + +@param iterable $array The array to iterate over. + +@param callable $predicate The function invoked per iteration. + + + +**Return:** + +@return array the new filtered array. + +Example: +```php + 'barney', 'age' => 36, 'active' => true], +[ 'user' => 'fred', 'age' => 40, 'active' => false] +]; + +filter($users, function($o) { return !$o['active']; }); +// => objects for ['fred'] + +// The `matches` iteratee shorthand. +filter($users, ['age' => 36, 'active' => true]); +// => objects for ['barney'] + +// The `matchesProperty` iteratee shorthand. +filter($users, ['active', false]); +// => objects for ['fred'] + +// The `property` iteratee shorthand. +filter($users, 'active'); +// => objects for ['barney'] + +``` +### find + +Iterates over elements of `collection`, returning the first element +`predicate` returns truthy for. The predicate is invoked with three +arguments: (value, index|key, collection). + + + + + +**Arguments:** + +@param iterable $collection The collection to inspect. + +@param callable $predicate The function invoked per iteration. + +@param int $fromIndex The index to search from. + + + +**Return:** + +@return mixed Returns the matched element, else `null`. + +Example: +```php + 'barney', 'age' => 36, 'active' => true], +['user' => 'fred', 'age' => 40, 'active' => false], +['user' => 'pebbles', 'age' => 1, 'active' => true] +]; + +find($users, function($o) { return $o['age'] < 40; }); +// => object for 'barney' + +// The `matches` iteratee shorthand. +find($users, ['age' => 1, 'active' => true]); +// => object for 'pebbles' + +// The `matchesProperty` iteratee shorthand. +find($users, ['active', false]); +// => object for 'fred' + +// The `property` iteratee shorthand. +find($users, 'active'); +// => object for 'barney' + +``` +### findLast + +This method is like `find` except that it iterates over elements of +`collection` from right to left. + + + + + +**Arguments:** + +@param iterable $collection The collection to inspect. + +@param callable $predicate The function invoked per iteration. + +@param int $fromIndex The index to search from. + + + +**Return:** + +@return mixed Returns the matched element, else `undefined`. + +Example: +```php + 3 + +``` +### flatMap + +Creates a flattened array of values by running each element in `collection` +through `iteratee` and flattening the mapped results. The iteratee is invoked +with three arguments: (value, index|key, collection). + + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $iteratee The function invoked per iteration. + + + +**Return:** + +@return array the new flattened array. + +Example: +```php + [1, 1, 2, 2] + +``` +### flatMapDeep + +This method is like `flatMap` except that it recursively flattens the +mapped results. + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $iteratee The function invoked per iteration. + + + +**Return:** + +@return array Returns the new flattened array. + +Example: +```php + [1, 1, 2, 2] + +``` +### flatMapDepth + +This method is like `flatMap` except that it recursively flattens the +mapped results up to `depth` times. + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $iteratee The function invoked per iteration. + +@param int $depth The maximum recursion depth. + + + +**Return:** + +@return array the new flattened array. + +Example: +```php + [[1, 1], [2, 2]] + +``` +### groupBy + +Creates an array composed of keys generated from the results of running +each element of `collection` through `iteratee`. The order of grouped values +is determined by the order they occur in `collection`. The corresponding +value of each key is an array of elements responsible for generating the +key. The iteratee is invoked with one argument: (value). + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $iteratee The iteratee to transform keys. + + + +**Return:** + +@return array Returns the composed aggregate object. + +Example: +```php + ['6' => [6.1, 6.3], '4' => [4.2]] + +groupBy(['one', 'two', 'three'], 'strlen'); +// => ['3' => ['one', 'two'], '5' => ['three']] + +``` +### invokeMap + +Invokes the method at `path` of each element in `collection`, returning +an array of the results of each invoked method. Any additional arguments +are provided to each invoked method. If `path` is a function, it's invoked +for, and `this` bound to, each element in `collection`. + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param (array | callable | string) $path The path of the method to invoke or the function invoked per iteration. + +@param array $args The arguments to invoke each method with. + + + +**Return:** + +@return array the array of results. + +Example: +```php + [[1, 5, 7], [1, 2, 3]] + +invokeMap([123, 456], 'str_split') +// => [['1', '2', '3'], ['4', '5', '6']] + +``` +### keyBy + +Creates an object composed of keys generated from the results of running +each element of `collection` through `iteratee`. The corresponding value of +each key is the last element responsible for generating the key. The +iteratee is invoked with one argument: (value). + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $iteratee The iteratee to transform keys. + + + +**Return:** + +@return array the composed aggregate object. + +Example: +```php + 'left', 'code' => 97], +['direction' => 'right', 'code' => 100], +]; + +keyBy($array, function ($o) { return \chr($o['code']); }) +// => ['a' => ['direction' => 'left', 'code' => 97], 'd' => ['direction' => 'right', 'code' => 100]] + +keyBy($array, 'direction'); +// => ['left' => ['direction' => 'left', 'code' => 97], 'right' => ['direction' => 'right', 'code' => 100]] + +``` +### map + +Creates an array of values by running each element in `collection` through +`iteratee`. The iteratee is invoked with three arguments: +(value, index|key, collection). + +Many lodash-php methods are guarded to work as iteratees for methods like +`_::every`, `_::filter`, `_::map`, `_::mapValues`, `_::reject`, and `_::some`. + +The guarded methods are: +`ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, +`fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, +`sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, +`template`, `trim`, `trimEnd`, `trimStart`, and `words` + + + + +**Arguments:** + +@param (array | object) $collection The collection to iterate over. + +@param (callable | string | array) $iteratee The function invoked per iteration. + + + +**Return:** + +@return array Returns the new mapped array. + +Example: +```php + [16, 64] + +map((object) ['a' => 4, 'b' => 8], $square); +// => [16, 64] (iteration order is not guaranteed) + +$users = [ +[ 'user' => 'barney' ], +[ 'user' => 'fred' ] +]; + +// The `property` iteratee shorthand. +map($users, 'user'); +// => ['barney', 'fred'] + +``` +### orderBy + +This method is like `sortBy` except that it allows specifying the sort +orders of the iteratees to sort by. If `orders` is unspecified, all values +are sorted in ascending order. Otherwise, specify an order of "desc" for +descending or "asc" for ascending sort order of corresponding values. + + + + +**Arguments:** + +@param (iterable | null) $collection The collection to iterate over. + +@param (array[] | callable[] | string[]) $iteratee The iteratee(s) to sort by. + +@param string[] $orders The sort orders of `iteratees`. + + + +**Return:** + +@return array the new sorted array. + +Example: +```php + 'fred', 'age' => 48], +['user' => 'barney', 'age' => 34], +['user' => 'fred', 'age' => 40], +['user' => 'barney', 'age' => 36] +] + +// Sort by `user` in ascending order and by `age` in descending order. +orderBy($users, ['user', 'age'], ['asc', 'desc']) +// => [['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]] + +``` +### partition + +Creates an array of elements split into two groups, the first of which +contains elements `predicate` returns truthy for, the second of which +contains elements `predicate` returns falsey for. The predicate is +invoked with one argument: (value). + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $predicate The function invoked per iteration. + + + +**Return:** + +@return array the array of grouped elements. + +Example: +```php + 'barney', 'age' => 36, 'active' => false], +['user' => 'fred', 'age' => 40, 'active' => true], +['user' => 'pebbles', 'age' => 1, 'active' => false] +]; + +partition($users, function($user) { return $user['active']; }) +// => objects for [['fred'], ['barney', 'pebbles']] + +``` +### reduce + +Reduces `collection` to a value which is the accumulated result of running +each element in `collection` thru `iteratee`, where each successive +invocation is supplied the return value of the previous. If `accumulator` +is not given, the first element of `collection` is used as the initial +value. The iteratee is invoked with four arguments: +(accumulator, value, index|key, collection). + +Many lodash methods are guarded to work as iteratees for methods like +`reduce`, `reduceRight`, and `transform`. + +The guarded methods are: +`assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, +and `sortBy` + + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param mixed $iteratee The function invoked per iteration. + +@param mixed $accumulator The initial value. + + + +**Return:** + +@return mixed Returns the accumulated value. + +Example: +```php + 3 + +reduce(['a' => 1, 'b' => 2, 'c' => 1], function ($result, $value, $key) { +if (!isset($result[$value])) { +$result[$value] = []; +} +$result[$value][] = $key; + +return $result; +}, []) +// => ['1' => ['a', 'c'], '2' => ['b']] (iteration order is not guaranteed) + +``` +### reduceRight + +This method is like `reduce` except that it iterates over elements of +`collection` from right to left. + + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param mixed $iteratee The function invoked per iteration. + +@param mixed $accumulator The initial value. + + + +**Return:** + +@return mixed Returns the accumulated value. + +Example: +```php + flattened.concat(other), []) +// => [4, 5, 2, 3, 0, 1] + +``` +### reject + +The opposite of `filter` this method returns the elements of `collection` +that `predicate` does **not** return truthy for. + + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param callable $predicate The function invoked per iteration. + + + +**Return:** + +@return array the new filtered array. + +Example: +```php + 'barney', 'active' => true], +['user' => 'fred', 'active' => false] +] + +reject($users, 'active') +// => objects for ['fred'] + +``` +### size + +Gets the size of `collection` by returning its length for array +values or the number of public properties for objects. + + + + +**Arguments:** + +@param (array | object | string) $collection The collection to inspect. + + + +**Return:** + +@return int Returns the collection size. + +Example: +```php + 3 + +size(new class { public $a = 1; public $b = 2; private $c = 3; }); +// => 2 + +size('pebbles'); +// => 7 + +``` +### some + +Checks if `predicate` returns truthy for **any** element of `collection`. +Iteration is stopped once `predicate` returns truthy. The predicate is +invoked with three arguments: (value, index|key, collection). + + + + +**Arguments:** + +@param iterable $collection The collection to iterate over. + +@param (callable | string | array) $predicate The function invoked per iteration. + + + +**Return:** + +@return boolean Returns `true` if any element passes the predicate check, else `false`. + +Example: +```php + true + +$users = [ +['user' => 'barney', 'active' => true], +['user' => 'fred', 'active' => false] +]; + +// The `matches` iteratee shorthand. +some($users, ['user' => 'barney', 'active' => false ]); +// => false + +// The `matchesProperty` iteratee shorthand. +some($users, ['active', false]); +// => true + +// The `property` iteratee shorthand. +some($users, 'active'); +// => true + +``` +### sortBy + +Creates an array of elements, sorted in ascending order by the results of +running each element in a collection through each iteratee. This method +performs a stable sort, that is, it preserves the original sort order of +equal elements. The iteratees are invoked with one argument: (value). + + + + +**Arguments:** + +@param (array | object | null) $collection The collection to iterate over. + +@param (callable | callable[]) $iteratees The iteratees to sort by. + + + +**Return:** + +@return array Returns the new sorted array. + +Example: +```php + 'fred', 'age' => 48 ], +[ 'user' => 'barney', 'age' => 36 ], +[ 'user' => 'fred', 'age' => 40 ], +[ 'user' => 'barney', 'age' => 34 ], +]; + +sortBy($users, [function($o) { return $o['user']; }]); +// => [['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]] + +sortBy($users, ['user', 'age']); +// => [['user' => 'barney', 'age' => 34], ['user' => 'barney', 'age' => 36], ['user' => 'fred', 'age' => 40], ['user' => 'fred', 'age' => 48]] + +``` +## Date + +### now + +Gets the timestamp of the number of milliseconds that have elapsed since the Unix epoch (1 January 1970 00:00:00 UTC). + + + + +**Arguments:** + + + +**Return:** + +@return int Returns the timestamp. + +Example: +```php + 1511180325735 + +``` +## Function + +### after + +The opposite of `before`; this method creates a function that invokes +`func` once it's called `n` or more times. + + + + + +**Arguments:** + +@param int $n The number of calls before `func` is invoked. + +@param Callable $func The function to restrict. + + + +**Return:** + +@return Callable Returns the new restricted function. + +Example: +```php + $type, 'complete' => $done ]); +}); +// => Prints 'done saving!' after the two async saves have completed. + +``` +### ary + +Creates a function that invokes `func`, with up to `n` arguments, +ignoring any additional arguments. + + + + + +**Arguments:** + +@param callable $func The function to cap arguments for. + +@param int $n The arity cap. + + + +**Return:** + +@return Callable Returns the new capped function. + +Example: +```php + [6, 8, 10] + +``` +### before + +Creates a function that invokes `func`, with the arguments +of the created function, while it's called less than `n` times. Subsequent +calls to the created function return the result of the last `func` invocation. + + + + + +**Arguments:** + +@param int $n The number of calls at which `func` is no longer invoked. + +@param callable $func The function to restrict. + + + +**Return:** + +@return callable Returns the new restricted function. + +Example: +```php + Fetch up to 4 results. + +``` +### bind + +Creates a function that invokes `func` with the `this` binding of `object` +and `partials` prepended to the arguments it receives. + + + + +**Arguments:** + +@param callable $function The function to bind. + +@param (object | mixed) $object The `object` binding of `func`. + +@param array $partials The arguments to be partially applied. + + + +**Return:** + +@return callable Returns the new bound function. + +Example: +```php +user . $punctuation; +} + +$object = $object = new class { +public $user = 'fred'; +}; + +$bound = bind('greet', $object, 'hi'); +$bound('!'); +// => 'hi fred!' + +``` +### bindKey + +Creates a function that invokes the method `$function` of `$object` with `$partials` +prepended to the arguments it receives. + +This method differs from `bind` by allowing bound functions to reference +methods that may be redefined or don't yet exist + + + + +**Arguments:** + +@param object $object The object to invoke the method on. + +@param string $function The name of the method. + +@param array $partials The arguments to be partially applied. + + + +**Return:** + +@return callable Returns the new bound function. + +Example: +```php +user.$punctuation; +} +}; + +$bound = bindKey($object, 'greet', 'hi'); +$bound('!'); +// => 'hi fred!' + +``` +### curry + +Creates a function that accepts arguments of `func` and either invokes +`func` returning its result, if at least `arity` number of arguments have +been provided, or returns a function that accepts the remaining `func` +arguments, and so on. The arity of `func` may be specified if `func.length` +is not sufficient. + +The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, +may be used as a placeholder for provided arguments. + +**Note:** This method doesn't set the "length" property of curried functions. + + + + +**Arguments:** + +@param callable $func The function to curry. + +@param (int | null) $arity The arity of `func`. + + + +**Return:** + +@return callable Returns the new curried function. + +Example: +```php + [1, 2, 3] + +$curried(1, 2)(3); +// => [1, 2, 3] + +$curried(1, 2, 3); +// => [1, 2, 3] + +// Curried with placeholders. +$curried(1)(_, 3)(2); +// => [1, 2, 3] + +``` +### delay + +Invokes `func` after `wait` milliseconds. Any additional arguments are +provided to `func` when it's invoked. + + + + +**Arguments:** + +@param callable $func The function to delay. + +@param int $wait The number of milliseconds to delay invocation. + +@param array $args + + + +**Return:** + +@return int the timer id. + +Example: +```php + Echo 'later' after one second. + +``` +### flip + +Creates a function that invokes `func` with arguments reversed. + + + + + +**Arguments:** + +@param callable $func The function to flip arguments for. + + + +**Return:** + +@return callable Returns the new flipped function. + +Example: +```php + ['d', 'c', 'b', 'a'] + +``` +### memoize + +Creates a function that memoizes the result of `func`. If `resolver` is +provided, it determines the cache key for storing the result based on the +arguments provided to the memoized function. By default, the first argument +provided to the memoized function is used as the map cache key + +**Note:** The cache is exposed as the `cache` property on the memoized +function. Its creation may be customized by replacing the `_.memoize.Cache` +constructor with one whose instances implement the +[`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) +method interface of `clear`, `delete`, `get`, `has`, and `set`. + + + + + +**Arguments:** + +@param callable $func The function to have its output memoized. + +@param (callable | null) $resolver The function to resolve the cache key. + + + +**Return:** + +@return callable Returns the new memoized function. + +Example: +```php + 1, 'b' => 2]; +$other = ['c' => 3, 'd' => 4]; + +$values = memoize('_\values'); +$values($object); +// => [1, 2] + +$values($other); +// => [3, 4] + +$object['a'] = 2; +$values($object); +// => [1, 2] + +// Modify the result cache. +$values->cache->set($object, ['a', 'b']); +$values($object); +// => ['a', 'b'] + +``` +### negate + +Creates a function that negates the result of the predicate `func` + + + + + +**Arguments:** + +@param callable $predicate The predicate to negate. + + + +**Return:** + +@return callable Returns the new negated function. + +Example: +```php + [1, 3, 5] + +``` +### once + +Creates a function that is restricted to invoking `func` once. Repeat calls +to the function return the value of the first invocation. The `func` is +invoked with the arguments of the created function. + + + + + +**Arguments:** + +@param callable $func The function to restrict. + + + +**Return:** + +@return callable the new restricted function. + +Example: +```php + `createApplication` is invoked once + +``` +### overArgs + +Creates a function that invokes `func` with its arguments transformed. + + + + + +**Arguments:** + +@param callable $func The function to wrap. + +@param callable[] $transforms The argument transforms. + + + +**Return:** + +@return callable the new function. + +Example: +```php + [81, 6] + +$func(10, 5); +// => [100, 10] + +``` +### partial + +Creates a function that invokes `func` with `partials` prepended to the +arguments it receives. + + + + + +**Arguments:** + +@param callable $func The function to partially apply arguments to. + +@param array $partials The arguments to be partially applied. + + + +**Return:** + +@return callable Returns the new partially applied function. + +Example: +```php + 'hello fred' + +``` +### rest + +Creates a function that invokes `func` with the `this` binding of the +created function and arguments from `start` and beyond provided as +an array. + + + + + +**Arguments:** + +@param callable $func The function to apply a rest parameter to. + +@param (int | null) $start The start position of the rest parameter. + + + +**Return:** + +@return callable Returns the new function. + +Example: +```php + 1 ? ', & ' : '') . last($names); +}); + +$say('hello', 'fred', 'barney', 'pebbles'); +// => 'hello fred, barney, & pebbles' + +``` +### spread + +Creates a function that invokes `func` with the `this` binding of the +create function and an array of arguments much like +[`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). + +**Note:** This method is based on the +[spread operator](https://mdn.io/spread_operator). + + + + + +**Arguments:** + +@param callable $func The function to spread arguments over. + +@param int $start The start position of the spread. + + + +**Return:** + +@return callable Returns the new function. + +Example: +```php + 'fred says hello' + +``` +### unary + +Creates a function that accepts up to one argument, ignoring any +additional arguments. + + + + +**Arguments:** + +@param callable $func The function to cap arguments for. + + + +**Return:** + +@return callable the new capped function. + +Example: +```php + [6, 8, 10] + +``` +### wrap + +Creates a function that provides `value` to `wrapper` as its first +argument. Any additional arguments provided to the function are appended +to those provided to the `wrapper`. + + + + +**Arguments:** + +@param mixed $value The value to wrap. + +@param callable $wrapper The wrapper function. + + + +**Return:** + +@return callable the new function. + +Example: +```php +' . $func($text) . '

'; +}); + +$p('fred, barney, & pebbles'); +// => '

fred, barney, & pebbles

' + +``` +## Lang + +### eq + +Performs a comparison between two values to determine if they are equivalent. + + + + +**Arguments:** + +@param mixed $value The value to compare. + +@param mixed $other The other value to compare. + + + +**Return:** + +@return boolean Returns `true` if the values are equivalent, else `false`. + +Example: +```php + 1]; +$other = (object) ['a' => 1]; + +eq($object, $object); +// => true + +eq($object, $other); +// => false + +eq('a', 'a'); +// => true + +eq(['a'], (object) ['a']); +// => false + +eq(INF, INF); +// => true + +``` +### isEqual + +Performs a deep comparison between two values to determine if they are +equivalent. + +**Note:** This method supports comparing arrays, booleans, +DateTime objects, exception objects, SPLObjectStorage, numbers, +strings, typed arrays, resources, DOM Nodes. objects are compared +by their own, not inherited, enumerable properties. + + + + + +**Arguments:** + +@param mixed $value The value to compare. + +@param mixed $other The other value to compare. + + + +**Return:** + +@return bool Returns `true` if the values are equivalent, else `false`. + +Example: +```php + 1] +$other = ['a' => '1'] + +isEqual($object, $other) +// => true + +$object === $other +// => false + +``` +### isError + +Checks if `value` is an `\Exception`, `\ParseError`, \Error`, \Throwable`, \SoapFault`, \DOMException`, \PDOException`, object. + + + + + +**Arguments:** + +@param mixed $value The value to check. + + + +**Return:** + +@return boolean Returns `true` if `value` is an error object, else `false`. + +Example: +```php + true + +isError(\Exception::Class) +// => false + +``` +## Math + +### add + +Adds two numbers. + + + + + +**Arguments:** + +@param (int | float | string) $augend The first number in an addition. + +@param (int | float | string) $addend The second number in an addition. + + + +**Return:** + +@return (int | float) Returns the total. Example: ```php 1511180325735 +add(6, 4); +// => 10 ``` -## Lang +### max -### isEqual +Computes the maximum value of `array`. If `array` is empty or falsey, null is returned. -Performs a deep comparison between two values to determine if they are -equivalent. -**Note:** This method supports comparing arrays, booleans, -DateTime objects, exception objects, SPLObjectStorage, numbers, -strings, typed arrays, resources, DOM Nodes. objects are compared -by their own, not inherited, enumerable properties. -**Arguments:** -@param mixed $value The value to compare. +**Arguments:** -@param mixed $other The other value to compare. +@param (array | null) $array The array to iterate over. **Return:** -@return bool Returns `true` if the values are equivalent, else `false`. +@return (int | null) Returns the maximum value. Example: ```php 1] -$other = ['a' => '1'] + use function _\max; -isEqual($object, $other) -// => true +max([4, 2, 8, 6]); +// => 8 -$object === $other -// => false +max([]); +// => null ``` -### isError +### maxBy + +This method is like `max` except that it accepts `iteratee` which is +invoked for each element in `array` to generate the criterion by which +the value is ranked. The iteratee is invoked with one argument: (value). -Checks if `value` is an `\Exception`, `\ParseError`, \Error`, \Throwable`, \SoapFault`, \DOMException`, \PDOException`, object. **Arguments:** -@param mixed $value The value to check. +@param array $array The array to iterate over. + +@param (callable | string) $iteratee The iteratee invoked per element. **Return:** -@return bool Returns `true` if `value` is an error object, else `false`. +@return mixed Returns the maximum value. Example: ```php true +$objects = [['n' => 1], ['n' => 2]]; + +maxBy($objects, function($o) { return $o['n']; }); +// => ['n' => 2] + +// The `property` iteratee shorthand. +maxBy($objects, 'n'); +// => ['n' => 2] -isError(\Exception::Class) -// => false ``` ## Number @@ -1282,13 +3586,15 @@ Clamps `number` within the inclusive `lower` and `upper` bounds. + + **Arguments:** -@param int $ number The number to clamp. +@param int $number The number to clamp. -@param int $ lower The lower bound. +@param int $lower The lower bound. -@param int $ upper The upper bound. +@param int $upper The upper bound. @@ -1312,13 +3618,16 @@ clamp(10, -5, 5) Checks if `number` is between `start` and up to, but not including, `end`. If `end` is not specified, it's set to `start` with `start` then set to `0`. - If `start` is greater than `end` the params are swapped to support negative ranges. + + + + **Arguments:** -@param float $ number The number to check. +@param float $number The number to check. @param float $start The start of the range. @@ -1328,7 +3637,7 @@ negative ranges. **Return:** -@return bool Returns `true` if `number` is in the range, else `false`. +@return boolean Returns `true` if `number` is in the range, else `false`. Example: ```php @@ -1360,29 +3669,33 @@ inRange(-3, -2, -6) ### random Produces a random number between the inclusive `lower` and `upper` bounds. - If only one argument is provided a number between `0` and the given number is returned. If `floating` is `true`, or either `lower` or `upper` are floats, a floating-point number is returned instead of an integer. + + + + **Arguments:** -@param int|float $lower The lower bound. +@param (int | float | bool) $lower The lower bound. -@param int|float $upper The upper bound. +@param (int | float | bool) $upper The upper bound. -@param bool $floating Specify returning a floating-point number. +@param (bool | null) $floating Specify returning a floating-point number. **Return:** -@return int|float Returns the random number. +@return (int | float) Returns the random number. Example: ```php an integer between 0 and 5 @@ -1394,6 +3707,151 @@ random(5, true) random(1.2, 5.2) // => a floating-point number between 1.2 and 5.2 + +``` +## Object + +### get + +Gets the value at path of object. If the resolved value is null the defaultValue is returned in its place. + + + + + + +**Arguments:** + +@param mixed $object The associative array or object to fetch value from + +@param (array | string) $path Dot separated or array of string + +@param mixed $defaultValue (optional)The value returned for unresolved or null values. + + + +**Return:** + +@return mixed Returns the resolved value. + +Example: +```php + ["key2" => ["key3" => "val1", "key4" => ""]]]; +get($sampleArray, 'key1.key2.key3'); +// => "val1" + +get($sampleArray, 'key1.key2.key5', "default"); +// => "default" + +get($sampleArray, 'key1.key2.key4', "default"); +// => "" + +``` +### pick + +Creates an object composed of the picked `object` properties. + + + + +**Arguments:** + +@param object $object The source object. + +@param (string | string[]) $paths The property paths to pick. + + + +**Return:** + +@return \stdClass Returns the new object. + +Example: +```php + 1, 'b' => '2', 'c' => 3]; + +pick($object, ['a', 'c']); +// => (object) ['a' => 1, 'c' => 3] + +``` +### pickBy + +Creates an object composed of the `object` properties `predicate` returns +truthy for. The predicate is invoked with two arguments: (value, key). + + + + +**Arguments:** + +@param (object | null) $object The source object. + +@param callable $predicate The function invoked per property. + + + +**Return:** + +@return \stdClass Returns the new object. + +Example: +```php + 1, 'b' => 'abc', 'c' => 3]; + +pickBy(object, 'is_numeric'); +// => (object) ['a' => 1, 'c' => 3] + +``` +## Seq + +### chain + +Creates a `lodash` wrapper instance that wraps `value` with explicit method +chain sequences enabled. The result of such sequences must be unwrapped +with `->value()`. + + + + +**Arguments:** + +@param mixed $value The value to wrap. + + + +**Return:** + +@return \_ Returns the new `lodash` wrapper instance. + +Example: +```php + 'barney', 'age' => 36], +['user' => 'fred', 'age' => 40], +['user' => 'pebbles', 'age' => 1 ], +]; + +$youngest = chain($users) +->sortBy('age') +->map(function($o) { +return $o['user'] . ' is ' . $o['age']; +}) +->head() +->value(); +// => 'pebbles is 1' + ``` ## String @@ -1403,6 +3861,8 @@ Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). + + **Arguments:** @param string $string The string to convert. @@ -1435,6 +3895,7 @@ to lower case. + **Arguments:** @param string $string The string to capitalize. @@ -1464,6 +3925,8 @@ letters to basic Latin letters and removing + + **Arguments:** @param string $string The string to deburr. @@ -1478,8 +3941,10 @@ Example: ```php 'deja vu' + ``` ### endsWith @@ -1487,6 +3952,7 @@ Checks if `string` ends with the given target string. + **Arguments:** @param string $string The string to inspect. @@ -1499,7 +3965,7 @@ Checks if `string` ends with the given target string. **Return:** -@return bool Returns `true` if `string` ends with `target`, else `false`. +@return boolean Returns `true` if `string` ends with `target`, else `false`. Example: ```php @@ -1521,6 +3987,7 @@ endsWith('abc', 'b', 2) Converts the characters "&", "<", ">", '"', and "'" in `string` to their corresponding HTML entities. + Though the ">" character is escaped for symmetry, characters like ">" and "/" don't need escaping in HTML and have no special meaning unless they're part of a tag or unquoted attribute value. See @@ -1531,6 +3998,9 @@ When working with HTML you should always [quote attribute values](http://wonko.com/post/html-escaping) to reduce XSS vectors. + + + **Arguments:** @param string $string The string to escape. @@ -1557,6 +4027,7 @@ Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", + **Arguments:** @param string $string The string to escape. @@ -1583,6 +4054,7 @@ Converts `string` to + **Arguments:** @param string $string The string to convert. @@ -1614,6 +4086,7 @@ Converts `string`, as space separated words, to lower case. + **Arguments:** @param string $string The string to convert. @@ -1645,6 +4118,7 @@ Converts the first character of `string` to lower case. + **Arguments:** @param string $string The string to convert. @@ -1670,9 +4144,12 @@ lowerFirst('FRED') ### pad Pads `string` on the left and right sides if it's shorter than `length`. - Padding characters are truncated if they can't be evenly divided by `length`. + + + + **Arguments:** @param string $string The string to pad. @@ -1709,6 +4186,8 @@ characters are truncated if they exceed `length`. + + **Arguments:** @param string $string The string to pad. @@ -1745,6 +4224,7 @@ characters are truncated if they exceed `length`. +s **Arguments:** @param string $string ='' The string to pad. @@ -1772,7 +4252,7 @@ padStart('abc', 6, '_-') padStart('abc', 2) // => 'abc' -s + ``` ### parseInt @@ -1783,9 +4263,13 @@ hexadecimal, in which case a `radix` of `16` is used. **Note:** This method uses PHP's built-in integer casting, which does not necessarily align with the [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. + + + + **Arguments:** -@param int|float|string $string The string to convert. +@param (int | float | string) $string The string to convert. @param int $radix The radix to interpret `string` by. @@ -1810,6 +4294,8 @@ Repeats the given string `n` times. + + **Arguments:** @param string $string The string to repeat. @@ -1844,13 +4330,17 @@ Replaces matches for `pattern` in `string` with `replacement`. **Note:** This method is based on [`String#replace`](https://mdn.io/String/replace). + + + + **Arguments:** @param string $string The string to modify. @param string $pattern The pattern to replace. -@param callable|string $ replacement The match replacement. +@param (callable | string) $replacement The match replacement. @@ -1873,7 +4363,6 @@ Converts `string` to [snake case](https://en.wikipedia.org/wiki/Snake_case). - **Arguments:** @param string $string The string to convert. @@ -1888,6 +4377,7 @@ Example: ```php 'foo_bar' @@ -1896,6 +4386,7 @@ snakeCase('fooBar') snakeCase('--FOO-BAR--') // => 'foo_bar' + ``` ### split @@ -1904,9 +4395,12 @@ Splits `string` by `separator`. **Note:** This method is based on [`String#split`](https://mdn.io/String/split). + + + **Arguments:** -@param string $ string The string to split. +@param string $string The string to split. @param string $separator The separator pattern to split by. @@ -1934,6 +4428,7 @@ Converts `string` to + **Arguments:** @param string $string The string to convert. @@ -1965,6 +4460,7 @@ Checks if `string` starts with the given target string. + **Arguments:** @param string $string The string to inspect. @@ -1977,7 +4473,7 @@ Checks if `string` starts with the given target string. **Return:** -@return bool Returns `true` if `string` starts with `target`, else `false`. +@return boolean Returns `true` if `string` starts with `target`, else `false`. Example: ```php @@ -2003,16 +4499,18 @@ properties may be accessed as free variables in the template. If a setting object is given, it takes precedence over `$templateSettings` values. +RegExp $options['escape'] = _::$templateSettings['escape'] The HTML "escape" delimiter. +RegExp $options['evaluate'] = _::$templateSettings['evaluate'] The "evaluate" delimiter. +array $options['imports'] = _::$templateSettings['imports'] An object to import into the template as free variables. +RegExp $options['interpolate'] = _::$templateSettings['interpolate'] The "interpolate" delimiter. + + **Arguments:** @param string $string The template string. @param array $options The options array. -RegExp $options['escape'] = _::$templateSettings['escape'] The HTML "escape" delimiter. -RegExp $options['evaluate'] = _::$templateSettings['evaluate'] The "evaluate" delimiter. -array $options['imports'] = _::$templateSettings['imports'] An object to import into the template as free variables. -RegExp $options['interpolate'] = _::$templateSettings['interpolate'] The "interpolate" delimiter. @@ -2072,6 +4570,7 @@ Converts `string`, as a whole, to lower case + **Arguments:** @param string $string The string to convert. @@ -2103,6 +4602,7 @@ Converts `string`, as a whole, to upper case + **Arguments:** @param string $string The string to convert. @@ -2126,6 +4626,7 @@ toUpper('fooBar') toUpper('__foo_bar__') // => '__FOO_BAR__' + ``` ### trim @@ -2133,6 +4634,7 @@ Removes leading and trailing whitespace or specified characters from `string`. + **Arguments:** @param string $string The string to trim. @@ -2163,6 +4665,7 @@ Removes trailing whitespace or specified characters from `string`. + **Arguments:** @param string $string The string to trim. @@ -2179,11 +4682,13 @@ Example: ```php ' abc' trimEnd('-_-abc-_-', '_-') // => '-_-abc' + ``` ### trimStart @@ -2191,6 +4696,7 @@ Removes leading whitespace or specified characters from `string`. + **Arguments:** @param string $string The string to trim. @@ -2218,18 +4724,21 @@ trimStart('-_-abc-_-', '_-') ### truncate Truncates `string` if it's longer than the given maximum string length. - The last characters of the truncated string are replaced with the omission string which defaults to "...". + +length = 30 The maximum string length. +omission = '...' The string to indicate text is omitted. +separator The separator pattern to truncate to. + + + **Arguments:** @param string $string The string to truncate. @param array $options The options object. -length = 30 The maximum string length. -omission = '...' The string to indicate text is omitted. -separator The separator pattern to truncate to. @@ -2246,19 +4755,19 @@ truncate('hi-diddly-ho there, neighborino') // => 'hi-diddly-ho there, neighbo...' truncate('hi-diddly-ho there, neighborino', [ - 'length' => 24, - 'separator' => ' ' +'length' => 24, +'separator' => ' ' ]) // => 'hi-diddly-ho there,...' truncate('hi-diddly-ho there, neighborino', [ - 'length' => 24, - 'separator' => '/,? +/' +'length' => 24, +'separator' => '/,? +/' ]) // => 'hi-diddly-ho there...' truncate('hi-diddly-ho there, neighborino', [ - 'omission' => ' [...]' +'omission' => ' [...]' ]) // => 'hi-diddly-ho there, neig [...]' @@ -2271,6 +4780,7 @@ their corresponding characters. + **Arguments:** @param string $string The string to unescape. @@ -2295,7 +4805,6 @@ unescape('fred, barney, & pebbles') Converts `string`, as space separated words, to upper case. - **Arguments:** @param string $string The string to convert. @@ -2327,6 +4836,7 @@ Converts the first character of `string` to upper case. + **Arguments:** @param string $string The string to convert. @@ -2355,6 +4865,8 @@ Splits `string` into an array of its words. + + **Arguments:** @param string $string The string to inspect. @@ -2388,17 +4900,19 @@ object. Any additional arguments are provided to `func` when it's invoked. + +s **Arguments:** @param callable $func The function to attempt. -@param array $args The arguments to invoke `func` with. +@param array $args The arguments to invoke `func` with. **Return:** -@return mixed|\Throwable Returns the `func` result or error object. +@return (mixed | \Throwable) Returns the `func` result or error object. Example: ```php @@ -2407,13 +4921,51 @@ Example: // Avoid throwing errors for invalid PDO data source. $elements = attempt(function () { - new \PDO(null); +new \PDO(null); }); if (isError($elements)) { - $elements = []; +$elements = []; } -s + +``` +### defaultTo + +Checks value to determine whether a default value should be returned in its place. +The defaultValue is returned if value is NaN or null. + + + + + + +**Arguments:** + +@param mixed $value Any value. + +@param mixed $defaultValue Value to return when $value is null or NAN + + + +**Return:** + +@return mixed Returns `value`. + +Example: +```php + "default" + +$a = "x"; + +defaultTo($a, "default"); +// => "x" + ``` ### identity @@ -2421,6 +4973,7 @@ This method returns the first argument it receives. + **Arguments:** @param mixed $value Any value. @@ -2448,9 +5001,10 @@ Creates a function that returns the value at `path` of a given object. + **Arguments:** -@param array|string $ path The path of the property to get. +@param (array | string) $path The path of the property to get. @@ -2464,8 +5018,8 @@ Example: use function _\property; $objects = [ - [ 'a' => [ 'b' => 2 ] ], - [ 'a' => [ 'b' => 1 ] ] +[ 'a' => [ 'b' => 2 ] ], +[ 'a' => [ 'b' => 1 ] ] ]; map($objects, property('a.b')); diff --git a/bin/compile b/bin/compile new file mode 100755 index 0000000..1018030 --- /dev/null +++ b/bin/compile @@ -0,0 +1,36 @@ +#!/usr/bin/env php +getRealPath()); + $code = preg_replace(['#\<\?php#', '#declare\(strict_types=1\);#'], '', $code, 1); + $code = preg_replace('#namespace ([a-zA-Z\\\/-_0-9]+);\s?(.*)#', 'namespace $1 { $2 }', $code); + + return $code; +} + +$code = <<getBasename(), ['bootstrap.php', 'lodash.php', 'compiled.php', 'Lodash.php'], true)) { + continue; + } + + $code .= getCode($file); +} + +file_put_contents(dirname(__DIR__).'/src/compiled.php', $code); + diff --git a/bin/build b/bin/generate-readme similarity index 56% rename from bin/build rename to bin/generate-readme index 2b80eaa..a3d3419 100755 --- a/bin/build +++ b/bin/generate-readme @@ -1,11 +1,18 @@ #!/usr/bin/env php create($reflection->getDocComment()); + $docblock = $parser->parse(new TokenIterator((new Lexer())->tokenize($reflection->getDocComment()))); - $category = (string) $docblock->getTagsByName('category')[0]; + $category = (string) current($docblock->getTagsByName('@category'))->value; $readme[$category][$function] = ''; $readme[$category][$function] .= "### $function\n\n"; - $readme[$category][$function] .= $docblock->getSummary().PHP_EOL.PHP_EOL; - $readme[$category][$function] .= $docblock->getDescription().PHP_EOL.PHP_EOL; + + foreach (array_filter($docblock->children, function (PhpDocChildNode $child): bool { + return $child instanceof PhpDocTextNode; + }) as $text) { + $readme[$category][$function] .= (string) $text.PHP_EOL; + } $readme[$category][$function] .= "**Arguments:**\n\n"; - foreach ($docblock->getTagsByName('param') as $param) { - $readme[$category][$function] .= $param->render().PHP_EOL.PHP_EOL; + foreach ($docblock->getTagsByName('@param') as $param) { + $readme[$category][$function] .= (string) $param.PHP_EOL.PHP_EOL; } $readme[$category][$function] .= PHP_EOL.PHP_EOL; $readme[$category][$function] .= "**Return:**\n\n"; - $readme[$category][$function] .= $docblock->getTagsByName('return')[0]->render(); + $readme[$category][$function] .= (string) current($docblock->getTagsByName('@return')); $readme[$category][$function] .= PHP_EOL.PHP_EOL; - if ($docblock->hasTag('example')) { + if (\preg_match('#((.|\n)*?)#', $readme[$category][$function], $matches)) { + + $readme[$category][$function] = str_replace($matches[0], '', $readme[$category][$function]); - $example = str_replace(['', ''], '', $docblock->getTagsByName('example')[0] ?? ''); + $example = $matches[1]; - if ($example) { - $readme[$category][$function] .= "Example:\n```php\n $functions) { } } -file_put_contents(dirname(__DIR__).'/README.md', $content); \ No newline at end of file +file_put_contents(dirname(__DIR__).'/README.md', $content); diff --git a/composer.json b/composer.json index f82291f..35d299e 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "solidworx/lodash-php", + "name": "lodash-php/lodash-php", "description": "A port of Lodash to PHP", "keywords": [ "lodash", @@ -19,17 +19,19 @@ ], "autoload": { "files": [ - "src/internal/unicode.php", "src/bootstrap.php" ] }, "require": { - "php": "^7.1", - "sebastian/comparator": "^2.1", - "symfony/property-access": "^2.7 | ^3.0 | ^4.0" + "php": "^7.2 || ^8.0", + "sebastian/comparator": "^1.2 | ^2.0 | ^2.1 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "symfony/property-access": "^2.7 | ^3.0 | ^4.0 | ^5.0 | ^6.0" }, "require-dev": { - "phpunit/phpunit": "^6.4", - "phpdocumentor/reflection-docblock": "^4.2" + "phpdocumentor/reflection-docblock": "*", + "phpstan/phpdoc-parser": "*", + "phpunit/phpunit": "^7.3 || ^8.4", + "phpstan/phpstan": "^1.10", + "friendsofphp/php-cs-fixer": "^2.12" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..b806197 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,32 @@ +parameters: + ignoreErrors: + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Collection/size.php + + - + message: "#^Right side of \\|\\| is always false\\.$#" + count: 2 + path: src/Number/random.php + + - + message: "#^Call to function is_array\\(\\) with null will always evaluate to false\\.$#" + count: 1 + path: src/String/replace.php + + - + message: "#^Comparison operation \"\\>\\=\" between int\\<0, max\\> and 0 is always true\\.$#" + count: 1 + path: src/String/startsWith.php + + - + message: "#^Offset mixed on \\*NEVER\\* in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: src/internal/baseIntersection.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/internal/isIterateeCall.php + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..08970a1 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,15 @@ +includes: + - phpstan-baseline.neon + +parameters: + paths: + - src + bootstrapFiles: + - src/bootstrap.php + level: 5 + excludePaths: + - src/compiled.php + - src/Lodash.php + - src/internal/memoizeCapped.php # Exclude internal function due to dynamic class usage + ignoreErrors: + - "#^PHPDoc tag \\@param references unknown parameter\\: \\$[a-zA-Z]+$#" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3c500fe..192a663 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,7 @@ diff --git a/src/Array/chunk.php b/src/Array/chunk.php index 034349e..5e98626 100644 --- a/src/Array/chunk.php +++ b/src/Array/chunk.php @@ -38,4 +38,4 @@ function chunk(?array $array, int $number): array } return \array_chunk($array ?? [], $number, false); -} \ No newline at end of file +} diff --git a/src/Array/concat.php b/src/Array/concat.php index 94a7465..945f2f8 100644 --- a/src/Array/concat.php +++ b/src/Array/concat.php @@ -17,8 +17,9 @@ * * @category Array * - * @param array $array The array to concatenate. - * @param mixed $values The values to concatenate. + * @param array $array The array to concatenate. + * @param array $values The values to concatenate. + * * @return array Returns the new concatenated array. * * @example @@ -26,10 +27,10 @@ * $array = [1]; * $other = concat($array, 2, [3], [[4]]); * - * var_dump(other) + * var_dump($other) * // => [1, 2, 3, [4]] * - * var_dump(array) + * var_dump($array) * // => [1] * */ @@ -40,4 +41,4 @@ function concat($array, ...$values): array }; return \array_merge($check($array), ...\array_map($check, $values)); -} \ No newline at end of file +} diff --git a/src/Array/difference.php b/src/Array/difference.php index ad15a4c..22c779c 100644 --- a/src/Array/difference.php +++ b/src/Array/difference.php @@ -21,8 +21,8 @@ * * @category Array * - * @param array $array The array to inspect. - * @param mixed[] $values The values to exclude. + * @param array $array The array to inspect. + * @param array ...$values The values to exclude. * * @return array Returns the new array of filtered values. * @@ -35,4 +35,4 @@ function difference(array $array, array ...$values): array { return \array_values(\array_diff($array, ...$values)); -} \ No newline at end of file +} diff --git a/src/Array/differenceBy.php b/src/Array/differenceBy.php index ba7aabd..8c5a7ca 100644 --- a/src/Array/differenceBy.php +++ b/src/Array/differenceBy.php @@ -24,16 +24,17 @@ * * @category Array * - * @param array $array The array to inspect. - * @param array $values The values to exclude. - * @param callable iteratee The iteratee invoked per element. + * @param array $array The array to inspect. + * @param array ...$values The values to exclude. + * @param callable $iteratee The iteratee invoked per element. * * @return array Returns the new array of filtered values. * * @example - * + * * differenceBy([2.1, 1.2], [2.3, 3.4], 'floor') * // => [1.2] + * */ function differenceBy(array $array, ...$values): array { @@ -45,6 +46,7 @@ function differenceBy(array $array, ...$values): array return difference($array, ...$values); } + /** @var callable $iteratee */ $iteratee = \array_pop($values); $values = \array_map($iteratee, baseFlatten($values, 1, 'is_array', true, null)); @@ -65,4 +67,4 @@ function differenceBy(array $array, ...$values): array } return $result; -} \ No newline at end of file +} diff --git a/src/Array/differenceWith.php b/src/Array/differenceWith.php index d64ea02..912e117 100644 --- a/src/Array/differenceWith.php +++ b/src/Array/differenceWith.php @@ -23,11 +23,14 @@ * * @category Array * - * @param array $array The array to inspect. - * @param array[] $values The values to exclude. + * @param array $array The array to inspect. + * @param array ...$values The values to exclude. * @param callable $comparator The comparator invoked per element. * * @return array Returns the new array of filtered values. + * + * @throws \InvalidArgumentException + * * @example * * $objects = [[ 'x' => 1, 'y' => 2 ], [ 'x' => 2, 'y' => 1 ]] @@ -46,6 +49,7 @@ function differenceWith(array $array, ...$values): array return difference($array, ...$values); } + /** @var callable $comparator */ $comparator = \array_pop($values); $values = baseFlatten($values, 1, 'is_array', true, null); @@ -65,4 +69,4 @@ function differenceWith(array $array, ...$values): array } return $result; -} \ No newline at end of file +} diff --git a/src/Array/drop.php b/src/Array/drop.php index 4b3e53e..86bd80b 100644 --- a/src/Array/drop.php +++ b/src/Array/drop.php @@ -40,4 +40,4 @@ function drop(array $array, int $n = 1): array { return \array_slice($array, $n); -} \ No newline at end of file +} diff --git a/src/Array/dropRight.php b/src/Array/dropRight.php index 69d2177..3e578e3 100644 --- a/src/Array/dropRight.php +++ b/src/Array/dropRight.php @@ -45,4 +45,4 @@ function dropRight(array $array, int $n = 1): array } return \array_slice($array, 0, $count - $n); -} \ No newline at end of file +} diff --git a/src/Array/dropRightWhile.php b/src/Array/dropRightWhile.php index 76df132..b637215 100644 --- a/src/Array/dropRightWhile.php +++ b/src/Array/dropRightWhile.php @@ -47,4 +47,4 @@ function dropRightWhile(array $array, callable $predicate): array } return $array; -} \ No newline at end of file +} diff --git a/src/Array/dropWhile.php b/src/Array/dropWhile.php index e0f0d41..36020df 100644 --- a/src/Array/dropWhile.php +++ b/src/Array/dropWhile.php @@ -40,12 +40,14 @@ function dropWhile(array $array, callable $predicate): array $count = \count($array); $length = 0; $index = \key($array); - while ($length <= $count && $predicate($array[$index], $index, $array)) { + $value = \current($array); + while ($length <= $count && $predicate($value, $index, $array)) { array_shift($array); \reset($array); $length++; $index = \key($array); + $value = \current($array); } return $array; -} \ No newline at end of file +} diff --git a/src/Array/findIndex.php b/src/Array/findIndex.php index 04cb61d..36b9737 100644 --- a/src/Array/findIndex.php +++ b/src/Array/findIndex.php @@ -70,4 +70,4 @@ function findIndex(array $array, $predicate, int $fromIndex = null): int } return -1; -} \ No newline at end of file +} diff --git a/src/Array/findLastIndex.php b/src/Array/findLastIndex.php index 1bf43e2..a8f32f5 100644 --- a/src/Array/findLastIndex.php +++ b/src/Array/findLastIndex.php @@ -55,4 +55,4 @@ function findLastIndex(array $array, $predicate, int $fromIndex = null): int } return -1; -} \ No newline at end of file +} diff --git a/src/Array/flatten.php b/src/Array/flatten.php index 46eb08f..be6371b 100644 --- a/src/Array/flatten.php +++ b/src/Array/flatten.php @@ -22,11 +22,12 @@ * * @return array the new flattened array. * @example - * + * * flatten([1, [2, [3, [4]], 5]]) * // => [1, 2, [3, [4]], 5] + * */ function flatten(array $array = null): array { return baseFlatten($array, 1); -} \ No newline at end of file +} diff --git a/src/Array/flattenDeep.php b/src/Array/flattenDeep.php index 73c62f6..61a9933 100644 --- a/src/Array/flattenDeep.php +++ b/src/Array/flattenDeep.php @@ -29,5 +29,5 @@ */ function flattenDeep(array $array): array { - return baseFlatten($array, INF); -} \ No newline at end of file + return baseFlatten($array, PHP_INT_MAX); +} diff --git a/src/Array/flattenDepth.php b/src/Array/flattenDepth.php index 9f96b44..27e07e6 100644 --- a/src/Array/flattenDepth.php +++ b/src/Array/flattenDepth.php @@ -36,4 +36,4 @@ function flattenDepth(array $array, int $depth = 1): array { return baseFlatten($array, $depth); -} \ No newline at end of file +} diff --git a/src/Array/fromPairs.php b/src/Array/fromPairs.php index 6384743..7541e04 100644 --- a/src/Array/fromPairs.php +++ b/src/Array/fromPairs.php @@ -19,7 +19,8 @@ * * @param array $pairs The key-value pairs. * - * @return object the new object. + * @return \stdClass the new object. + * * @example * * fromPairs([['a', 1], ['b', 2]]) @@ -42,4 +43,4 @@ function fromPairs(array $pairs): \stdClass } return $result; -} \ No newline at end of file +} diff --git a/src/Array/head.php b/src/Array/head.php index ecdeb5f..b8bd6a8 100644 --- a/src/Array/head.php +++ b/src/Array/head.php @@ -41,4 +41,4 @@ function head(array $array) function first(array $array) { return head($array); -} \ No newline at end of file +} diff --git a/src/Array/indexOf.php b/src/Array/indexOf.php index ac1e8bc..db81dbe 100644 --- a/src/Array/indexOf.php +++ b/src/Array/indexOf.php @@ -56,4 +56,4 @@ function indexOf(array $array, $value, int $fromIndex = null): int } return -1; -} \ No newline at end of file +} diff --git a/src/Array/initial.php b/src/Array/initial.php index 9e7f1eb..8368454 100644 --- a/src/Array/initial.php +++ b/src/Array/initial.php @@ -20,12 +20,13 @@ * * @return array the slice of `array`. * @example - * + * * initial([1, 2, 3]) * // => [1, 2] + * */ function initial(array $array): array { \array_pop($array); return $array; -} \ No newline at end of file +} diff --git a/src/Array/intersection.php b/src/Array/intersection.php index 99d2cf1..6d05520 100644 --- a/src/Array/intersection.php +++ b/src/Array/intersection.php @@ -19,15 +19,17 @@ * * @category Array * - * @param array[] $arrays + * @param array ...$arrays * * @return array the new array of intersecting values. - * @example * + * @example + * * intersection([2, 1], [2, 3]) * // => [2] + * */ function intersection(array ...$arrays): array { return \array_intersect(...$arrays); -} \ No newline at end of file +} diff --git a/src/Array/intersectionBy.php b/src/Array/intersectionBy.php index e1177b3..44ca1c5 100644 --- a/src/Array/intersectionBy.php +++ b/src/Array/intersectionBy.php @@ -23,7 +23,7 @@ * * @category Array * - * @param array[] $arrays + * @param array ...$arrays * @param callable $iteratee The iteratee invoked per element. * * @return array the new array of intersecting values. @@ -42,4 +42,4 @@ function intersectionBy(...$arrays/*, callable $iteratee*/): array $iteratee = \array_pop($arrays); return baseIntersection($arrays, baseIteratee($iteratee)); -} \ No newline at end of file +} diff --git a/src/Array/intersectionWith.php b/src/Array/intersectionWith.php index a2eac29..be8dbd4 100644 --- a/src/Array/intersectionWith.php +++ b/src/Array/intersectionWith.php @@ -21,10 +21,11 @@ * * @category Array * - * @param array[] $arrays + * @param array ...$arrays * @param callable $comparator The comparator invoked per element. * * @return array the new array of intersecting values. + * * @example * * $objects = [[ 'x' => 1, 'y' => 2 ], [ 'x' => 2, 'y' => 1 ]] @@ -37,7 +38,7 @@ function intersectionWith(...$arrays /*, callable $comparator = null*/): array { $copy = $arrays; - $comparator = array_pop($arrays); + $comparator = \array_pop($arrays); if (!\is_callable($comparator)) { $arrays = $copy; @@ -45,4 +46,4 @@ function intersectionWith(...$arrays /*, callable $comparator = null*/): array } return baseIntersection($arrays, null, $comparator); -} \ No newline at end of file +} diff --git a/src/Array/last.php b/src/Array/last.php index 45f2628..77ff437 100644 --- a/src/Array/last.php +++ b/src/Array/last.php @@ -28,4 +28,4 @@ function last(array $array) { return \end($array) ?: null; -} \ No newline at end of file +} diff --git a/src/Array/lastIndexOf.php b/src/Array/lastIndexOf.php index 42498f8..f1ba610 100644 --- a/src/Array/lastIndexOf.php +++ b/src/Array/lastIndexOf.php @@ -50,4 +50,4 @@ function lastIndexOf(array $array, $value, int $fromIndex = null): int } return -1; -} \ No newline at end of file +} diff --git a/src/Array/nth.php b/src/Array/nth.php index 61c5842..4b037fa 100644 --- a/src/Array/nth.php +++ b/src/Array/nth.php @@ -22,7 +22,7 @@ * * @return mixed Returns the nth element of `array`. * @example - * + * * $array = ['a', 'b', 'c', 'd'] * * nth($array, 1) @@ -30,8 +30,9 @@ * * nth($array, -2) * // => 'c' + * */ function nth(array $array, int $n) { return \array_values($array)[$n < 0 ? \count($array) + $n : $n] ?? null; -} \ No newline at end of file +} diff --git a/src/Array/pull.php b/src/Array/pull.php index aca39a5..f585be2 100644 --- a/src/Array/pull.php +++ b/src/Array/pull.php @@ -21,10 +21,11 @@ * * @category Array * - * @param array $array The array to modify. - * @param mixed[] $values The values to remove. + * @param array $array The array to modify. + * @param array $values The values to remove. * * @return array + * * @example * * $array = ['a', 'b', 'c', 'a', 'b', 'c'] @@ -43,4 +44,4 @@ function pull(array &$array, ...$values): array $array = \array_values($array); // Re-index array return $array; -} \ No newline at end of file +} diff --git a/src/Array/pullAll.php b/src/Array/pullAll.php index b15b023..d2880b9 100644 --- a/src/Array/pullAll.php +++ b/src/Array/pullAll.php @@ -34,4 +34,4 @@ function pullAll(array &$array, array $values): array { return pull($array, ...$values); -} \ No newline at end of file +} diff --git a/src/Array/pullAllBy.php b/src/Array/pullAllBy.php index f871245..5e50a76 100644 --- a/src/Array/pullAllBy.php +++ b/src/Array/pullAllBy.php @@ -29,14 +29,15 @@ * * @return array `array`. * @example - * + * * $array = [[ 'x' => 1 ], [ 'x' => 2 ], [ 'x' => 3 ], [ 'x' => 1 ]] * * pullAllBy($array, [[ 'x' => 1 ], [ 'x' => 3 ]], 'x') * var_dump($array) * // => [[ 'x' => 2 ]] + * */ function pullAllBy(array &$array, array $values, $iteratee): array { return basePullAll($array, $values, baseIteratee($iteratee)); -} \ No newline at end of file +} diff --git a/src/Array/pullAllWith.php b/src/Array/pullAllWith.php index c5088bb..2c9c20b 100644 --- a/src/Array/pullAllWith.php +++ b/src/Array/pullAllWith.php @@ -39,4 +39,4 @@ function pullAllWith(array &$array, array $values, callable $comparator): array { return basePullAll($array, $values, null, $comparator); -} \ No newline at end of file +} diff --git a/src/Array/pullAt.php b/src/Array/pullAt.php index 6fd5f3f..19e53bc 100644 --- a/src/Array/pullAt.php +++ b/src/Array/pullAt.php @@ -53,4 +53,4 @@ function pullAt(array &$array, $indexes): array $array = \array_values($array); return $pulled; -} \ No newline at end of file +} diff --git a/src/Array/remove.php b/src/Array/remove.php index df7dae1..a939bf5 100644 --- a/src/Array/remove.php +++ b/src/Array/remove.php @@ -26,7 +26,7 @@ * * @return array the new array of removed elements. * @example - * + * * $array = [1, 2, 3, 4] * $evens = remove($array, function ($n) { return $n % 2 === 0; }) * @@ -35,6 +35,7 @@ * * var_dump($evens) * // => [2, 4] + * */ function remove(array &$array, callable $predicate): array { @@ -52,4 +53,4 @@ function remove(array &$array, callable $predicate): array $array = \array_values($array); // Re-index array return $resultArray; -} \ No newline at end of file +} diff --git a/src/Array/slice.php b/src/Array/slice.php index e882e83..33382db 100644 --- a/src/Array/slice.php +++ b/src/Array/slice.php @@ -25,4 +25,4 @@ function slice(array $array, int $start, int $end = null): array { return \array_slice($array, $start, $end); -} \ No newline at end of file +} diff --git a/src/Array/tail.php b/src/Array/tail.php new file mode 100644 index 0000000..085e287 --- /dev/null +++ b/src/Array/tail.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Gets all but the first element of `array`. + * + * @category Array + * + * @param array $array The array to query. + * + * @return array the slice of `array`. + * + * @example + * + * tail([1, 2, 3]) + * // => [2, 3] + * + */ +function tail(array $array): array +{ + array_shift($array); + + return $array; +} diff --git a/src/Array/take.php b/src/Array/take.php new file mode 100644 index 0000000..7f7d9dc --- /dev/null +++ b/src/Array/take.php @@ -0,0 +1,47 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a slice of `array` with `n` elements taken from the beginning. + * + * @category Array + * + * @param array $array The array to query. + * @param int $n The number of elements to take. + * + * @return array the slice of `array`. + * @example + * + * take([1, 2, 3]) + * // => [1] + * + * take([1, 2, 3], 2) + * // => [1, 2] + * + * take([1, 2, 3], 5) + * // => [1, 2, 3] + * + * take([1, 2, 3], 0) + * // => [] + * + */ +function take(array $array, int $n = 1): array +{ + if (1 > $n) { + return []; + } + + array_splice($array, $n); + + return $array; +} diff --git a/src/Array/takeRight.php b/src/Array/takeRight.php new file mode 100644 index 0000000..1655c6d --- /dev/null +++ b/src/Array/takeRight.php @@ -0,0 +1,45 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a slice of `array` with `n` elements taken from the end. + * + * @category Array + * + * @param array $array The array to query. + * @param int $n The number of elements to take. + * + * @return array the slice of `array`. + * @example + * + * takeRight([1, 2, 3]) + * // => [3] + * + * takeRight([1, 2, 3], 2) + * // => [2, 3] + * + * takeRight([1, 2, 3], 5) + * // => [1, 2, 3] + * + * takeRight([1, 2, 3], 0) + * // => [] + * + */ +function takeRight(array $array, int $n = 1): array +{ + if (1 > $n) { + return []; + } + + return array_slice($array, -$n); +} diff --git a/src/Array/takeRightWhile.php b/src/Array/takeRightWhile.php new file mode 100644 index 0000000..da898ba --- /dev/null +++ b/src/Array/takeRightWhile.php @@ -0,0 +1,52 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Creates a slice of `array` with elements taken from the end. Elements are + * taken until `predicate` returns falsey. The predicate is invoked with + * three arguments: (value, index, array). + * + * @category Array + * + * @param array $array The array to query. + * @param callable $predicate The function invoked per iteration. + * + * @return array the slice of `array`. + * + * @example + * + * $users = [ + * [ 'user' => 'barney', 'active' => false ], + * [ 'user' => 'fred', 'active' => true ], + * [ 'user' => 'pebbles', 'active' => true ] + * ]; + * + * takeRightWhile($users, function($value) { return $value['active']; }) + * // => objects for ['fred', 'pebbles'] + * + */ +function takeRightWhile(array $array, $predicate): array +{ + $iteratee = baseIteratee($predicate); + $result = []; + + foreach (array_reverse($array, true) as $index => $value) { + if ($iteratee($value, $index, $array)) { + $result[$index] = $value; + } + } + + return array_reverse($result); +} diff --git a/src/Array/takeWhile.php b/src/Array/takeWhile.php new file mode 100644 index 0000000..6e6218f --- /dev/null +++ b/src/Array/takeWhile.php @@ -0,0 +1,53 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Creates a slice of `array` with elements taken from the beginning. Elements + * are taken until `predicate` returns falsey. The predicate is invoked with + * three arguments: (value, index, array). + * + * @category Array + * + * @param array $array The array to query. + * @param mixed $predicate The function invoked per iteration. + * + * @return array the slice of `array`. + * + * @example + * + * $users = [ + * [ 'user' => 'barney', 'active' => true ], + * [ 'user' => 'fred', 'active' => true ], + * [ 'user' => 'pebbles', 'active' => false ] + * ] + * + * takeWhile($users, function($value) { return $value['active']; }) + * // => objects for ['barney', 'fred'] + * + */ +function takeWhile(array $array, $predicate): array +{ + $result = []; + + $iteratee = baseIteratee($predicate); + + foreach ($array as $index => $value) { + if ($iteratee($value, $index, $array)) { + $result[$index] = $value; + } + } + + return $result; +} diff --git a/src/Array/union.php b/src/Array/union.php new file mode 100644 index 0000000..e02f4f7 --- /dev/null +++ b/src/Array/union.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates an array of unique values, in order, from all given arrays using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @category Array + * + * @param array ...$arrays The arrays to inspect. + * + * @return array the new array of combined values. + * + * @example + * + * union([2], [1, 2]) + * // => [2, 1] + * + */ +function union(array ...$arrays): array +{ + return array_unique(array_merge(...$arrays)); +} diff --git a/src/Array/unionBy.php b/src/Array/unionBy.php new file mode 100644 index 0000000..af400c6 --- /dev/null +++ b/src/Array/unionBy.php @@ -0,0 +1,45 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseFlatten; +use function _\internal\baseIteratee; +use function _\internal\baseUniq; + +/** + * This method is like `union` except that it accepts `iteratee` which is + * invoked for each element of each `arrays` to generate the criterion by + * which uniqueness is computed. Result values are chosen from the first + * array in which the value occurs. The iteratee is invoked with one argument: + * (value). + * + * @category Array + * + * @param array ...$arrays The arrays to inspect. + * @param callable $iteratee The iteratee invoked per element. + * + * @return array the new array of combined values. + * + * @example + * + * unionBy([2.1], [1.2, 2.3], 'floor') + * // => [2.1, 1.2] + * + * // The `_::property` iteratee shorthand. + * unionBy([['x' => 1]], [['x' => 2], ['x' => 1]], 'x'); + * // => [['x' => 1], ['x' => 2]] + * + */ +function unionBy(...$arrays): array +{ + return baseUniq(baseFlatten($arrays, 1, '\is_array', true), baseIteratee(\array_pop($arrays))); +} diff --git a/src/Array/unionWith.php b/src/Array/unionWith.php new file mode 100644 index 0000000..56b2071 --- /dev/null +++ b/src/Array/unionWith.php @@ -0,0 +1,51 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseFlatten; +use function _\internal\baseUniq; + +/** + * This method is like `union` except that it accepts `comparator` which + * is invoked to compare elements of `arrays`. Result values are chosen from + * the first array in which the value occurs. The comparator is invoked + * with two arguments: (arrVal, othVal). + * + * @category Array + * + * @param array ...$arrays The arrays to inspect. + * @param callable $comparator The comparator invoked per element. + * + * @return array the new array of combined values. + * + * @throws \InvalidArgumentException + * + * @example + * + * $objects = [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1]] + * $others = [['x' => 1, 'y' => 1], ['x' => 1, 'y' => 2]] + * + * unionWith($objects, $others, '_::isEqual') + * // => [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1], ['x' => 1, 'y' => 1]] + * + */ +function unionWith(... $arrays): array +{ + /** @var callable $comparator */ + $comparator = \array_pop($arrays); + + if (!\is_callable($comparator)) { + throw new \InvalidArgumentException(__FUNCTION__.' expects the last value passed to be callable'); + } + + return baseUniq(baseFlatten($arrays, 1, '\is_array', true), null, $comparator); +} diff --git a/src/Array/uniq.php b/src/Array/uniq.php new file mode 100644 index 0000000..4aa7515 --- /dev/null +++ b/src/Array/uniq.php @@ -0,0 +1,35 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a duplicate-free version of an array, using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons, in which only the first occurrence of each element + * is kept. The order of result values is determined by the order they occur + * in the array. + * + * @category Array + * + * @param array $array The array to inspect. + * + * @return array the new duplicate free array. + * @example + * + * uniq([2, 1, 2]) + * // => [2, 1]s + * + */ +function uniq(array $array = []): array +{ + return \array_unique($array); +} diff --git a/src/Array/uniqBy.php b/src/Array/uniqBy.php new file mode 100644 index 0000000..012d557 --- /dev/null +++ b/src/Array/uniqBy.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; +use function _\internal\baseUniq; + +/** + * This method is like `uniq` except that it accepts `iteratee` which is + * invoked for each element in `array` to generate the criterion by which + * uniqueness is computed. The order of result values is determined by the + * order they occur in the array. The iteratee is invoked with one argument: + * (value). + * + * @category Array + * + * @param array $array The array to inspect. + * @param mixed $iteratee The iteratee invoked per element. + * + * @return array the new duplicate free array. + * @example + * + * uniqBy([2.1, 1.2, 2.3], 'floor') + * // => [2.1, 1.2] + * + */ +function uniqBy(array $array, $iteratee): array +{ + return baseUniq($array, baseIteratee($iteratee)); +} diff --git a/src/Array/uniqWith.php b/src/Array/uniqWith.php new file mode 100644 index 0000000..0f98a2c --- /dev/null +++ b/src/Array/uniqWith.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseUniq; + +/** + * This method is like `uniq` except that it accepts `comparator` which + * is invoked to compare elements of `array`. The order of result values is + * determined by the order they occur in the array.The comparator is invoked + * with two arguments: (arrVal, othVal). + * + * @category Array + * + * @param array $array The array to inspect. + * @param callable $comparator The comparator invoked per element. + * + * @return array the new duplicate free array. + * @example + * + * $objects = [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1], ['x' => 1, 'y' => 2]] + * + * uniqWith($objects, '_::isEqual') + * // => [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1]] + * + */ +function uniqWith(array $array, callable $comparator): array +{ + return baseUniq($array, null, $comparator); +} diff --git a/src/Array/unzip.php b/src/Array/unzip.php new file mode 100644 index 0000000..60c24e5 --- /dev/null +++ b/src/Array/unzip.php @@ -0,0 +1,55 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\arrayMap; +use function _\internal\baseProperty; +use function _\internal\baseTimes; + +/** + * This method is like `zip` except that it accepts an array of grouped + * elements and creates an array regrouping the elements to their pre-zip + * configuration. + * + * @category Array + * + * @param array $array The array of grouped elements to process. + * + * @return array the new array of regrouped elements. + * @example + * + * $zipped = zip(['a', 'b'], [1, 2], [true, false]) + * // => [['a', 1, true], ['b', 2, false]] + * + * unzip($zipped) + * // => [['a', 'b'], [1, 2], [true, false]] + * + */ +function unzip(array $array): array +{ + if (!\count($array)) { + return []; + } + + $length = 0; + $array = \array_filter($array, function ($group) use (&$length) { + if (\is_array($group)) { + $length = \max(\count($group), $length); + + return true; + } + }); + + return baseTimes($length, function ($index) use ($array) { + return arrayMap($array, baseProperty($index)); + }); +} diff --git a/src/Array/unzipWith.php b/src/Array/unzipWith.php new file mode 100644 index 0000000..17365f8 --- /dev/null +++ b/src/Array/unzipWith.php @@ -0,0 +1,51 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\arrayMap; + +/** + * This method is like `unzip` except that it accepts `iteratee` to specify + * how regrouped values should be combined. The iteratee is invoked with the + * elements of each group: (...group). + * + * @category Array + * + * @param array $array The array of grouped elements to process. + * @param callable|null $iteratee The function to combine regrouped values. + * + * @return array the new array of regrouped elements. + * + * @example + * + * $zipped = zip([1, 2], [10, 20], [100, 200]) + * // => [[1, 10, 100], [2, 20, 200]] + * + * unzipWith(zipped, '_::add') + * // => [3, 30, 300] + * + */ +function unzipWith(array $array, ?callable $iteratee = null): array +{ + if (!\count($array)) { + return []; + } + + $result = unzip($array); + if (!is_callable($iteratee)) { + return $result; + } + + return arrayMap($result, function ($group) use ($iteratee) { + return $iteratee(...$group); + }); +} diff --git a/src/Array/without.php b/src/Array/without.php new file mode 100644 index 0000000..7976e97 --- /dev/null +++ b/src/Array/without.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseRest; + +/** + * Creates an array excluding all given values using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * **Note:** Unlike `pull`, this method returns a new array. + * + * @category Array + * + * @param array $array The array to inspect. + * @param array $values The values to exclude. + * + * @return array the new array of filtered values. + * @example + * + * without([2, 1, 2, 3], 1, 2) + * // => [3] + * + */ +function without(array $array, ...$values): array +{ + return baseRest('\_\difference')($array, ...$values); +} diff --git a/src/Array/zip.php b/src/Array/zip.php new file mode 100644 index 0000000..24c8cbd --- /dev/null +++ b/src/Array/zip.php @@ -0,0 +1,35 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseRest; + +/** + * Creates an array of grouped elements, the first of which contains the + * first elements of the given arrays, the second of which contains the + * second elements of the given arrays, and so on. + * + * @category Array + * + * @param array ...$arrays The arrays to process. + * + * @return array the new array of grouped elements. + * @example + * + * zip(['a', 'b'], [1, 2], [true, false]) + * // => [['a', 1, true], ['b', 2, false]] + * + */ +function zip(array ...$arrays): array +{ + return baseRest('\_\unzip')(...$arrays); +} diff --git a/src/Array/zipObject.php b/src/Array/zipObject.php new file mode 100644 index 0000000..69fd505 --- /dev/null +++ b/src/Array/zipObject.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * This method is like `fromPairs` except that it accepts two arrays, + * one of property identifiers and one of corresponding values. + * + * @category Array + * + * @param array $props The property identifiers. + * @param array $values The property values. + * + * @return object the new object. + * + * @example + * + * zipObject(['a', 'b'], [1, 2]) + * /* => object(stdClass)#210 (2) { + * ["a"] => int(1) + * ["b"] => int(2) + * } + * *\/ + * + */ +function zipObject(array $props = [], array $values = []) +{ + $result = new \stdClass; + $index = -1; + $length = \count($props); + $props = \array_values($props); + $values = \array_values($values); + + while (++$index < $length) { + $value = $values[$index] ?? null; + $result->{$props[$index]} = $value; + } + + return $result; +} diff --git a/src/Array/zipObjectDeep.php b/src/Array/zipObjectDeep.php new file mode 100644 index 0000000..dd8defc --- /dev/null +++ b/src/Array/zipObjectDeep.php @@ -0,0 +1,59 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseSet; + +/** + * This method is like `zipObject` except that it supports property paths. + * + * @category Array + * + * @param array $props The property identifiers. + * @param array $values The property values. + * + * @return \stdClass the new object. + * + * @example + * + * zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]) + * /* => class stdClass#20 (1) { + * public $a => class stdClass#19 (1) { + * public $b => + * array(2) { + * [0] => class stdClass#17 (1) { + * public $c => int(1) + * } + * [1] => class stdClass#18 (1) { + * public $d => int(2) + * } + * } + * } + * } + * *\/ + * + */ +function zipObjectDeep(array $props = [], array $values = []): \stdClass +{ + $result = new \stdClass; + $index = -1; + $length = \count($props); + $props = \array_values($props); + $values = \array_values($values); + + while (++$index < $length) { + $value = $values[$index] ?? null; + baseSet($result, $props[$index], $value); + } + + return $result; +} diff --git a/src/Array/zipWith.php b/src/Array/zipWith.php new file mode 100644 index 0000000..58727f0 --- /dev/null +++ b/src/Array/zipWith.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * This method is like `zip` except that it accepts `iteratee` to specify + * how grouped values should be combined. The iteratee is invoked with the + * elements of each group: (...group). + * + * @category Array + * + * @param array ...$arrays The arrays to process. + * @param callable $iteratee The function to combine grouped values. + * + * @return array the new array of grouped elements. + * + * @example + * + * zipWith([1, 2], [10, 20], [100, 200], function($a, $b, $c) { return $a + $b + $c; }) + * // => [111, 222] + * + */ +function zipWith(...$arrays): array +{ + /** @var callable|null $iteratee */ + $iteratee = \is_callable(\end($arrays)) ? \array_pop($arrays) : null; + + return unzipWith($arrays, $iteratee); +} diff --git a/src/CacheInterface.php b/src/CacheInterface.php new file mode 100644 index 0000000..096a0b7 --- /dev/null +++ b/src/CacheInterface.php @@ -0,0 +1,27 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +interface CacheInterface +{ + public function set($key, $value): CacheInterface; + + public function get($key); + + public function has($key): bool; + + public function clear(); + + public function delete($key); + + public function getSize(); +} diff --git a/src/Collection/countBy.php b/src/Collection/countBy.php new file mode 100644 index 0000000..ae2dd51 --- /dev/null +++ b/src/Collection/countBy.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\createAggregator; + +/** + * Creates an array composed of keys generated from the results of running + * each element of `collection` through `iteratee`. The corresponding value of + * each key is the number of times the key was returned by `iteratee`. The + * iteratee is invoked with one argument: (value). + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $iteratee The iteratee to transform keys. + * + * @return array Returns the composed aggregate object. + * @example + * + * countBy([6.1, 4.2, 6.3], 'floor'); + * // => ['6' => 2, '4' => 1] + * + * // The `property` iteratee shorthand. + * countBy(['one', 'two', 'three'], 'strlen'); + * // => ['3' => 2, '5' => 1] + * + */ +function countBy(iterable $collection, callable $iteratee): array +{ + return createAggregator(function ($result, $key, $value) { + if (!isset($result[$value])) { + $result[$value] = 0; + } + + $result[$value]++; + + return $result; + })($collection, $iteratee); +} diff --git a/src/Collection/each.php b/src/Collection/each.php index 123009b..a78a041 100644 --- a/src/Collection/each.php +++ b/src/Collection/each.php @@ -22,11 +22,11 @@ * * @category Collection * - * @param array|object $collection The collection to iterate over. - * @param callable $iteratee The function invoked per iteration. + * @param array|iterable|object $collection The collection to iterate over. + * @param callable $iteratee The function invoked per iteration. * * @return array|object Returns `collection`. - * @related forEachRight, forIn, forInRight, forOwn, forOwnRight + * * @example * * each([1, 2], function ($value) { echo $value; }) @@ -38,12 +38,9 @@ */ function each($collection, callable $iteratee) { - $values = $collection; - - if (\is_object($collection)) { - $values = \get_object_vars($collection); - } + $values = \is_object($collection) ? \get_object_vars($collection) : $collection; + /** @var array $values */ foreach ($values as $index => $value) { if (false === $iteratee($value, $index, $collection)) { break; @@ -51,4 +48,4 @@ function each($collection, callable $iteratee) } return $collection; -} \ No newline at end of file +} diff --git a/src/Collection/eachRight.php b/src/Collection/eachRight.php new file mode 100644 index 0000000..94d3130 --- /dev/null +++ b/src/Collection/eachRight.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * This method is like `each` except that it iterates over elements of + * `collection` from right to left. + * + * @category Collection + * + * @param array|iterable|object $collection The collection to iterate over. + * @param callable $iteratee The function invoked per iteration. + * + * @return array|object Returns `collection`. + * @example + * + * eachRight([1, 2], function($value) { echo $value; }) + * // => Echoes `2` then `1`. + * + */ +function eachRight($collection, callable $iteratee) +{ + $values = \is_object($collection) ? \get_object_vars($collection) : $collection; + + foreach (\array_reverse($values, true) as $index => $value) { + if (false === $iteratee($value, $index, $collection)) { + break; + } + } + + return $collection; +} diff --git a/src/Collection/every.php b/src/Collection/every.php new file mode 100644 index 0000000..6ee989a --- /dev/null +++ b/src/Collection/every.php @@ -0,0 +1,67 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Checks if `predicate` returns truthy for **all** elements of `array`. + * Iteration is stopped once `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * **Note:** This method returns `true` for + * [empty arrays](https://en.wikipedia.org/wiki/Empty_set) because + * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of + * elements of empty arrays. + * + * @category Array + * + * @param iterable $collection The array to iterate over. + * @param callable $predicate The function invoked per iteration. + * + * @return bool `true` if all elements pass the predicate check, else `false`. + * @example + * + * every([true, 1, null, 'yes'], function ($value) { return is_bool($value);}) + * // => false + * + * $users = [ + * ['user' => 'barney', 'age' => 36, 'active' => false], + * ['user' => 'fred', 'age' => 40, 'active' => false], + * ]; + * + * // The `matches` iteratee shorthand. + * $this->assertFalse(every($users, ['user' => 'barney', 'active' => false])); + * // false + * + * // The `matchesProperty` iteratee shorthand. + * $this->assertTrue(every($users, ['active', false])); + * // true + * + * // The `property` iteratee shorthand. + * $this->assertFalse(every($users, 'active')); + * //false + * + * + */ +function every(iterable $collection, $predicate): bool +{ + $iteratee = baseIteratee($predicate); + + foreach ($collection as $key => $value) { + if (!$iteratee($value, $key, $collection)) { + return false; + } + } + + return true; +} diff --git a/src/Collection/filter.php b/src/Collection/filter.php new file mode 100644 index 0000000..3c3b9bf --- /dev/null +++ b/src/Collection/filter.php @@ -0,0 +1,66 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Iterates over elements of `array`, returning an array of all elements + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index, array). + * + * **Note:** Unlike `remove`, this method returns a new array. + * + * @category Collection + * + * @param iterable $array The array to iterate over. + * @param callable $predicate The function invoked per iteration. + * + * @return array the new filtered array. + * + * @example + * + * $users = [ + * [ 'user' => 'barney', 'age' => 36, 'active' => true], + * [ 'user' => 'fred', 'age' => 40, 'active' => false] + * ]; + * + * filter($users, function($o) { return !$o['active']; }); + * // => objects for ['fred'] + * + * // The `matches` iteratee shorthand. + * filter($users, ['age' => 36, 'active' => true]); + * // => objects for ['barney'] + * + * // The `matchesProperty` iteratee shorthand. + * filter($users, ['active', false]); + * // => objects for ['fred'] + * + * // The `property` iteratee shorthand. + * filter($users, 'active'); + * // => objects for ['barney'] + * + */ +function filter(iterable $array, $predicate = null): array +{ + $iteratee = baseIteratee($predicate); + + $result = \array_filter( + \is_array($array) ? $array : \iterator_to_array($array), + function ($value, $key) use ($array, $iteratee) { + return $iteratee($value, $key, $array); + }, + \ARRAY_FILTER_USE_BOTH + ); + + return \array_values($result); +} diff --git a/src/Collection/find.php b/src/Collection/find.php new file mode 100644 index 0000000..e38807f --- /dev/null +++ b/src/Collection/find.php @@ -0,0 +1,64 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Iterates over elements of `collection`, returning the first element + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index|key, collection). + * + * @category Collection + * + * @param iterable $collection The collection to inspect. + * @param callable $predicate The function invoked per iteration. + * @param int $fromIndex The index to search from. + * + * @return mixed Returns the matched element, else `null`. + * + * @example + * + * $users = [ + * ['user' => 'barney', 'age' => 36, 'active' => true], + * ['user' => 'fred', 'age' => 40, 'active' => false], + * ['user' => 'pebbles', 'age' => 1, 'active' => true] + * ]; + * + * find($users, function($o) { return $o['age'] < 40; }); + * // => object for 'barney' + * + * // The `matches` iteratee shorthand. + * find($users, ['age' => 1, 'active' => true]); + * // => object for 'pebbles' + * + * // The `matchesProperty` iteratee shorthand. + * find($users, ['active', false]); + * // => object for 'fred' + * + * // The `property` iteratee shorthand. + * find($users, 'active'); + * // => object for 'barney' + * + */ +function find(iterable $collection, $predicate = null, int $fromIndex = 0) +{ + $iteratee = baseIteratee($predicate); + + foreach (\array_slice(\is_array($collection) ? $collection : \iterator_to_array($collection), $fromIndex) as $key => $value) { + if ($iteratee($value, $key, $collection)) { + return $value; + } + } + + return null; +} diff --git a/src/Collection/findLast.php b/src/Collection/findLast.php new file mode 100644 index 0000000..2e7cb05 --- /dev/null +++ b/src/Collection/findLast.php @@ -0,0 +1,45 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * This method is like `find` except that it iterates over elements of + * `collection` from right to left. + * + * @category Collection + * + * @param iterable $collection The collection to inspect. + * @param callable $predicate The function invoked per iteration. + * @param int $fromIndex The index to search from. + * + * @return mixed Returns the matched element, else `undefined`. + * + * @example + * + * findLast([1, 2, 3, 4], function ($n) { return $n % 2 == 1; }) + * // => 3 + * + */ +function findLast(iterable $collection, $predicate = null, int $fromIndex = 0) +{ + $iteratee = baseIteratee($predicate); + + foreach (\array_slice(\array_reverse(\is_array($collection) ? $collection : \iterator_to_array($collection), true), $fromIndex) as $key => $value) { + if ($iteratee($value, $key, $collection)) { + return $value; + } + } + + return null; +} diff --git a/src/Collection/flatMap.php b/src/Collection/flatMap.php new file mode 100644 index 0000000..889cead --- /dev/null +++ b/src/Collection/flatMap.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseFlatten; + +/** + * Creates a flattened array of values by running each element in `collection` + * through `iteratee` and flattening the mapped results. The iteratee is invoked + * with three arguments: (value, index|key, collection). + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $iteratee The function invoked per iteration. + * + * @return array the new flattened array. + * + * @example + * + * function duplicate($n) { + * return [$n, $n] + * } + * + * flatMap([1, 2], 'duplicate') + * // => [1, 1, 2, 2] + * + */ +function flatMap(iterable $collection, callable $iteratee): array +{ + return baseFlatten(map($collection, $iteratee), 1); +} diff --git a/src/Collection/flatMapDeep.php b/src/Collection/flatMapDeep.php new file mode 100644 index 0000000..5c09dd7 --- /dev/null +++ b/src/Collection/flatMapDeep.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseFlatten; + +/** + * This method is like `flatMap` except that it recursively flattens the + * mapped results. + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $iteratee The function invoked per iteration. + * + * @return array Returns the new flattened array. + * @example + * + * function duplicate($n) { + * return [[[$n, $n]]]; + * } + * + * flatMapDeep([1, 2], 'duplicate'); + * // => [1, 1, 2, 2] + * + */ +function flatMapDeep(iterable $collection, callable $iteratee): array +{ + return baseFlatten(map($collection, $iteratee), \PHP_INT_MAX); +} diff --git a/src/Collection/flatMapDepth.php b/src/Collection/flatMapDepth.php new file mode 100644 index 0000000..4bdd5ad --- /dev/null +++ b/src/Collection/flatMapDepth.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseFlatten; + +/** + * This method is like `flatMap` except that it recursively flattens the + * mapped results up to `depth` times. + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $iteratee The function invoked per iteration. + * @param int $depth The maximum recursion depth. + * + * @return array the new flattened array. + * @example + * + * function duplicate($n) { + * return [[[$n, $n]]] + * } + * + * flatMapDepth([1, 2], 'duplicate', 2) + * // => [[1, 1], [2, 2]] + * + */ +function flatMapDepth(iterable $collection, callable $iteratee, int $depth = 1): array +{ + return baseFlatten(map($collection, $iteratee), $depth); +} diff --git a/src/Collection/groupBy.php b/src/Collection/groupBy.php new file mode 100644 index 0000000..2295d56 --- /dev/null +++ b/src/Collection/groupBy.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\createAggregator; + +/** + * Creates an array composed of keys generated from the results of running + * each element of `collection` through `iteratee`. The order of grouped values + * is determined by the order they occur in `collection`. The corresponding + * value of each key is an array of elements responsible for generating the + * key. The iteratee is invoked with one argument: (value). + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $iteratee The iteratee to transform keys. + * + * @return array Returns the composed aggregate object. + * @example + * + * groupBy([6.1, 4.2, 6.3], 'floor'); + * // => ['6' => [6.1, 6.3], '4' => [4.2]] + * + * groupBy(['one', 'two', 'three'], 'strlen'); + * // => ['3' => ['one', 'two'], '5' => ['three']] + * + */ +function groupBy(iterable $collection, $iteratee): array +{ + return createAggregator(function ($result, $value, $key) { + if (!isset($result[$key])) { + $result[$key] = []; + } + + $result[$key][] = $value; + + return $result; + })($collection, $iteratee); +} diff --git a/src/Collection/invokeMap.php b/src/Collection/invokeMap.php new file mode 100644 index 0000000..b5b4ddd --- /dev/null +++ b/src/Collection/invokeMap.php @@ -0,0 +1,51 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseInvoke; +use function _\internal\baseRest; + +/** + * Invokes the method at `path` of each element in `collection`, returning + * an array of the results of each invoked method. Any additional arguments + * are provided to each invoked method. If `path` is a function, it's invoked + * for, and `this` bound to, each element in `collection`. + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param array|callable|string $path The path of the method to invoke or the function invoked per iteration. + * @param array $args The arguments to invoke each method with. + * + * @return array the array of results. + * @example + * + * invokeMap([[5, 1, 7], [3, 2, 1]], function($result) { sort($result); return $result;}) + * // => [[1, 5, 7], [1, 2, 3]] + * + * invokeMap([123, 456], 'str_split') + * // => [['1', '2', '3'], ['4', '5', '6']] + * + */ +function invokeMap(iterable $collection, $path, array $args = []): array +{ + return baseRest(function ($collection, $path, $args) { + $isFunc = \is_callable($path); + $result = []; + + each($collection, function ($value) use (&$result, $isFunc, $path, $args) { + $result[] = $isFunc ? $path($value, ...$args) : baseInvoke($value, $path, $args); + }); + + return $result; + })($collection, $path, ...$args); +} diff --git a/src/Collection/keyBy.php b/src/Collection/keyBy.php new file mode 100644 index 0000000..229206b --- /dev/null +++ b/src/Collection/keyBy.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\createAggregator; + +/** + * Creates an object composed of keys generated from the results of running + * each element of `collection` through `iteratee`. The corresponding value of + * each key is the last element responsible for generating the key. The + * iteratee is invoked with one argument: (value). + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $iteratee The iteratee to transform keys. + * + * @return array the composed aggregate object. + * @example + * + * $array = [ + * ['direction' => 'left', 'code' => 97], + * ['direction' => 'right', 'code' => 100], + * ]; + * + * keyBy($array, function ($o) { return \chr($o['code']); }) + * // => ['a' => ['direction' => 'left', 'code' => 97], 'd' => ['direction' => 'right', 'code' => 100]] + * + * keyBy($array, 'direction'); + * // => ['left' => ['direction' => 'left', 'code' => 97], 'right' => ['direction' => 'right', 'code' => 100]] + * + */ +function keyBy(iterable $collection, $iteratee): array +{ + return createAggregator(function ($result, $value, $key) { + $result[$key] = $value; + + return $result; + })($collection, $iteratee); +} diff --git a/src/Collection/map.php b/src/Collection/map.php index 3f37f31..5b6fb6c 100644 --- a/src/Collection/map.php +++ b/src/Collection/map.php @@ -61,9 +61,9 @@ function map($collection, $iteratee): array if (\is_array($collection)) { $values = $collection; - } else if ($collection instanceof \Traversable) { + } elseif ($collection instanceof \Traversable) { $values = \iterator_to_array($collection); - } else if (\is_object($collection)) { + } elseif (\is_object($collection)) { $values = \get_object_vars($collection); } @@ -72,4 +72,4 @@ function map($collection, $iteratee): array return \array_map(function ($value, $index) use ($callable, $collection) { return $callable($value, $index, $collection); }, $values, \array_keys($values)); -} \ No newline at end of file +} diff --git a/src/Collection/orderBy.php b/src/Collection/orderBy.php new file mode 100644 index 0000000..346fb75 --- /dev/null +++ b/src/Collection/orderBy.php @@ -0,0 +1,50 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseOrderBy; + +/** + * This method is like `sortBy` except that it allows specifying the sort + * orders of the iteratees to sort by. If `orders` is unspecified, all values + * are sorted in ascending order. Otherwise, specify an order of "desc" for + * descending or "asc" for ascending sort order of corresponding values. + * + * @category Collection + * + * @param iterable|null $collection The collection to iterate over. + * @param array[]|callable[]|string[] $iteratee The iteratee(s) to sort by. + * @param string[] $orders The sort orders of `iteratees`. + * + * @return array the new sorted array. + * @example + * + * $users = [ + * ['user' => 'fred', 'age' => 48], + * ['user' => 'barney', 'age' => 34], + * ['user' => 'fred', 'age' => 40], + * ['user' => 'barney', 'age' => 36] + * ] + * + * // Sort by `user` in ascending order and by `age` in descending order. + * orderBy($users, ['user', 'age'], ['asc', 'desc']) + * // => [['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]] + * + */ +function orderBy(?iterable $collection, array $iteratee, array $orders): array +{ + if (null === $collection) { + return []; + } + + return baseOrderBy($collection, $iteratee, $orders); +} diff --git a/src/Collection/partition.php b/src/Collection/partition.php new file mode 100644 index 0000000..ac4eae5 --- /dev/null +++ b/src/Collection/partition.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\createAggregator; + +/** + * Creates an array of elements split into two groups, the first of which + * contains elements `predicate` returns truthy for, the second of which + * contains elements `predicate` returns falsey for. The predicate is + * invoked with one argument: (value). + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $predicate The function invoked per iteration. + * + * @return array the array of grouped elements. + * @example + * + * $users = [ + * ['user' => 'barney', 'age' => 36, 'active' => false], + * ['user' => 'fred', 'age' => 40, 'active' => true], + * ['user' => 'pebbles', 'age' => 1, 'active' => false] + * ]; + * + * partition($users, function($user) { return $user['active']; }) + * // => objects for [['fred'], ['barney', 'pebbles']] + * + */ +function partition(iterable $collection, $predicate = null): array +{ + return createAggregator(function ($result, $value, $key) { + $result[$key ? 0 : 1][] = $value; + + return $result; + }, function () { + return [[], []]; + })($collection, $predicate); +} diff --git a/src/Collection/reduce.php b/src/Collection/reduce.php new file mode 100644 index 0000000..703be86 --- /dev/null +++ b/src/Collection/reduce.php @@ -0,0 +1,71 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Reduces `collection` to a value which is the accumulated result of running + * each element in `collection` thru `iteratee`, where each successive + * invocation is supplied the return value of the previous. If `accumulator` + * is not given, the first element of `collection` is used as the initial + * value. The iteratee is invoked with four arguments: + * (accumulator, value, index|key, collection). + * + * Many lodash methods are guarded to work as iteratees for methods like + * `reduce`, `reduceRight`, and `transform`. + * + * The guarded methods are: + * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, + * and `sortBy` + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param mixed $iteratee The function invoked per iteration. + * @param mixed $accumulator The initial value. + * + * @return mixed Returns the accumulated value. + * + * @example + * + * reduce([1, 2], function($sum, $n) { return $sum + $n; }, 0) + * // => 3 + * + * reduce(['a' => 1, 'b' => 2, 'c' => 1], function ($result, $value, $key) { + * if (!isset($result[$value])) { + * $result[$value] = []; + * } + * $result[$value][] = $key; + * + * return $result; + * }, []) + * // => ['1' => ['a', 'c'], '2' => ['b']] (iteration order is not guaranteed) + * + */ +function reduce(iterable $collection, $iteratee, $accumulator = null) +{ + $func = function (iterable $array, $iteratee, $accumulator, $initAccum = null) { + $length = \count(\is_array($array) ? $array : \iterator_to_array($array)); + + if ($initAccum && $length) { + $accumulator = \current($array); + } + foreach ($array as $key => $value) { + $accumulator = $iteratee($accumulator, $value, $key, $array); + } + + return $accumulator; + }; + + return $func($collection, baseIteratee($iteratee), $accumulator, null === $accumulator); +} diff --git a/src/Collection/reduceRight.php b/src/Collection/reduceRight.php new file mode 100644 index 0000000..8a51c16 --- /dev/null +++ b/src/Collection/reduceRight.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; +use function _\internal\baseReduce; + +/** + * This method is like `reduce` except that it iterates over elements of + * `collection` from right to left. + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param mixed $iteratee The function invoked per iteration. + * @param mixed $accumulator The initial value. + * + * @return mixed Returns the accumulated value. + * + * @example + * + * $array = [[0, 1], [2, 3], [4, 5]]; + * + * reduceRight(array, (flattened, other) => flattened.concat(other), []) + * // => [4, 5, 2, 3, 0, 1] + * + */ +function reduceRight(iterable $collection, $iteratee, $accumulator = null) +{ + return baseReduce(\array_reverse($collection instanceof \Traversable ? \iterator_to_array($collection, true) : $collection, true), baseIteratee($iteratee), $accumulator, null === $accumulator); +} diff --git a/src/Collection/reject.php b/src/Collection/reject.php new file mode 100644 index 0000000..1a63722 --- /dev/null +++ b/src/Collection/reject.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * The opposite of `filter` this method returns the elements of `collection` + * that `predicate` does **not** return truthy for. + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable $predicate The function invoked per iteration. + * + * @return array the new filtered array. + * + * @example + * + * $users = [ + * ['user' => 'barney', 'active' => true], + * ['user' => 'fred', 'active' => false] + * ] + * + * reject($users, 'active') + * // => objects for ['fred'] + * + */ +function reject(iterable $collection, $predicate = null): array +{ + return filter($collection, negate(baseIteratee($predicate))); +} diff --git a/src/Collection/sample.php b/src/Collection/sample.php new file mode 100644 index 0000000..43dec1f --- /dev/null +++ b/src/Collection/sample.php @@ -0,0 +1,35 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Gets a random element from `array`. + * + * @category Array + * + * @param array $array The array to sample. + * + * @return mixed Returns the random element. + * + * @example + * + * sample([1, 2, 3, 4]) + * // => 2 + * + */ +function sample(array $array) +{ + /** @var string|int $key */ + $key = \array_rand($array, 1); + + return $array[$key]; +} diff --git a/src/Collection/sampleSize.php b/src/Collection/sampleSize.php new file mode 100644 index 0000000..199842a --- /dev/null +++ b/src/Collection/sampleSize.php @@ -0,0 +1,43 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Gets `n` random elements at unique keys from `array` up to the + * size of `array`. + * + * @category Array + * + * @param array $array The array to sample. + * @param int $n The number of elements to sample. + * + * @return array the random elements. + * @example + * + * sampleSize([1, 2, 3], 2) + * // => [3, 1] + * + * sampleSize([1, 2, 3], 4) + * // => [2, 3, 1] + * + */ +function sampleSize(array $array, int $n = 1): array +{ + $result = []; + $count = \count($array); + + foreach ((array) \array_rand($array, $n > $count ? $count : $n) as $index) { + $result[] = $array[$index]; + } + + return $result; +} diff --git a/src/Collection/shuffle.php b/src/Collection/shuffle.php new file mode 100644 index 0000000..5dc4242 --- /dev/null +++ b/src/Collection/shuffle.php @@ -0,0 +1,33 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates an array of shuffled values + * + * @category Array + * + * @param array $array The array to shuffle. + * + * @return array the new shuffled array. + * @example + * + * shuffle([1, 2, 3, 4]) + * // => [4, 1, 3, 2] + * + */ +function shuffle(array $array = []): array +{ + \shuffle($array); + + return $array; +} diff --git a/src/Collection/size.php b/src/Collection/size.php new file mode 100644 index 0000000..5116e9e --- /dev/null +++ b/src/Collection/size.php @@ -0,0 +1,54 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Gets the size of `collection` by returning its length for array + * values or the number of public properties for objects. + * + * @category Collection + * + * @param array|object|string $collection The collection to inspect. + * + * @return int Returns the collection size. + * @example + * + * size([1, 2, 3]); + * // => 3 + * + * size(new class { public $a = 1; public $b = 2; private $c = 3; }); + * // => 2 + * + * size('pebbles'); + * // => 7 + * + */ +function size($collection): int +{ + if (\is_string($collection)) { + return \strlen($collection); + } + + if (\is_array($collection) || $collection instanceof \Countable) { + return \count($collection); + } + + if ($collection instanceof \Traversable) { + return \count(\iterator_to_array($collection)); + } + + if (\is_object($collection)) { + return \count(\get_object_vars($collection)); + } + + return 0; +} diff --git a/src/Collection/some.php b/src/Collection/some.php new file mode 100644 index 0000000..02d8b9d --- /dev/null +++ b/src/Collection/some.php @@ -0,0 +1,61 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * Checks if `predicate` returns truthy for **any** element of `collection`. + * Iteration is stopped once `predicate` returns truthy. The predicate is + * invoked with three arguments: (value, index|key, collection). + * + * @category Collection + * + * @param iterable $collection The collection to iterate over. + * @param callable|string|array $predicate The function invoked per iteration. + * + * @return boolean Returns `true` if any element passes the predicate check, else `false`. + * @example + * + * some([null, 0, 'yes', false], , function ($value) { return \is_bool($value); })); + * // => true + * + * $users = [ + * ['user' => 'barney', 'active' => true], + * ['user' => 'fred', 'active' => false] + * ]; + * + * // The `matches` iteratee shorthand. + * some($users, ['user' => 'barney', 'active' => false ]); + * // => false + * + * // The `matchesProperty` iteratee shorthand. + * some($users, ['active', false]); + * // => true + * + * // The `property` iteratee shorthand. + * some($users, 'active'); + * // => true + * + */ +function some(iterable $collection, $predicate = null): bool +{ + $iteratee = baseIteratee($predicate); + + foreach ($collection as $key => $value) { + if ($iteratee($value, $key, $collection)) { + return true; + } + } + + return false; +} diff --git a/src/Collection/sortBy.php b/src/Collection/sortBy.php index b87d2b6..0e2e41c 100644 --- a/src/Collection/sortBy.php +++ b/src/Collection/sortBy.php @@ -21,8 +21,8 @@ * * @category Collection * - * @param array|object $collection The collection to iterate over. - * @param callable|callable[] $iteratees The iteratees to sort by. + * @param array|object|null $collection The collection to iterate over. + * @param callable|callable[] $iteratees The iteratees to sort by. * * @return array Returns the new sorted array. * @example @@ -41,13 +41,13 @@ * // => [['user' => 'barney', 'age' => 34], ['user' => 'barney', 'age' => 36], ['user' => 'fred', 'age' => 40], ['user' => 'fred', 'age' => 48]] * */ -function sortBy($collection, $iteratees) +function sortBy($collection, $iteratees): array { if (null === $collection) { return []; }; - if (\is_callable($iteratees)) { + if (\is_callable($iteratees) || !\is_iterable($iteratees)) { $iteratees = [$iteratees]; } @@ -58,15 +58,15 @@ function sortBy($collection, $iteratees) $iteratees = [$iteratees[0]]; }*/ - $result = $collection; + $result = \is_object($collection) ? \get_object_vars($collection) : $collection; foreach ($iteratees as $callable) { usort($result, function ($a, $b) use ($callable) { $iteratee = baseIteratee($callable); - return $iteratee($a) <=> $iteratee($b); + return $iteratee($a, $b) <=> $iteratee($b, $a); }); } return $result; -} \ No newline at end of file +} diff --git a/src/Date/now.php b/src/Date/now.php index 7e536e8..d619283 100644 --- a/src/Date/now.php +++ b/src/Date/now.php @@ -27,4 +27,4 @@ function now(): int { return (int) (\microtime(true) * 1000); -} \ No newline at end of file +} diff --git a/src/Function/after.php b/src/Function/after.php new file mode 100644 index 0000000..6a972c1 --- /dev/null +++ b/src/Function/after.php @@ -0,0 +1,46 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * The opposite of `before`; this method creates a function that invokes + * `func` once it's called `n` or more times. + * + * @category Function + * + * @param int $n The number of calls before `func` is invoked. + * @param Callable $func The function to restrict. + * + * @return Callable Returns the new restricted function. + * + * @example + * + * $saves = ['profile', 'settings']; + * + * $done = after(count($saves), function() { + * echo 'done saving!'; + * }); + * + * forEach($saves, function($type) use($done) { + * asyncSave([ 'type' => $type, 'complete' => $done ]); + * }); + * // => Prints 'done saving!' after the two async saves have completed. + * + */ +function after(int $n, callable $func): callable +{ + return function (...$args) use (&$n, $func) { + if (--$n < 1) { + return $func(...$args); + } + }; +} diff --git a/src/Function/ary.php b/src/Function/ary.php new file mode 100644 index 0000000..47d5436 --- /dev/null +++ b/src/Function/ary.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that invokes `func`, with up to `n` arguments, + * ignoring any additional arguments. + * + * @category Function + * + * @param callable $func The function to cap arguments for. + * @param int $n The arity cap. + * + * @return Callable Returns the new capped function. + * + * @example + * + * map(['6', '8', '10'], ary('intval', 1)); + * // => [6, 8, 10] + * + */ +function ary(callable $func, int $n): callable +{ + return function (...$args) use ($func, $n) { + \array_splice($args, $n); + + return $func(...$args); + }; +} diff --git a/src/Function/before.php b/src/Function/before.php new file mode 100644 index 0000000..1dcacd4 --- /dev/null +++ b/src/Function/before.php @@ -0,0 +1,44 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that invokes `func`, with the arguments + * of the created function, while it's called less than `n` times. Subsequent + * calls to the created function return the result of the last `func` invocation. + * + * @category Function + * + * @param int $n The number of calls at which `func` is no longer invoked. + * @param callable $func The function to restrict. + * + * @return callable Returns the new restricted function. + * + * @example + * + * $users = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + * $result = uniqBy(map($users, before(5, [$repository, 'find'])), 'id') + * // => Fetch up to 4 results. + * + */ +function before(int $n, callable $func): callable +{ + $result = null; + + return function (...$args) use (&$result, &$n, &$func) { + if (--$n > 0) { + $result = $func(...$args); + } + + return $result; + }; +} diff --git a/src/Function/bind.php b/src/Function/bind.php new file mode 100644 index 0000000..e69e0bd --- /dev/null +++ b/src/Function/bind.php @@ -0,0 +1,47 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that invokes `func` with the `this` binding of `object` + * and `partials` prepended to the arguments it receives. + * + * @category Function + * + * @param callable $function The function to bind. + * @param object|mixed $object The `object` binding of `func`. + * @param array $partials The arguments to be partially applied. + * + * @return callable Returns the new bound function. + * @example + * + * function greet($greeting, $punctuation) { + * return $greeting . ' ' . $this->user . $punctuation; + * } + * + * $object = $object = new class { + * public $user = 'fred'; + * }; + * + * $bound = bind('greet', $object, 'hi'); + * $bound('!'); + * // => 'hi fred!' + * + */ +function bind(callable $function, $object, ...$partials): callable +{ + return function (...$args) use ($object, $function, $partials) { + $function = \Closure::fromCallable($function)->bindTo($object, $function instanceof \Closure ? $object : null); + + return $function(...array_merge($partials, $args)); + }; +} diff --git a/src/Function/bindKey.php b/src/Function/bindKey.php new file mode 100644 index 0000000..636cec6 --- /dev/null +++ b/src/Function/bindKey.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that invokes the method `$function` of `$object` with `$partials` + * prepended to the arguments it receives. + * + * This method differs from `bind` by allowing bound functions to reference + * methods that may be redefined or don't yet exist + * + * @category Function + * + * @param object $object The object to invoke the method on. + * @param string $function The name of the method. + * @param array $partials The arguments to be partially applied. + * + * @return callable Returns the new bound function. + * @example + * + * $object = new class { + * private $user = 'fred'; + * function greet($greeting, $punctuation) { + * return $greeting.' '.$this->user.$punctuation; + * } + * }; + * + * $bound = bindKey($object, 'greet', 'hi'); + * $bound('!'); + * // => 'hi fred!' + * + */ +function bindKey($object, string $function, ...$partials): callable +{ + return function (...$args) use ($object, $function, $partials) { + $function = \Closure::fromCallable([$object, $function])->bindTo($object, get_class($object)); + + return $function(...array_merge($partials, $args)); + }; +} diff --git a/src/Function/curry.php b/src/Function/curry.php new file mode 100644 index 0000000..d4cdd1c --- /dev/null +++ b/src/Function/curry.php @@ -0,0 +1,85 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that accepts arguments of `func` and either invokes + * `func` returning its result, if at least `arity` number of arguments have + * been provided, or returns a function that accepts the remaining `func` + * arguments, and so on. The arity of `func` may be specified if `func.length` + * is not sufficient. + * + * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, + * may be used as a placeholder for provided arguments. + * + * **Note:** This method doesn't set the "length" property of curried functions. + * + * @category Function + * + * @param callable $func The function to curry. + * @param int|null $arity The arity of `func`. + * + * @return callable Returns the new curried function. + * @example + * + * $abc = function($a, $b, $c) { + * return [$a, $b, $c]; + * }; + * + * $curried = curry($abc); + * + * $curried(1)(2)(3); + * // => [1, 2, 3] + * + * $curried(1, 2)(3); + * // => [1, 2, 3] + * + * $curried(1, 2, 3); + * // => [1, 2, 3] + * + * // Curried with placeholders. + * $curried(1)(_, 3)(2); + * // => [1, 2, 3] + * + */ +function curry(callable $func, ?int $arity = null) +{ + $curry = function ($arguments) use ($func, &$curry, $arity) { + $requiredArguments = (new \ReflectionFunction($func))->getNumberOfParameters(); + $arity = $arity ?? $requiredArguments; + + return function (...$args) use ($func, $arguments, $curry, $arity) { + if (false !== \array_search(_, $arguments)) { + foreach ($arguments as $i => $argument) { + if (_ !== $argument) { + continue; + } + + $arguments[$i] = current($args); + next($args); + } + } else { + $arguments = \array_merge($arguments, $args); + } + + if ($arity <= \count(\array_filter($arguments, function ($value) { + return _ !== $value; + }))) { + return $func(...$arguments); + } + + return $curry($arguments); + }; + }; + + return $curry([]); +} diff --git a/src/Function/delay.php b/src/Function/delay.php new file mode 100644 index 0000000..53418fe --- /dev/null +++ b/src/Function/delay.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Invokes `func` after `wait` milliseconds. Any additional arguments are + * provided to `func` when it's invoked. + * + * @category Function + * + * @param callable $func The function to delay. + * @param int $wait The number of milliseconds to delay invocation. + * @param array $args + * + * @return int the timer id. + * @example + * + * delay(function($text) { + * echo $text; + * }, 1000, 'later'); + * // => Echo 'later' after one second. + * + */ +function delay(callable $func, int $wait = 1, ...$args): int +{ + usleep($wait * 1000); + + $func(...$args); + + return 1; +} diff --git a/src/Function/flip.php b/src/Function/flip.php new file mode 100644 index 0000000..91c1e33 --- /dev/null +++ b/src/Function/flip.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseRest; +use function _\internal\castSlice; + +/** + * Creates a function that invokes `func` with arguments reversed. + * + * @category Function + * + * @param callable $func The function to flip arguments for. + * + * @return callable Returns the new flipped function. + * + * @example + * + * $flipped = flip(function() { + * return func_get_args(); + * }); + * + * flipped('a', 'b', 'c', 'd'); + * // => ['d', 'c', 'b', 'a'] + * + */ +function flip(callable $func): callable +{ + return function (...$values) use ($func) { + return \array_reverse($func(...$values), false); + }; +} diff --git a/src/Function/memoize.php b/src/Function/memoize.php new file mode 100644 index 0000000..13ec3a1 --- /dev/null +++ b/src/Function/memoize.php @@ -0,0 +1,100 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +/** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided, it determines the cache key for storing the result based on the + * arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is used as the map cache key + * + * **Note:** The cache is exposed as the `cache` property on the memoized + * function. Its creation may be customized by replacing the `_.memoize.Cache` + * constructor with one whose instances implement the + * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) + * method interface of `clear`, `delete`, `get`, `has`, and `set`. + * + * @category Function + * + * @param callable $func The function to have its output memoized. + * @param callable|null $resolver The function to resolve the cache key. + * + * @return callable Returns the new memoized function. + * + * @example + * + * $object = ['a' => 1, 'b' => 2]; + * $other = ['c' => 3, 'd' => 4]; + * + * $values = memoize('_\values'); + * $values($object); + * // => [1, 2] + * + * $values($other); + * // => [3, 4] + * + * $object['a'] = 2; + * $values($object); + * // => [1, 2] + * + * // Modify the result cache. + * $values->cache->set($object, ['a', 'b']); + * $values($object); + * // => ['a', 'b'] + * + */ +function memoize(callable $func, callable $resolver = null) +{ + $memoized = new class($func, $resolver ?? null) { + + /** + * @var CacheInterface + */ + public $cache; + + private $resolver; + + private $func; + + public function __construct(callable $func, ?callable $resolver) + { + $this->resolver = $resolver; + $this->func = $func; + } + + public function __invoke() + { + $args = \func_get_args(); + + if ($this->resolver) { + $key = \Closure::fromCallable($this->resolver)->bindTo($this)(...$args); + } else { + $key = &$args[0]; + } + + $cache = $this->cache; + + if ($cache->has($key)) { + return $cache->get($key); + } + + $result = ($this->func)(...$args); + $this->cache = $this->cache->set($key, $result); + + return $result; + } + }; + + $memoized->cache = new MapCache; + + return $memoized; +} diff --git a/src/Function/negate.php b/src/Function/negate.php new file mode 100644 index 0000000..4032304 --- /dev/null +++ b/src/Function/negate.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +/** + * Creates a function that negates the result of the predicate `func` + * + * @category Function + * + * @param callable $predicate The predicate to negate. + * + * @return callable Returns the new negated function. + * + * @example + * + * function isEven($n) { + * return $n % 2 == 0; + * } + * + * filter([1, 2, 3, 4, 5, 6], negate($isEven)); + * // => [1, 3, 5] + * + */ + +function negate(callable $predicate): callable +{ + return function () use ($predicate) { + return !$predicate(...\func_get_args()); + }; +} diff --git a/src/Function/once.php b/src/Function/once.php new file mode 100644 index 0000000..2ff0909 --- /dev/null +++ b/src/Function/once.php @@ -0,0 +1,36 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that is restricted to invoking `func` once. Repeat calls + * to the function return the value of the first invocation. The `func` is + * invoked with the arguments of the created function. + * + * @category Function + * + * @param callable $func The function to restrict. + * + * @return callable the new restricted function. + * + * @example + * + * $initialize = once('createApplication'); + * $initialize(); + * $initialize(); + * // => `createApplication` is invoked once + * + */ +function once(callable $func): callable +{ + return before(2, $func); +} diff --git a/src/Function/overArgs.php b/src/Function/overArgs.php new file mode 100644 index 0000000..386c27e --- /dev/null +++ b/src/Function/overArgs.php @@ -0,0 +1,72 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\arrayMap; +use function _\internal\baseFlatten; +use function _\internal\baseRest; +use function _\internal\baseUnary; + +/** + * Creates a function that invokes `func` with its arguments transformed. + * + * @static + * @memberOf _ + * @category Function + * + * @param callable $func The function to wrap. + * @param callable[] $transforms The argument transforms. + * + * @return callable the new function. + * + * @example + * + * function doubled($n) { + * return $n * 2; + * } + * + * function square($n) { + * return $n * $n; + * } + * + * $func = overArgs(function($x, $y) { + * return [$x, $y]; + * }, ['square', 'doubled']); + * + * $func(9, 3); + * // => [81, 6] + * + * $func(10, 5); + * // => [100, 10] + * + */ +function overArgs(callable $func, array $transforms): callable +{ + return baseRest(function ($func, $transforms) { + $transforms = (\count($transforms) == 1 && \is_array($transforms[0])) + ? arrayMap($transforms[0], baseUnary('\_\internal\baseIteratee')) + : arrayMap(baseFlatten($transforms, 1), baseUnary('\_\internal\baseIteratee')); + + $funcsLength = \count($transforms); + + return baseRest(function ($args) use ($funcsLength, $transforms, $func) { + $index = -1; + $length = \min(\count($args), $funcsLength); + + while (++$index < $length) { + $args[$index] = $transforms[$index]($args[$index]); + } + + return $func(...$args); + }); + })($func, $transforms); +} diff --git a/src/Function/partial.php b/src/Function/partial.php new file mode 100644 index 0000000..8ada3d7 --- /dev/null +++ b/src/Function/partial.php @@ -0,0 +1,62 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseRest; +use function _\internal\shortOut; + +/** + * Creates a function that invokes `func` with `partials` prepended to the + * arguments it receives. + * + * @category Function + * + * @param callable $func The function to partially apply arguments to. + * @param array $partials The arguments to be partially applied. + * + * @return callable Returns the new partially applied function. + * + * @example + * + * function greet($greeting, $name) { + * return $greeting . ' ' . $name; + * } + * + * $sayHelloTo = partial('greet', 'hello'); + * $sayHelloTo('fred'); + * // => 'hello fred' + * + */ +function partial(callable $func, ...$partials): callable +{ + return baseRest(function ($func, $partials) { + $wrapper = function () use ($func, $partials) { + $arguments = \func_get_args(); + $argsIndex = -1; + $argsLength = \func_num_args(); + $leftIndex = -1; + $leftLength = \count($partials); + $args = []; + + while (++$leftIndex < $leftLength) { + $args[$leftIndex] = $partials[$leftIndex]; + } + while ($argsLength--) { + $args[$leftIndex++] = $arguments[++$argsIndex]; + } + + return $func(...$args); + }; + + return shortOut($wrapper); + })($func, ...$partials); +} diff --git a/src/Function/rest.php b/src/Function/rest.php new file mode 100644 index 0000000..1402941 --- /dev/null +++ b/src/Function/rest.php @@ -0,0 +1,42 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseRest; + +/** + * Creates a function that invokes `func` with the `this` binding of the + * created function and arguments from `start` and beyond provided as + * an array. + * + * @category Function + * + * @param callable $func The function to apply a rest parameter to. + * @param int|null $start The start position of the rest parameter. + * + * @return callable Returns the new function. + * + * @example + * + * $say = rest(function($what, $names) { + * return $what . ' ' . implode(', ', initial($names)) . + * (size($names) > 1 ? ', & ' : '') . last($names); + * }); + * + * $say('hello', 'fred', 'barney', 'pebbles'); + * // => 'hello fred, barney, & pebbles' + * + */ +function rest(callable $func, ?int $start = null): callable +{ + return baseRest($func, $start); +} diff --git a/src/Function/spread.php b/src/Function/spread.php new file mode 100644 index 0000000..7dfab5f --- /dev/null +++ b/src/Function/spread.php @@ -0,0 +1,59 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\baseRest; +use function _\internal\castSlice; + +/** + * Creates a function that invokes `func` with the `this` binding of the + * create function and an array of arguments much like + * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). + * + * **Note:** This method is based on the + * [spread operator](https://mdn.io/spread_operator). + * + * @static + * @memberOf _ + * @since 3.2.0 + * @category Function + * + * @param callable $func The function to spread arguments over. + * @param int $start The start position of the spread. + * + * @return callable Returns the new function. + * + * @example + * + * $say = spread(function($who, $what) { + * return $who . ' says ' . $what; + * }); + * + * $say(['fred', 'hello']); + * // => 'fred says hello' + * + */ +function spread(callable $func, ?int $start = null) +{ + $start = null === $start ? 0 : \max($start, 0); + + return baseRest(function ($args) use ($start, $func) { + $array = $args[$start]; + $otherArgs = castSlice($args, 0, $start); + + if ($array) { + $otherArgs = \array_merge($otherArgs, $array); + } + + return $func(...$otherArgs); + }); +} diff --git a/src/Function/unary.php b/src/Function/unary.php new file mode 100644 index 0000000..d179ba8 --- /dev/null +++ b/src/Function/unary.php @@ -0,0 +1,32 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that accepts up to one argument, ignoring any + * additional arguments. + * + * @category Function + * + * @param callable $func The function to cap arguments for. + * + * @return callable the new capped function. + * @example + * + * map(['6', '8', '10'], unary('intval')); + * // => [6, 8, 10] + * + */ +function unary(callable $func): callable +{ + return ary($func, 1); +} diff --git a/src/Function/wrap.php b/src/Function/wrap.php new file mode 100644 index 0000000..84df495 --- /dev/null +++ b/src/Function/wrap.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a function that provides `value` to `wrapper` as its first + * argument. Any additional arguments provided to the function are appended + * to those provided to the `wrapper`. + * + * @category Function + * + * @param mixed $value The value to wrap. + * @param callable $wrapper The wrapper function. + * + * @return callable the new function. + * @example + * + * $p = wrap('_\escape', function($func, $text) { + * return '

' . $func($text) . '

'; + * }); + * + * $p('fred, barney, & pebbles'); + * // => '

fred, barney, & pebbles

' + *
+ */ +function wrap($value, callable $wrapper = null): callable +{ + return partial($wrapper ?? '\_\identity', $value); +} diff --git a/src/Hash.php b/src/Hash.php new file mode 100644 index 0000000..365b8f8 --- /dev/null +++ b/src/Hash.php @@ -0,0 +1,59 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +use _\internal\Traits\CacheDataTrait; + +final class Hash implements CacheInterface +{ + use CacheDataTrait; + + public function __construct() + { + $this->clear(); + } + + public function set($key, $value): CacheInterface + { + $this->size += $this->has($key) ? 0 : 1; + $this->__data__[$key] = $value; + + return $this; + } + + public function get($key) + { + return $this->__data__[$key] ?? null; + } + + public function has($key): bool + { + return \array_key_exists($key, $this->__data__); + } + + public function clear() + { + $this->__data__ = []; + $this->size = 0; + } + + public function delete($key) + { + $result = $this->has($key); + + unset($this->__data__[$key]); + + $this->size -= $result ? 1 : 0; + + return $result; + } +} diff --git a/src/Lang/eq.php b/src/Lang/eq.php new file mode 100644 index 0000000..4f1a280 --- /dev/null +++ b/src/Lang/eq.php @@ -0,0 +1,47 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +/** + * Performs a comparison between two values to determine if they are equivalent. + * + * @param mixed $value The value to compare. + * @param mixed $other The other value to compare. + * + * @category Lang + * + * @return boolean Returns `true` if the values are equivalent, else `false`. + * @example + * + * $object = (object) ['a' => 1]; + * $other = (object) ['a' => 1]; + * + * eq($object, $object); + * // => true + * + * eq($object, $other); + * // => false + * + * eq('a', 'a'); + * // => true + * + * eq(['a'], (object) ['a']); + * // => false + * + * eq(INF, INF); + * // => true + * + */ +function eq($value, $other): bool +{ + return $value === $other; +} diff --git a/src/Lang/isEqual.php b/src/Lang/isEqual.php index 2d32001..a0c210b 100644 --- a/src/Lang/isEqual.php +++ b/src/Lang/isEqual.php @@ -55,4 +55,4 @@ function isEqual($value, $other): bool } catch (ComparisonFailure $failure) { return false; } -} \ No newline at end of file +} diff --git a/src/Lang/isError.php b/src/Lang/isError.php index 9f46150..0a9598f 100644 --- a/src/Lang/isError.php +++ b/src/Lang/isError.php @@ -27,6 +27,7 @@ * * isError(\Exception::Class) * // => false + *
*/ function isError($value): bool { @@ -35,4 +36,4 @@ function isError($value): bool } return $value instanceof \ParseError || $value instanceof \Error || $value instanceof \Throwable || $value instanceof \SoapFault || $value instanceof \DOMException || $value instanceof \PDOException; -} \ No newline at end of file +} diff --git a/src/ListCache.php b/src/ListCache.php new file mode 100644 index 0000000..0c8931d --- /dev/null +++ b/src/ListCache.php @@ -0,0 +1,86 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +use _\internal\Traits\CacheDataTrait; +use function _\internal\assocIndexOf; + +/** + * @property array $__data__ + * @property int $size + */ +final class ListCache implements CacheInterface +{ + use CacheDataTrait; + + public function __construct(iterable $entries = null) + { + $this->clear(); + + if (null !== $entries) { + foreach ($entries as $key => $entry) { + $this->set($key, $entry); + } + } + } + + final public function set($key, $value): CacheInterface + { + $index = assocIndexOf($this->__data__, $key); + + if ($index < 0) { + ++$this->size; + $this->__data__[] = [$key, $value]; + } else { + $this->__data__[$index][1] = $value; + } + + return $this; + } + + final public function get($key) + { + $index = assocIndexOf($this->__data__, $key); + + return $index < 0 ? null : $this->__data__[$index][1]; + } + + final public function has($key): bool + { + return assocIndexOf($this->__data__, $key) > -1; + } + + final public function clear() + { + $this->__data__ = []; + $this->size = 0; + } + + final public function delete($key) + { + $index = assocIndexOf($this->__data__, $key); + + if ($index < 0) { + return false; + } + + $lastIndex = \count($this->__data__) - 1; + if ($index === $lastIndex) { + \array_pop($this->__data__); + } else { + \array_splice($this->__data__, $index, 1); + } + --$this->size; + + return true; + } +} diff --git a/src/Lodash.php b/src/Lodash.php index cf7a1cc..08a6e01 100644 --- a/src/Lodash.php +++ b/src/Lodash.php @@ -11,6 +11,8 @@ final class _ { + public $__chain__ = false; + public const reInterpolate = '<%=([\s\S]+?)%>'; public const reEvaluate = "<%([\s\S]+?)%>"; public const reEscape = "<%-([\s\S]+?)%>"; @@ -44,6 +46,13 @@ final class _ ], ]; + private $value; + + public function __construct($value) + { + $this->value = $value; + } + /** * @param string $method * @param array $args @@ -53,6 +62,40 @@ final class _ */ public static function __callStatic(string $method, array $args) { + if (!\is_callable("_\\$method")) { + throw new \InvalidArgumentException("Function _::$method is not valid"); + } + return ("_\\$method")(...$args); } -} \ No newline at end of file + + public function __call($method, $arguments) + { + $this->value = self::__callStatic($method, \array_merge([$this->value], $arguments)); + + return $this; + } + + public function value() + { + return $this->value; + } +} + +function lodash($value): _ +{ + return new _($value); +} + +// We can't use "_" as a function name, since it collides with the "_" function in the gettext extension +// Laravel uses a function __, so only register the alias if the function name is not in use +if (!function_exists('__')) { + function __($value): _ + { + return new _($value); + } +} + +if (!defined('_')) { + define('_', _::class); +} diff --git a/src/MapCache.php b/src/MapCache.php new file mode 100644 index 0000000..7726fad --- /dev/null +++ b/src/MapCache.php @@ -0,0 +1,87 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +use _\internal\Traits\CacheDataTrait; + +/** + * @property array $__data__ + * @property int $size + */ +final class MapCache implements CacheInterface +{ + use CacheDataTrait; + + public function __construct(iterable $entries = null) + { + $this->clear(); + + if (null !== $entries) { + foreach ($entries as $key => $entry) { + $this->set($key, $entry); + } + } + } + + final public function set($key, $value): CacheInterface + { + $data = $this->getMapData($key); + $size = $data->getSize(); + + $data->set($key, $value); + $this->size += $data->getSize() === $size ? 0 : 1; + + return $this; + } + + final public function get($key) + { + return $this->getMapData($key)->get($key); + } + + final public function has($key): bool + { + return $this->getMapData($key)->has($key); + } + + final public function clear() + { + $this->size = 0; + $this->__data__ = [ + 'hash' => new Hash, + 'map' => new ListCache, + 'string' => new Hash, + ]; + } + + final public function delete($key) + { + $result = $this->getMapData($key)->delete($key); + $this->size -= $result ? 1 : 0; + + return $result; + } + + private function isKey($key) + { + return \is_scalar($key); + } + + private function getMapData($key): CacheInterface + { + if ($this->isKey($key)) { + return $this->__data__[\is_string($key) ? 'string' : 'hash']; + } + + return $this->__data__['map']; + } +} diff --git a/src/Math/add.php b/src/Math/add.php new file mode 100644 index 0000000..5d53cb4 --- /dev/null +++ b/src/Math/add.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +use function _\internal\createMathOperation; + +/** + * Adds two numbers. + * + * @category Math + * + * @param int|float|string $augend The first number in an addition. + * @param int|float|string $addend The second number in an addition. + * + * @return int|float Returns the total. + * + * @example + * + * add(6, 4); + * // => 10 + * + */ +function add($augend, $addend) +{ + return createMathOperation(function ($augend, $addend) { + return $augend + $addend; + }, 0)($augend, $addend); +} diff --git a/src/Math/max.php b/src/Math/max.php new file mode 100644 index 0000000..bc61451 --- /dev/null +++ b/src/Math/max.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +/** + * Computes the maximum value of `array`. If `array` is empty or falsey, null is returned. + * + * @category Math + * + * @param array|null $array The array to iterate over. + * + * @return int|null Returns the maximum value. + * @example + * + * max([4, 2, 8, 6]); + * // => 8 + * + * max([]); + * // => null + * + */ +function max(?array $array): ?int +{ + return $array ? \max($array) : null; +} diff --git a/src/Math/maxBy.php b/src/Math/maxBy.php new file mode 100644 index 0000000..3127605 --- /dev/null +++ b/src/Math/maxBy.php @@ -0,0 +1,55 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +use function _\internal\baseIteratee; + +/** + * This method is like `max` except that it accepts `iteratee` which is + * invoked for each element in `array` to generate the criterion by which + * the value is ranked. The iteratee is invoked with one argument: (value). + * + * @category Math + * + * @param array $array The array to iterate over. + * @param callable|string $iteratee The iteratee invoked per element. + * + * @return mixed Returns the maximum value. + * @example + * + * $objects = [['n' => 1], ['n' => 2]]; + * + * maxBy($objects, function($o) { return $o['n']; }); + * // => ['n' => 2] + * + * // The `property` iteratee shorthand. + * maxBy($objects, 'n'); + * // => ['n' => 2] + * + */ +function maxBy(?array $array, $iteratee) +{ + $iteratee = baseIteratee($iteratee); + $result = null; + $computed = null; + + foreach ($array as $key => $value) { + $current = $iteratee($value); + + if (null !== $current && (null === $computed ? ($current === $current) : $current > $computed)) { + $computed = $current; + $result = $value; + } + } + + return $result; +} diff --git a/src/Number/clamp.php b/src/Number/clamp.php index 7ef9017..2ab5ddc 100644 --- a/src/Number/clamp.php +++ b/src/Number/clamp.php @@ -16,9 +16,9 @@ * * @category Number * - * @param int number The number to clamp. - * @param int lower The lower bound. - * @param int upper The upper bound. + * @param int $number The number to clamp. + * @param int $lower The lower bound. + * @param int $upper The upper bound. * * @return int Returns the clamped number. * @@ -37,4 +37,4 @@ function clamp(int $number, int $lower, int $upper): int $number = $number >= $lower ? $number : $lower; return $number; -} \ No newline at end of file +} diff --git a/src/Number/inRange.php b/src/Number/inRange.php index 8f78f26..f1408ae 100644 --- a/src/Number/inRange.php +++ b/src/Number/inRange.php @@ -19,11 +19,12 @@ * * @category Number * - * @param float number The number to check. - * @param float $start The start of the range. - * @param float $end The end of the range. + * @param float $number The number to check. + * @param float $start The start of the range. + * @param float $end The end of the range. * * @return boolean Returns `true` if `number` is in the range, else `false`. + * * @example * * inRange(3, 2, 4) @@ -50,10 +51,10 @@ */ function inRange(float $number, float $start = 0, float $end = 0): bool { - if (0 === $end || 0.0 === $end) { + if (0.0 === $end) { $end = $start; $start = 0; } return $number >= \min($start, $end) && $number < \max($start, $end); -} \ No newline at end of file +} diff --git a/src/Number/random.php b/src/Number/random.php index bdc158e..8d322af 100644 --- a/src/Number/random.php +++ b/src/Number/random.php @@ -19,14 +19,14 @@ * * @category Number * - * @param int|float $lower The lower bound. - * @param int|float $upper The upper bound. - * @param bool $floating Specify returning a floating-point number. + * @param int|float|bool $lower The lower bound. + * @param int|float|bool $upper The upper bound. + * @param bool|null $floating Specify returning a floating-point number. * * @return int|float Returns the random number. * * @example - * + * * random(0, 5) * // => an integer between 0 and 5 * @@ -38,6 +38,7 @@ * * random(1.2, 5.2) * // => a floating-point number between 1.2 and 5.2 + * */ function random($lower = null, $upper = null, $floating = null) { @@ -45,7 +46,7 @@ function random($lower = null, $upper = null, $floating = null) if (\is_bool($upper)) { $floating = $upper; $upper = null; - } else if (\is_bool($lower)) { + } elseif (\is_bool($lower)) { $floating = $lower; $lower = null; } @@ -54,7 +55,7 @@ function random($lower = null, $upper = null, $floating = null) if (null === $lower && null === $upper) { $lower = 0; $upper = 1; - } else if (null === $upper) { + } elseif (null === $upper) { $upper = $lower; $lower = 0; } @@ -73,5 +74,5 @@ function random($lower = null, $upper = null, $floating = null) return $lower + \abs($upper - $lower) * \mt_rand(0, $randMax) / $randMax; } - return \rand((int) $lower, (int) $upper); + return \random_int((int) $lower, (int) $upper); } diff --git a/src/Object/get.php b/src/Object/get.php new file mode 100644 index 0000000..3a0c66d --- /dev/null +++ b/src/Object/get.php @@ -0,0 +1,45 @@ + + * @copyright Copyright (c) 2019 + */ + +namespace _; + +use function _\internal\baseGet; + +/** + * Gets the value at path of object. If the resolved value is null the defaultValue is returned in its place. + * + * @category Object + * + * @param mixed $object The associative array or object to fetch value from + * @param array|string $path Dot separated or array of string + * @param mixed $defaultValue (optional)The value returned for unresolved or null values. + * + * @return mixed Returns the resolved value. + * + * @author punit-kulal + * + * @example + * + * $sampleArray = ["key1" => ["key2" => ["key3" => "val1", "key4" => ""]]]; + * get($sampleArray, 'key1.key2.key3'); + * // => "val1" + * + * get($sampleArray, 'key1.key2.key5', "default"); + * // => "default" + * + * get($sampleArray, 'key1.key2.key4', "default"); + * // => "" + * + */ +function get($object, $path, $defaultValue = null) +{ + return ($object !== null ? baseGet($object, $path) : null) ?? $defaultValue; +} diff --git a/src/Object/pick.php b/src/Object/pick.php new file mode 100644 index 0000000..9813514 --- /dev/null +++ b/src/Object/pick.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _; + +use function _\internal\basePick; +use function _\internal\flatRest; + +/** + * Creates an object composed of the picked `object` properties. + * + * @category Object + * + * @param object $object The source object. + * @param string|string[] $paths The property paths to pick. + * + * @return \stdClass Returns the new object. + * @example + * + * $object = (object) ['a' => 1, 'b' => '2', 'c' => 3]; + * + * pick($object, ['a', 'c']); + * // => (object) ['a' => 1, 'c' => 3] + * + */ +function pick($object, $paths): \stdClass +{ + return flatRest(function ($object, $paths) { + return basePick($object, $paths); + })($object, $paths); +} diff --git a/src/Object/pickBy.php b/src/Object/pickBy.php new file mode 100644 index 0000000..7d85cc0 --- /dev/null +++ b/src/Object/pickBy.php @@ -0,0 +1,50 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +use function _\internal\arrayMap; +use function _\internal\baseIteratee; +use function _\internal\basePickBy; + +/** + * Creates an object composed of the `object` properties `predicate` returns + * truthy for. The predicate is invoked with two arguments: (value, key). + * + * @category Object + * + * @param object|null $object The source object. + * @param callable $predicate The function invoked per property. + * + * @return \stdClass Returns the new object. + * @example + * + * $object = (object) ['a' => 1, 'b' => 'abc', 'c' => 3]; + * + * pickBy(object, 'is_numeric'); + * // => (object) ['a' => 1, 'c' => 3] + * + */ +function pickBy($object, callable $predicate): \stdClass +{ + if (null === $object) { + return new \stdClass; + } + + $props = arrayMap(\array_keys(\get_object_vars($object)), function ($prop) { + return [$prop]; + }); + $predicate = baseIteratee($predicate); + + return basePickBy($object, $props, function ($value, $path) use ($predicate) { + return $predicate($value, $path[0]); + }); +} diff --git a/src/Seq/chain.php b/src/Seq/chain.php new file mode 100644 index 0000000..63e3149 --- /dev/null +++ b/src/Seq/chain.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2017 + */ + +namespace _; + +/** + * Creates a `lodash` wrapper instance that wraps `value` with explicit method + * chain sequences enabled. The result of such sequences must be unwrapped + * with `->value()`. + * + * @category Seq + * + * @param mixed $value The value to wrap. + * + * @return \_ Returns the new `lodash` wrapper instance. + * @example + * + * $users = [ + * ['user' => 'barney', 'age' => 36], + * ['user' => 'fred', 'age' => 40], + * ['user' => 'pebbles', 'age' => 1 ], + * ]; + * + * $youngest = chain($users) + * ->sortBy('age') + * ->map(function($o) { + * return $o['user'] . ' is ' . $o['age']; + * }) + * ->head() + * ->value(); + * // => 'pebbles is 1' + * + */ +function chain($value): \_ +{ + /** @var \_ $result */ + $result = __($value); + $result->__chain__ = true; + + return $result; +} diff --git a/src/String/camelCase.php b/src/String/camelCase.php index 1886062..e669e5a 100644 --- a/src/String/camelCase.php +++ b/src/String/camelCase.php @@ -37,4 +37,4 @@ function camelCase(string $string): string return \lcfirst(\array_reduce(words(\preg_replace("/['\\x{2019}]/u", '', $string)), function ($result, $word) { return $result.capitalize(\strtolower($word)); }, '')); -} \ No newline at end of file +} diff --git a/src/String/capitalize.php b/src/String/capitalize.php index 54df344..c6403f2 100644 --- a/src/String/capitalize.php +++ b/src/String/capitalize.php @@ -30,4 +30,4 @@ function capitalize(string $string): string { return \ucfirst(\mb_strtolower($string)); -} \ No newline at end of file +} diff --git a/src/String/deburr.php b/src/String/deburr.php index 10ca5a7..a850d78 100644 --- a/src/String/deburr.php +++ b/src/String/deburr.php @@ -96,9 +96,10 @@ * @return string Returns the deburred string. * * @example - * + * * deburr('déjà vu') * // => 'deja vu' + * */ function deburr(string $string): string { @@ -110,4 +111,4 @@ function ($pattern) { ); return \preg_replace(rsCombo, '', \preg_replace($patterns, \array_values(deburredLetters), $string)); -} \ No newline at end of file +} diff --git a/src/String/endsWith.php b/src/String/endsWith.php index 1b84f65..677effb 100644 --- a/src/String/endsWith.php +++ b/src/String/endsWith.php @@ -40,11 +40,11 @@ function endsWith(string $string, string $target, int $position = null): bool if ($position < 0) { $position = 0; - } else if ($position > $length) { + } elseif ($position > $length) { $position = $length; } $position -= \strlen($target); return $position >= 0 && \substr($string, $position, \strlen($target)) === $target; -} \ No newline at end of file +} diff --git a/src/String/escape.php b/src/String/escape.php index 416a956..680ca98 100644 --- a/src/String/escape.php +++ b/src/String/escape.php @@ -40,4 +40,4 @@ function escape(string $string) { return \htmlentities($string); -} \ No newline at end of file +} diff --git a/src/String/kebabCase.php b/src/String/kebabCase.php index 0839402..bccbc43 100644 --- a/src/String/kebabCase.php +++ b/src/String/kebabCase.php @@ -35,4 +35,4 @@ function kebabCase(string $string) { return \implode('-', \array_map('\strtolower', words(\preg_replace("/['\x{2019}]/u", '', $string)))); -} \ No newline at end of file +} diff --git a/src/String/lowerCase.php b/src/String/lowerCase.php index c1aeba9..0266600 100644 --- a/src/String/lowerCase.php +++ b/src/String/lowerCase.php @@ -36,4 +36,4 @@ function lowerCase(string $string) { return \implode(' ', \array_map('\strtolower', words(\preg_replace(reQuotes, '', $string)))); -} \ No newline at end of file +} diff --git a/src/String/lowerFirst.php b/src/String/lowerFirst.php index cfe8597..51d4f96 100644 --- a/src/String/lowerFirst.php +++ b/src/String/lowerFirst.php @@ -32,4 +32,4 @@ function lowerFirst(string $string): string { return \lcfirst($string); -} \ No newline at end of file +} diff --git a/src/String/pad.php b/src/String/pad.php index 7bb8cde..efb76bf 100644 --- a/src/String/pad.php +++ b/src/String/pad.php @@ -38,4 +38,4 @@ function pad(string $string, int $length, string $chars = ' '): string { return \str_pad($string, $length, $chars, \STR_PAD_BOTH); -} \ No newline at end of file +} diff --git a/src/String/padStart.php b/src/String/padStart.php index c3ffb19..e7c6443 100644 --- a/src/String/padStart.php +++ b/src/String/padStart.php @@ -37,4 +37,4 @@ function padStart(string $string, int $length, string $chars = ' '): string { return \str_pad($string, $length, $chars, \STR_PAD_LEFT); -} \ No newline at end of file +} diff --git a/src/String/parseInt.php b/src/String/parseInt.php index d3ea8cb..9fbe799 100644 --- a/src/String/parseInt.php +++ b/src/String/parseInt.php @@ -36,9 +36,9 @@ function parseInt($string, int $radix = null): int { if (null === $radix) { $radix = 10; - } else if ($radix) { + } elseif ($radix) { $radix = +$radix; } return \intval($string, $radix); -} \ No newline at end of file +} diff --git a/src/String/repeat.php b/src/String/repeat.php index 58ffa51..c650fd4 100644 --- a/src/String/repeat.php +++ b/src/String/repeat.php @@ -36,4 +36,4 @@ function repeat(string $string, int $n = 1): string { return \str_repeat($string, $n); -} \ No newline at end of file +} diff --git a/src/String/replace.php b/src/String/replace.php index 60e7546..9e308f6 100644 --- a/src/String/replace.php +++ b/src/String/replace.php @@ -19,9 +19,9 @@ * * @category String * - * @param string $string The string to modify. - * @param string $pattern The pattern to replace. - * @param callable|string replacement The match replacement. + * @param string $string The string to modify. + * @param string $pattern The pattern to replace. + * @param callable|string $replacement The match replacement. * * @return string Returns the modified string. * @@ -31,23 +31,23 @@ * // => 'Hi Barney' * */ -function replace(string $string, string $pattern, $replacement = ''): string +function replace(string $string, string $pattern, $replacement = null): string { $callback = function (array $matches) use ($replacement): ?string { if (!\array_filter($matches)) { return null; } - return $replacement(...$matches); + return \is_callable($replacement) ? $replacement(...$matches) : null; }; if (\preg_match(reRegExpChar, $pattern)) { - if (!is_callable($replacement)) { - return \preg_replace($pattern, $replacement, $string); + if (!\is_callable($replacement)) { + return \preg_replace($pattern, \is_string($replacement) || \is_array($replacement) ? $replacement : '', $string); } return \preg_replace_callback($pattern, $callback, $string); } - return \str_replace($pattern, $replacement, $string); -} \ No newline at end of file + return \str_replace($pattern, \is_string($replacement) || \is_array($replacement) ? $replacement : '', $string); +} diff --git a/src/String/snakeCase.php b/src/String/snakeCase.php index 3369166..07c3d0f 100644 --- a/src/String/snakeCase.php +++ b/src/String/snakeCase.php @@ -19,7 +19,7 @@ * @param string $string The string to convert. * @return string Returns the snake cased string. * @example - * + * * snakeCase('Foo Bar') * // => 'foo_bar' * @@ -28,8 +28,9 @@ * * snakeCase('--FOO-BAR--') * // => 'foo_bar' + * */ function snakeCase(string $string): string { return \implode('_', \array_map('\strtolower', words(\preg_replace("/['\x{2019}]/u", '', $string)))); -} \ No newline at end of file +} diff --git a/src/String/split.php b/src/String/split.php index f001b0b..73916c3 100644 --- a/src/String/split.php +++ b/src/String/split.php @@ -19,7 +19,7 @@ * * @category String * - * @param string string The string to split. + * @param string $string The string to split. * @param string $separator The separator pattern to split by. * @param int $limit The length to truncate results to. * @@ -30,12 +30,13 @@ * // => ['a', 'b'] * */ -function split(string $string, string $separator, int $limit = null): array +function split(string $string, string $separator, int $limit = 0): array { if (\preg_match(reRegExpChar, $separator)) { - return \preg_split($separator, $string, $limit ?? -1, PREG_SPLIT_DELIM_CAPTURE); + return \preg_split($separator, $string, $limit ?: -1, PREG_SPLIT_DELIM_CAPTURE) ?: []; } + /** @var array $result */ $result = \explode($separator, $string); if ($limit > 0) { @@ -43,4 +44,4 @@ function split(string $string, string $separator, int $limit = null): array } return $result; -} \ No newline at end of file +} diff --git a/src/String/startCase.php b/src/String/startCase.php index a1f5f66..7ffb8cb 100644 --- a/src/String/startCase.php +++ b/src/String/startCase.php @@ -35,4 +35,4 @@ function startCase(string $string) { return \implode(' ', \array_map('\ucfirst', words(\preg_replace("/['\x{2019}]/u", '', $string)))); -} \ No newline at end of file +} diff --git a/src/String/startsWith.php b/src/String/startsWith.php index 4860ec8..af01975 100644 --- a/src/String/startsWith.php +++ b/src/String/startsWith.php @@ -40,9 +40,9 @@ function startsWith(string $string, string $target, int $position = null): bool if ($position < 0) { $position = 0; - } else if ($position > $length) { + } elseif ($position > $length) { $position = $length; } return $position >= 0 && \substr($string, $position, \strlen($target)) === $target; -} \ No newline at end of file +} diff --git a/src/String/template.php b/src/String/template.php index 0063bd8..8b76ef0 100644 --- a/src/String/template.php +++ b/src/String/template.php @@ -106,14 +106,13 @@ function template(string $string, array $options = []): callable ($options['evaluate'] ?? reNoMatch), ]); - $string = \preg_replace_callback('#'.$reDelimiters.'#u', function ($matches) use (&$options) { - list( - , + $string = \preg_replace_callback('#'.$reDelimiters.'#u', function ($matches) { + [, $escapeValue, $interpolateValue, $esTemplateValue, $evaluateValue, - ) = \array_merge($matches, \array_fill(\count($matches), 5 - \count($matches), null)); + ] = \array_merge($matches, \array_fill(\count($matches), 5 - \count($matches), null)); $interpolateValue = $interpolateValue ?: $esTemplateValue; @@ -121,17 +120,17 @@ function template(string $string, array $options = []): callable if ($escapeValue) { $escapeValue = \trim($escapeValue); - $source .= ""; + $source .= ""; } if ($evaluateValue) { - $source .= ""; + $source .= ""; } if ($interpolateValue) { - $interpolateValue = \trim($interpolateValue ?? $esTemplateValue); + $interpolateValue = \trim($interpolateValue); $interpolateValue = \preg_replace('#^([\p{L}\p{N}_]+)$#u', '$$1', $interpolateValue); - $source .= ""; + $source .= ""; } return $source; @@ -143,12 +142,17 @@ function template(string $string, array $options = []): callable $imports = $options['imports'] ?? []; - return new class($string, $imports) - { + return new class($string, $imports) { public $source; + /** + * @var array + */ private $imports; + /** + * @param array $imports + */ public function __construct(string $source, array $imports) { $this->source = $source; @@ -162,13 +166,18 @@ public function __invoke(array $arguments = []) foreach ($this->imports as $import => $alias) { if (\class_exists($import)) { $imports .= "use $import as $alias;"; - } else if (\function_exists($import)) { + } elseif (\function_exists($import)) { $imports .= "use function $import as $alias;"; } } + /** @var string $file */ $file = \tempnam(\sys_get_temp_dir(), 'lodashphp'); + if (!$file) { + throw new \RuntimeException('Unable to create temporary file for template'); + } + \file_put_contents($file, "'.$this->source.''); $content = attempt(function () use ($file) { @@ -183,4 +192,4 @@ public function __invoke(array $arguments = []) return $content; } }; -} \ No newline at end of file +} diff --git a/src/String/toLower.php b/src/String/toLower.php index 8144d68..bab4c7a 100644 --- a/src/String/toLower.php +++ b/src/String/toLower.php @@ -34,4 +34,4 @@ function toLower(string $string): string { return \strtolower($string); -} \ No newline at end of file +} diff --git a/src/String/toUpper.php b/src/String/toUpper.php index 3a0779a..af61dcf 100644 --- a/src/String/toUpper.php +++ b/src/String/toUpper.php @@ -29,6 +29,7 @@ * * toUpper('__foo_bar__') * // => '__FOO_BAR__' + * */ function toUpper(string $string): string { diff --git a/src/String/trim.php b/src/String/trim.php index 8967999..7bef528 100644 --- a/src/String/trim.php +++ b/src/String/trim.php @@ -32,4 +32,4 @@ function trim(string $string, string $chars = ' '): string { return \trim($string, $chars); -} \ No newline at end of file +} diff --git a/src/String/trimEnd.php b/src/String/trimEnd.php index 4fc55e7..a90e9da 100644 --- a/src/String/trimEnd.php +++ b/src/String/trimEnd.php @@ -21,14 +21,15 @@ * * @return string Returns the trimmed string. * @example - * + * * trimEnd(' abc ') * // => ' abc' * * trimEnd('-_-abc-_-', '_-') * // => '-_-abc' + * */ function trimEnd(string $string, string $chars = ' '): string { return \rtrim($string, $chars); -} \ No newline at end of file +} diff --git a/src/String/trimStart.php b/src/String/trimStart.php index fcdbd66..b2351c6 100644 --- a/src/String/trimStart.php +++ b/src/String/trimStart.php @@ -32,4 +32,4 @@ function trimStart(string $string, string $chars = ' '): string { return \ltrim($string, $chars); -} \ No newline at end of file +} diff --git a/src/String/truncate.php b/src/String/truncate.php index d03ae7c..381c0df 100644 --- a/src/String/truncate.php +++ b/src/String/truncate.php @@ -101,14 +101,14 @@ function truncate($string, array $options = []) $newEnd = \end($match[0])[1]; } - $result = \substr($result, 0, null === $newEnd ? $end : $newEnd); + $result = \substr($result, 0, $newEnd ?? $end); } - } else if (\strpos($string, $separator) !== $end) { + } elseif (\strpos($string, $separator) !== $end) { $index = \strrpos($result, $separator); - if ($index > -1) { + if (false !== $index) { $result = \substr($result, 0, $index); } } return $result.$omission; -} \ No newline at end of file +} diff --git a/src/String/unescape.php b/src/String/unescape.php index 7aeb89d..779cf06 100644 --- a/src/String/unescape.php +++ b/src/String/unescape.php @@ -30,4 +30,4 @@ function unescape(string $string): string { return \html_entity_decode($string); -} \ No newline at end of file +} diff --git a/src/String/upperCase.php b/src/String/upperCase.php index 378f348..149b39d 100644 --- a/src/String/upperCase.php +++ b/src/String/upperCase.php @@ -32,4 +32,4 @@ function upperCase(string $string) { return \implode(' ', \array_map('\strtoupper', words(\preg_replace(reQuotes, '', $string)))); -} \ No newline at end of file +} diff --git a/src/String/upperFirst.php b/src/String/upperFirst.php index ef27478..7343905 100644 --- a/src/String/upperFirst.php +++ b/src/String/upperFirst.php @@ -31,4 +31,4 @@ function upperFirst(string $string): string { return \ucfirst($string); -} \ No newline at end of file +} diff --git a/src/String/words.php b/src/String/words.php index 1cd00c8..1bd86c7 100644 --- a/src/String/words.php +++ b/src/String/words.php @@ -53,4 +53,4 @@ function words(string $string, string $pattern = null): array } return []; -} \ No newline at end of file +} diff --git a/src/Util/attempt.php b/src/Util/attempt.php index bc6211a..2b1895b 100644 --- a/src/Util/attempt.php +++ b/src/Util/attempt.php @@ -18,7 +18,7 @@ * @category Util * * @param callable $func The function to attempt. - * @param mixed[] $args The arguments to invoke `func` with. + * @param array $args The arguments to invoke `func` with. * * @return mixed|\Throwable Returns the `func` result or error object. * @@ -41,4 +41,4 @@ function attempt(callable $func, ...$args) } catch (\ParseError | \Error | \Throwable | \SoapFault | \DOMException | \PDOException $e) { return $e; } -} \ No newline at end of file +} diff --git a/src/Util/defaultTo.php b/src/Util/defaultTo.php new file mode 100644 index 0000000..79b8baf --- /dev/null +++ b/src/Util/defaultTo.php @@ -0,0 +1,43 @@ + + * @copyright Copyright (c) 2019 + */ + +namespace _; + +/** + * Checks value to determine whether a default value should be returned in its place. + * The defaultValue is returned if value is NaN or null. + * + * @category Util + * + * @param mixed $value Any value. + * @param mixed $defaultValue Value to return when $value is null or NAN + * + * @return mixed Returns `value`. + * + * @author punit-kulal + * + * @example + * + * $a = null; + * + * defaultTo($a, "default"); + * // => "default" + * + * $a = "x"; + * + * defaultTo($a, "default"); + * // => "x" + * + */ +function defaultTo($value, $defaultValue) +{ + return (null !== $value && (is_object($value) || !\is_nan(\floatval($value)))) ? $value : $defaultValue; +} diff --git a/src/Util/identity.php b/src/Util/identity.php index 86c11b6..9b0ef5f 100644 --- a/src/Util/identity.php +++ b/src/Util/identity.php @@ -30,4 +30,4 @@ function identity($value = null) { return $value; -} \ No newline at end of file +} diff --git a/src/Util/property.php b/src/Util/property.php index 233e850..17f75f0 100644 --- a/src/Util/property.php +++ b/src/Util/property.php @@ -11,6 +11,8 @@ namespace _; +use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; /** @@ -18,7 +20,7 @@ * * @category Util * - * @param array|string path The path of the property to get. + * @param array|string $path The path of the property to get. * * @return callable Returns the new accessor function. * @example @@ -42,7 +44,6 @@ function property($path): callable ->getPropertyAccessor(); return function ($value, $index = 0, $collection = []) use ($path, $propertyAccess) { - $path = \implode('.', (array) $path); if (\is_array($value)) { @@ -61,6 +62,10 @@ function property($path): callable } } - return $propertyAccess->getValue($value, $path); + try { + return $propertyAccess->getValue($value, $path); + } catch (NoSuchPropertyException | NoSuchIndexException $e) { + return null; + } }; -} \ No newline at end of file +} diff --git a/src/bootstrap.php b/src/bootstrap.php index 745a01a..8bc7456 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -9,6 +9,14 @@ * @copyright Copyright (c) 2017 */ -foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__, RecursiveDirectoryIterator::SKIP_DOTS)) as $file) { - require_once $file->getRealPath(); +if (file_exists($file = __DIR__.'/compiled.php')) { + require_once $file; +} else { + require_once __DIR__.'/internal/Traits/CacheDataTrait.php'; + require_once __DIR__.'/internal/unicode.php'; + require_once __DIR__.'/CacheInterface.php'; + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__, RecursiveDirectoryIterator::SKIP_DOTS)) as $file) { + require_once $file->getRealPath(); + } } diff --git a/src/internal/Traits/CacheDataTrait.php b/src/internal/Traits/CacheDataTrait.php new file mode 100644 index 0000000..4700cce --- /dev/null +++ b/src/internal/Traits/CacheDataTrait.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal\Traits; + +trait CacheDataTrait +{ + private $__data__ = []; + + private $size; + + public function getSize(): int + { + return $this->size; + } +} diff --git a/src/internal/arrayIncludes.php b/src/internal/arrayIncludes.php index 5d56cbd..3c95463 100644 --- a/src/internal/arrayIncludes.php +++ b/src/internal/arrayIncludes.php @@ -16,4 +16,4 @@ function arrayIncludes(?array $array, $value) { return null !== $array && indexOf($array, $value, 0) > -1; -} \ No newline at end of file +} diff --git a/src/internal/arrayIncludesWith.php b/src/internal/arrayIncludesWith.php index 4cedfba..6b218d1 100644 --- a/src/internal/arrayIncludesWith.php +++ b/src/internal/arrayIncludesWith.php @@ -22,4 +22,4 @@ function arrayIncludesWith(?array $array, $value, callable $comparator) } return false; -} \ No newline at end of file +} diff --git a/src/internal/arrayMap.php b/src/internal/arrayMap.php new file mode 100644 index 0000000..2a56b5d --- /dev/null +++ b/src/internal/arrayMap.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function arrayMap(?array $array, callable $iteratee) +{ + $index = -1; + $length = null === $array ? 0 : \count($array); + $result = []; + + while (++$index < $length) { + $result[$index] = $iteratee($array[$index], $index, $array); + } + + return $result; +} diff --git a/src/internal/arrayPush.php b/src/internal/arrayPush.php new file mode 100644 index 0000000..a0af2b3 --- /dev/null +++ b/src/internal/arrayPush.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function arrayPush(&$array, $values) +{ + $index = -1; + $length = \is_array($values) ? \count($values) : \strlen($values); + $offset = \count($array); + + while (++$index < $length) { + $array[$offset + $index] = $values[$index]; + } + + return $array; +} diff --git a/src/internal/assocIndexOf.php b/src/internal/assocIndexOf.php new file mode 100644 index 0000000..b8cb19a --- /dev/null +++ b/src/internal/assocIndexOf.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +use function _\eq; + +function assocIndexOf(array $array, $key): int +{ + $length = \count($array); + while ($length--) { + if (eq($array[$length][0], $key)) { + return $length; + } + } + + return -1; +} diff --git a/src/internal/baseFlatten.php b/src/internal/baseFlatten.php index 9072d37..61a7a03 100644 --- a/src/internal/baseFlatten.php +++ b/src/internal/baseFlatten.php @@ -16,15 +16,15 @@ * * @private * - * @param array $array The array to flatten. - * @param int $depth The maximum recursion depth. - * @param callable $predicate The function invoked per iteration [isFlattenable]. - * @param bool $isStrict Restrict to values that pass `predicate` checks. - * @param array $result The initial result value. + * @param array|null $array The array to flatten. + * @param int $depth The maximum recursion depth. + * @param callable|null $predicate The function invoked per iteration [isFlattenable]. + * @param bool|null $isStrict Restrict to values that pass `predicate` checks. + * @param array|null $result The initial result value. * * @return array Returns the new flattened array. */ -function baseFlatten(?array $array, float $depth, callable $predicate = null, bool $isStrict = null, array $result = null): array +function baseFlatten(?array $array, int $depth, callable $predicate = null, bool $isStrict = null, array $result = null): array { $result = $result ?? []; @@ -40,12 +40,12 @@ function baseFlatten(?array $array, float $depth, callable $predicate = null, bo // Recursively flatten arrays (susceptible to call stack limits). $result = baseFlatten($value, $depth - 1, $predicate, $isStrict, $result); } else { - $result = \array_merge($result, $value); + arrayPush($result, $value); } - } else if (!$isStrict) { - $result[count($result)] = $value; + } elseif (!$isStrict) { + $result[\count($result)] = $value; } } return $result; -} \ No newline at end of file +} diff --git a/src/internal/baseGet.php b/src/internal/baseGet.php new file mode 100644 index 0000000..a9ff96a --- /dev/null +++ b/src/internal/baseGet.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +use function _\property; + +function baseGet($object, $path) +{ + $path = castPath($path, $object); + + $index = 0; + $length = \count($path); + + while ($object !== null && !is_scalar($object) && $index < $length) { + $object = property(toKey($path[$index++]))($object); + } + + return ($index > 0 && $index === $length) ? $object : null; +} diff --git a/src/internal/baseIndexOfWith.php b/src/internal/baseIndexOfWith.php index 1eb7f39..5a391c5 100644 --- a/src/internal/baseIndexOfWith.php +++ b/src/internal/baseIndexOfWith.php @@ -23,4 +23,4 @@ function baseIndexOfWith(array $array, $value, int $fromIndex, $comparator) } return -1; -} \ No newline at end of file +} diff --git a/src/internal/baseIntersection.php b/src/internal/baseIntersection.php index 080a6cd..e366d37 100644 --- a/src/internal/baseIntersection.php +++ b/src/internal/baseIntersection.php @@ -41,15 +41,15 @@ function baseIntersection($arrays, ?callable $iteratee, $comparator = null) $computed = $iteratee ? $iteratee($value) : $value; $value = ($comparator ?: $value !== 0) ? $value : 0; - if (!($seen ? isset($seen[$computed]) : $includes($result, $computed, $comparator))) { + if (!($seen ? \is_scalar($computed) && isset($seen[$computed]) : $includes($result, $computed, $comparator))) { $othIndex = $othLength; while (--$othIndex) { $cache = $caches[$othIndex]; - if (!($cache ? isset($cache[$computed]) : $includes($arrays[$othIndex], $computed, $comparator))) { + if (!(!empty($cache) ? isset($cache[$computed]) : $includes($arrays[$othIndex], $computed, $comparator))) { continue 2; } } - if ($seen) { + if (empty($seen)) { $seen[] = $computed; } @@ -58,4 +58,4 @@ function baseIntersection($arrays, ?callable $iteratee, $comparator = null) } return $result; -} \ No newline at end of file +} diff --git a/src/internal/baseInvoke.php b/src/internal/baseInvoke.php new file mode 100644 index 0000000..1170f84 --- /dev/null +++ b/src/internal/baseInvoke.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +use function _\last; + +function baseInvoke($object, $path, $args) +{ + $path = castPath($path, $object); + $object = parent($object, $path); + /** @var callable|null $func */ + $func = null === $object ? $object : [$object, toKey(last($path))]; + + return \is_callable($func) ? $func($object, ...$args) : null; +} diff --git a/src/internal/baseIteratee.php b/src/internal/baseIteratee.php index 22efba8..13cc2af 100644 --- a/src/internal/baseIteratee.php +++ b/src/internal/baseIteratee.php @@ -30,4 +30,4 @@ function baseIteratee($value): callable } return property($value); -} \ No newline at end of file +} diff --git a/src/internal/baseMatches.php b/src/internal/baseMatches.php index 400c027..3f39f4f 100644 --- a/src/internal/baseMatches.php +++ b/src/internal/baseMatches.php @@ -21,7 +21,7 @@ function baseMatches($source): callable return true; } - if (\is_array($source) || $source instanceof \Traversable) { + if (\is_iterable($source)) { foreach ($source as $k => $v) { if (!isEqual(property($k)($value, $index, $collection), $v)) { return false; @@ -33,4 +33,4 @@ function baseMatches($source): callable return false; }; -} \ No newline at end of file +} diff --git a/src/internal/baseMatchesProperty.php b/src/internal/baseMatchesProperty.php index 2f48dd9..f1d7797 100644 --- a/src/internal/baseMatchesProperty.php +++ b/src/internal/baseMatchesProperty.php @@ -19,4 +19,4 @@ function baseMatchesProperty($property, $source): callable return function ($value, $index, $collection) use ($property, $source) { return isEqual(property($property)($value, $index, $collection), $source); }; -} \ No newline at end of file +} diff --git a/src/internal/baseOrderBy.php b/src/internal/baseOrderBy.php new file mode 100644 index 0000000..d8c2d89 --- /dev/null +++ b/src/internal/baseOrderBy.php @@ -0,0 +1,33 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +use function _\map; +use function _\sortBy; + +function baseOrderBy(iterable $collection, array $iteratees, array $orders): array +{ + $index = -1; + $iteratees = arrayMap($iteratees, baseUnary('\_\internal\baseIteratee')); + + $result = map($collection, function ($value) use ($iteratees, &$index) { + $criteria = arrayMap($iteratees, function ($iteratee) use ($value) { + return $iteratee($value); + }); + + return ['criteria' => $criteria, 'index' => ++$index, 'value' => $value]; + }); + + return map(sortBy($result, function ($object, $other) use ($orders) { + return compareMultiple($object, $other, $orders); + }), 'value'); +} diff --git a/src/internal/basePick.php b/src/internal/basePick.php new file mode 100644 index 0000000..477ab46 --- /dev/null +++ b/src/internal/basePick.php @@ -0,0 +1,19 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function basePick($object, $paths): \stdClass +{ + return basePickBy($object, $paths, function ($value, $path) use ($object) { + return property_exists($object, $path) || method_exists($object, 'get'.(ucfirst($path))); + }); +} diff --git a/src/internal/basePickBy.php b/src/internal/basePickBy.php new file mode 100644 index 0000000..3176a70 --- /dev/null +++ b/src/internal/basePickBy.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function basePickBy($object, $paths, callable $predicate): \stdClass +{ + $index = -1; + $length = \is_array($paths) ? \count($paths) : \strlen($paths); + $result = new \stdClass(); + + while (++$index < $length) { + $path = $paths[$index]; + $value = baseGet($object, $path); + + if ($predicate($value, $path)) { + baseSet($result, castPath($path, $object), $value); + } + } + + return $result; +} diff --git a/src/internal/baseProperty.php b/src/internal/baseProperty.php index cd14aa7..803596e 100644 --- a/src/internal/baseProperty.php +++ b/src/internal/baseProperty.php @@ -14,13 +14,13 @@ /** * The base implementation of `_.property` without support for deep paths. * - * @param string $key The key of the property to get. + * @param mixed $key The key of the property to get. * * @return callable Returns the new accessor function. */ -function baseProperty(string $key) +function baseProperty($key) { return function ($object) use ($key) { return null === $object ? null : $object[$key]; }; -} \ No newline at end of file +} diff --git a/src/internal/basePullAll.php b/src/internal/basePullAll.php index 788e761..9b69ae3 100644 --- a/src/internal/basePullAll.php +++ b/src/internal/basePullAll.php @@ -13,7 +13,7 @@ function basePullAll(&$array, array $values, ?callable $iteratee, callable $comparator = null) { - $indexOf = $comparator ? '_\internal\baseIndexOfWith' : '_\indexOf'; + $indexOf = $comparator ? '_\\internal\\baseIndexOfWith' : '_\\indexOf'; $seen = $array; @@ -34,4 +34,4 @@ function basePullAll(&$array, array $values, ?callable $iteratee, callable $comp } return $array; -} \ No newline at end of file +} diff --git a/src/internal/baseReduce.php b/src/internal/baseReduce.php new file mode 100644 index 0000000..9d0f360 --- /dev/null +++ b/src/internal/baseReduce.php @@ -0,0 +1,27 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function baseReduce(iterable $array, $iteratee, $accumulator, $initAccum = null) +{ + $length = \is_array($array) || $array instanceof \Countable ? \count($array) : 0; + + if ($initAccum && $length) { + $accumulator = \current($array); + } + + foreach ($array as $key => $value) { + $accumulator = $iteratee($accumulator, $value, $key, $array); + } + + return $accumulator; +} diff --git a/src/internal/baseRest.php b/src/internal/baseRest.php new file mode 100644 index 0000000..95094d0 --- /dev/null +++ b/src/internal/baseRest.php @@ -0,0 +1,17 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function baseRest(callable $func, $start = null): callable +{ + return overRest($func, $start, '\_\identity'); +} diff --git a/src/internal/baseSet.php b/src/internal/baseSet.php new file mode 100644 index 0000000..204a908 --- /dev/null +++ b/src/internal/baseSet.php @@ -0,0 +1,56 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function baseSet($object, $path, $value, callable $customizer = null) +{ + if (!\is_object($object)) { + return $object; + } + + $path = castPath($path, $object); + + $index = -1; + $length = \count($path); + $lastIndex = $length - 1; + $nested = $object; + + while ($nested !== null && ++$index < $length) { + $key = toKey($path[$index]); + + if ($index !== $lastIndex) { + $objValue = \is_array($nested) ? ($nested[$key] ?? null) : ($nested->$key ?? null); + $newValue = $customizer ? $customizer($objValue, $key, $nested) : $objValue; + if (null === $newValue) { + $newValue = \is_object($objValue) ? $objValue : (\is_numeric($path[$index + 1]) ? [] : new \stdClass()); + } + + if (\is_array($nested)) { + $nested[$key] = $newValue; + } else { + $nested->{$key} = $newValue; + } + + if (\is_array($nested)) { + $nested = &$nested[$key]; + } else { + $nested = &$nested->$key; + } + + continue; + } + + $nested->{$key} = $value; + } + + return $object; +} diff --git a/src/internal/baseTimes.php b/src/internal/baseTimes.php new file mode 100644 index 0000000..cfc60b6 --- /dev/null +++ b/src/internal/baseTimes.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function baseTimes(int $n, callable $iteratee) +{ + $index = -1; + $result = []; + + while (++$index < $n) { + $result[$index] = $iteratee($index); + } + + return $result; +} diff --git a/src/internal/baseUnary.php b/src/internal/baseUnary.php new file mode 100644 index 0000000..ddfbf80 --- /dev/null +++ b/src/internal/baseUnary.php @@ -0,0 +1,19 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function baseUnary($func) +{ + return function ($value) use ($func) { + return $func($value); + }; +} diff --git a/src/internal/baseUniq.php b/src/internal/baseUniq.php new file mode 100644 index 0000000..1445b74 --- /dev/null +++ b/src/internal/baseUniq.php @@ -0,0 +1,57 @@ + + * @copyright Copyright (c) 2018 + */ + +function baseUniq(array $array, callable $iteratee = null, callable $comparator = null) +{ + $index = -1; + $includes = '\_\internal\arrayIncludes'; + $length = \count($array); + $isCommon = true; + $result = []; + $seen = $result; + + if ($comparator) { + $isCommon = false; + $includes = '\_\internal\arrayIncludesWith'; + } else { + $seen = $iteratee ? [] : $result; + } + + while (++$index < $length) { + $value = $array[$index]; + $computed = $iteratee ? $iteratee($value) : $value; + + $value = ($comparator || $value !== 0) ? $value : 0; + + if ($isCommon && $computed) { + $seenIndex = \count($seen); + while ($seenIndex--) { + if ($seen[$seenIndex] === $computed) { + continue 2; + } + } + if ($iteratee) { + $seen[] = $computed; + } + + $result[] = $value; + } elseif (!$includes($result, $computed, $comparator)) { + if ($seen !== $result) { + $seen[] = $computed; + } + $result[] = $value; + } + } + + return $result; +} diff --git a/src/internal/castPath.php b/src/internal/castPath.php new file mode 100644 index 0000000..6c2ea8e --- /dev/null +++ b/src/internal/castPath.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function castPath($value, $object): array +{ + if (\is_array($value)) { + return $value; + } + + return isKey($value, $object) ? [$value] : stringToPath((string) $value); +} diff --git a/src/internal/castSlice.php b/src/internal/castSlice.php index 4e945b5..6d50495 100644 --- a/src/internal/castSlice.php +++ b/src/internal/castSlice.php @@ -25,7 +25,7 @@ function castSlice(array $array, int $start, ?int $end = null): array { $length = \count($array); - $end = null === $end ? $length : $end; + $end = $end ?? $length; return (!$start && $end >= $length) ? $array : \array_slice($array, $start, $end); -} \ No newline at end of file +} diff --git a/src/internal/compareMultiple.php b/src/internal/compareMultiple.php new file mode 100644 index 0000000..ec345de --- /dev/null +++ b/src/internal/compareMultiple.php @@ -0,0 +1,35 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function compareMultiple($object, $other, $orders) +{ + $index = -1; + $objCriteria = $object['criteria']; + $othCriteria = $other['criteria']; + $length = \count($objCriteria); + $ordersLength = \count($orders); + + while (++$index < $length) { + $result = $objCriteria[$index] <=> $othCriteria[$index]; + if ($result) { + if ($index >= $ordersLength) { + return $result; + } + $order = $orders[$index]; + + return $result * ('desc' === $order ? -1 : 1); + } + } + + return $object['index'] - $other['index']; +} diff --git a/src/internal/createAggregator.php b/src/internal/createAggregator.php new file mode 100644 index 0000000..ea63c5a --- /dev/null +++ b/src/internal/createAggregator.php @@ -0,0 +1,31 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +use function _\each; + +function createAggregator($setter, $initializer = null) +{ + return function ($collection, $iteratee) use ($setter, $initializer) { + $accumulator = null !== $initializer ? $initializer() : []; + + $func = function ($collection, $setter, &$accumulator, $iteratee) { + each($collection, function ($value, $key, $collection) use ($setter, &$accumulator, $iteratee) { + $accumulator = $setter($accumulator, $value, $iteratee($value), $collection); + }); + + return $accumulator; + }; + + return $func($collection, $setter, $accumulator, baseIteratee($iteratee)); + }; +} diff --git a/src/internal/createMathOperation.php b/src/internal/createMathOperation.php new file mode 100644 index 0000000..7f4fcf0 --- /dev/null +++ b/src/internal/createMathOperation.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function createMathOperation(callable $operator, $defaultValue) +{ + return function ($value, $other) use ($defaultValue, $operator) { + if (null === $value && null === $other) { + return $defaultValue; + } + + $result = null; + + if (null !== $value) { + $result = $value; + } + + if (null !== $other) { + if (null === $result) { + return $other; + } + + $result = $operator($value, $other); + } + + return $result; + }; +} diff --git a/src/internal/flatRest.php b/src/internal/flatRest.php new file mode 100644 index 0000000..0cee4a8 --- /dev/null +++ b/src/internal/flatRest.php @@ -0,0 +1,17 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function flatRest(callable $func): callable +{ + return shortOut(overRest($func, null, '\_\flatten')); +} diff --git a/src/internal/hasUnicode.php b/src/internal/hasUnicode.php index 22c0ceb..117866e 100644 --- a/src/internal/hasUnicode.php +++ b/src/internal/hasUnicode.php @@ -26,4 +26,4 @@ function hasUnicode(string $string): bool { return \preg_match('#'.reHasUnicode.'#u', $string) > 0; -} \ No newline at end of file +} diff --git a/src/internal/isFlattenable.php b/src/internal/isFlattenable.php index fa298e4..f14b5b4 100644 --- a/src/internal/isFlattenable.php +++ b/src/internal/isFlattenable.php @@ -22,5 +22,5 @@ */ function isFlattenable($value): bool { - return \is_array($value) && \range(0, \count($value) - 1) === \array_keys($value); -} \ No newline at end of file + return \is_array($value) && ([] === $value || \range(0, \count($value) - 1) === \array_keys($value)); +} diff --git a/src/internal/isIterateeCall.php b/src/internal/isIterateeCall.php index 1b1c792..b9c3169 100644 --- a/src/internal/isIterateeCall.php +++ b/src/internal/isIterateeCall.php @@ -24,7 +24,7 @@ */ function isIterateeCall($value, $index = null, $object = null) { - if (!\is_object($object) || !\is_array($object)) { + if (!\is_object($object) && !\is_array($object)) { return false; } @@ -43,4 +43,4 @@ function isIterateeCall($value, $index = null, $object = null) } return false; -} \ No newline at end of file +} diff --git a/src/internal/isKey.php b/src/internal/isKey.php index 5d4019a..bb0ce5b 100644 --- a/src/internal/isKey.php +++ b/src/internal/isKey.php @@ -12,13 +12,13 @@ namespace _\internal; /** Used to match property names within property paths. */ -const reIsDeepProp = '/\.|\[(?:[^[\]]*|(["\'])(?:(?!\1)[^\\]|\\.)*?\1)\]/'; +const reIsDeepProp = '#\.|\[(?:[^[\]]*|(["\'])(?:(?!\1)[^\\\\]|\\.)*?\1)\]#'; const reIsPlainProp = '/^\w*$/'; /** * Checks if `value` is a property name and not a property path. * - * @param mixed value The value to check. + * @param mixed $value The value to check. * @param object|array $object The object to query keys on. * * @return boolean Returns `true` if `value` is a property name, else `false`. @@ -29,9 +29,9 @@ function isKey($value, $object = []): bool return false; } - if (\is_scalar($value)) { + if (\is_numeric($value)) { return true; } return \preg_match(reIsPlainProp, $value) || !\preg_match(reIsDeepProp, $value) || (null !== $object && isset(((object) $object)->$value)); -} \ No newline at end of file +} diff --git a/src/internal/memoizeCapped.php b/src/internal/memoizeCapped.php new file mode 100644 index 0000000..0eb18d0 --- /dev/null +++ b/src/internal/memoizeCapped.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +use function _\memoize; + +function memoizeCapped(callable $func) +{ + $MaxMemoizeSize = 500; + $result = memoize($func, function ($key) use ($MaxMemoizeSize) { + if ($this->cache->getSize() === $MaxMemoizeSize) { + $this->cache->clear(); + } + + return $key; + }); + + return $result; +} diff --git a/src/internal/overRest.php b/src/internal/overRest.php new file mode 100644 index 0000000..a17be23 --- /dev/null +++ b/src/internal/overRest.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function overRest(callable $func, $start, callable $transform): callable +{ + $parameters = (new \ReflectionFunction($func))->getNumberOfParameters(); + $start = max($start ?? $parameters - 1, 0); + + return function () use ($func, $start, $transform) { + $args = \func_get_args(); + $index = -1; + $length = \max(\count($args) - $start, 0); + $array = []; + + while (++$index < $length) { + $array[$index] = $args[$start + $index]; + } + $index = -1; + $otherArgs = []; + while (++$index < $start) { + $otherArgs[$index] = $args[$index]; + } + $otherArgs[$start] = $transform($array); + + return $func(...$otherArgs); + }; +} diff --git a/src/internal/parent.php b/src/internal/parent.php new file mode 100644 index 0000000..49b3d78 --- /dev/null +++ b/src/internal/parent.php @@ -0,0 +1,17 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +function parent($object, $path) +{ + return count($path) < 2 ? $object : null; +} diff --git a/src/internal/shortOut.php b/src/internal/shortOut.php new file mode 100644 index 0000000..826ac6b --- /dev/null +++ b/src/internal/shortOut.php @@ -0,0 +1,48 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +const HOT_COUNT = 800; +const HOT_SPAN = 16; + +/** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * + * @param callable $func The function to restrict. + * + * @return callable Returns the new shortable function. + */ +function shortOut(callable $func): callable +{ + $count = 0; + $lastCalled = 0; + + return function () use ($func, &$count, &$lastCalled) { + $stamp = microtime(true); + $remaining = HOT_SPAN - ($stamp - $lastCalled); + + $lastCalled = $stamp; + if ($remaining > 0) { + if (++$count >= HOT_COUNT) { + return func_get_arg(0); + } + } else { + $count = 0; + } + + return $func(...func_get_args()); + }; +} diff --git a/src/internal/stringSize.php b/src/internal/stringSize.php index 4957576..10efb63 100644 --- a/src/internal/stringSize.php +++ b/src/internal/stringSize.php @@ -15,9 +15,12 @@ * Gets the number of symbols in `string`. * * @private - * @param {string} string The string to inspect. - * @returns {number} Returns the string size. + * + * @param string $string The string to inspect. + * + * @return int Returns the string size. */ -function stringSize(string $string) { +function stringSize(string $string): int +{ return hasUnicode($string) ? unicodeSize($string) : \strlen($string); -} \ No newline at end of file +} diff --git a/src/internal/stringToArray.php b/src/internal/stringToArray.php index f451361..ff9458f 100644 --- a/src/internal/stringToArray.php +++ b/src/internal/stringToArray.php @@ -18,9 +18,9 @@ * * @param string $string The string to convert. * - * @returns array Returns the converted array. + * @return array Returns the converted array. */ function stringToArray(string $string): array { return hasUnicode($string) ? unicodeToArray($string) : \str_split($string); -} \ No newline at end of file +} diff --git a/src/internal/stringToPath.php b/src/internal/stringToPath.php new file mode 100644 index 0000000..d2d8b39 --- /dev/null +++ b/src/internal/stringToPath.php @@ -0,0 +1,36 @@ + + * @copyright Copyright (c) 2018 + */ + +namespace _\internal; + +const reLeadingDot = '/^\./'; +const rePropName = '#[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["\'])((?:(?!\2)[^\\\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))#'; + +/** Used to match backslashes in property paths. */ +const reEscapeChar = '/\\(\\)?/g'; + +function stringToPath(...$args) +{ + return memoizeCapped(function ($string) { + $result = []; + if (\preg_match(reLeadingDot, $string)) { + $result[] = ''; + } + + \preg_match_all(rePropName, $string, $matches, PREG_SPLIT_DELIM_CAPTURE); + + foreach ($matches as $match) { + $result[] = $match[1] ?? $match[0]; + } + + return $result; + })(...$args); +} diff --git a/src/internal/toKey.php b/src/internal/toKey.php index e44522f..d7a8117 100644 --- a/src/internal/toKey.php +++ b/src/internal/toKey.php @@ -14,9 +14,9 @@ /** * Converts `value` to a string key if it's not a string. * - * @param mixed value The value to inspect. + * @param mixed $value The value to inspect. * - * @returns string Returns the key. + * @return string Returns the key. */ function toKey($value): string { @@ -27,4 +27,4 @@ function toKey($value): string $result = (string) $value; return ('0' === $result && (1 / $value) === -INF) ? '-0' : $result; -} \ No newline at end of file +} diff --git a/src/internal/unicode.php b/src/internal/unicode.php index eee0dc8..dd371ba 100644 --- a/src/internal/unicode.php +++ b/src/internal/unicode.php @@ -60,4 +60,4 @@ define('rsSymbol', '(?:'.implode('|', [rsNonAstralCombo, rsCombo, rsRegional, rsSurrPair, rsAstral]).')'); /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ -const reUnicode = rsFitz.'(?='.rsFitz.')|'.rsSymbol.rsSeq; \ No newline at end of file +const reUnicode = rsFitz.'(?='.rsFitz.')|'.rsSymbol.rsSeq; diff --git a/src/internal/unicodeSize.php b/src/internal/unicodeSize.php index af7f993..47ed627 100644 --- a/src/internal/unicodeSize.php +++ b/src/internal/unicodeSize.php @@ -18,9 +18,9 @@ * * @param string $string The string inspect. * - * @returns int Returns the string size. + * @return int Returns the string size. */ -function unicodeSize($string): int +function unicodeSize(string $string): int { - return \preg_match_all(reUnicode, $string); -} \ No newline at end of file + return \preg_match_all(reUnicode, $string) ?: 0; +} diff --git a/src/internal/unicodeToArray.php b/src/internal/unicodeToArray.php index bcf5f3e..b63030f 100644 --- a/src/internal/unicodeToArray.php +++ b/src/internal/unicodeToArray.php @@ -18,7 +18,7 @@ * * @param string $string The string to convert. * - * @returns array Returns the converted array. + * @return array Returns the converted array. */ function unicodeToArray(string $string): array { diff --git a/src/internal/unicodeWords.php b/src/internal/unicodeWords.php index 25602f6..9f2a0c3 100644 --- a/src/internal/unicodeWords.php +++ b/src/internal/unicodeWords.php @@ -16,10 +16,11 @@ * * @private * - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. + * @param string $string The string to inspect. + * + * @return array Returns the words of `string`. */ -function unicodeWords(string $string) +function unicodeWords(string $string): array { $regex = '#'.\implode('|', [ rsUpper.'?'.rsLower.'+'.rsOptContrLower.'(?='.\implode('|', [rsBreak, rsUpper, '$']).')', @@ -37,4 +38,4 @@ function unicodeWords(string $string) } return []; -} \ No newline at end of file +} diff --git a/tests/Array/ChunkTest.php b/tests/Array/ChunkTest.php index 36ae165..7eec8b4 100644 --- a/tests/Array/ChunkTest.php +++ b/tests/Array/ChunkTest.php @@ -23,4 +23,4 @@ public function testChunk() $this->assertSame([], chunk([], -12)); $this->assertSame([], chunk([], 22)); } -} \ No newline at end of file +} diff --git a/tests/Array/CompactTest.php b/tests/Array/CompactTest.php index 0ff804d..578b600 100644 --- a/tests/Array/CompactTest.php +++ b/tests/Array/CompactTest.php @@ -20,4 +20,4 @@ public function testChunk() $this->assertSame([], compact([])); $this->assertSame([], compact(null)); } -} \ No newline at end of file +} diff --git a/tests/Array/ConcatTest.php b/tests/Array/ConcatTest.php index e601367..672945e 100644 --- a/tests/Array/ConcatTest.php +++ b/tests/Array/ConcatTest.php @@ -22,4 +22,4 @@ public function testChunk() $this->assertSame([1, 2, 3, [4]], $other); $this->assertSame([1], $array); // Ensure original array doesn't get changed } -} \ No newline at end of file +} diff --git a/tests/Array/DifferenceByTest.php b/tests/Array/DifferenceByTest.php index 32acd27..ac13c82 100644 --- a/tests/Array/DifferenceByTest.php +++ b/tests/Array/DifferenceByTest.php @@ -14,10 +14,10 @@ class DifferenceByTest extends TestCase { - public function testChunk() + public function testDifferenceBy() { $this->assertSame([], differenceBy([])); $this->assertSame([1, 2], differenceBy([1, 2], [3, 4])); $this->assertSame([1.2], differenceBy([2.1, 1.2], [2.3, 3.4], 'floor')); } -} \ No newline at end of file +} diff --git a/tests/Array/DifferenceTest.php b/tests/Array/DifferenceTest.php index 149033c..5e74f5e 100644 --- a/tests/Array/DifferenceTest.php +++ b/tests/Array/DifferenceTest.php @@ -18,4 +18,4 @@ public function testChunk() { $this->assertSame([1], difference([2, 1], [2, 3])); } -} \ No newline at end of file +} diff --git a/tests/Array/DifferenceWithTest.php b/tests/Array/DifferenceWithTest.php index 89bf204..75e3032 100644 --- a/tests/Array/DifferenceWithTest.php +++ b/tests/Array/DifferenceWithTest.php @@ -22,4 +22,4 @@ public function testDifferenceWith() $this->assertSame([], differenceWith([])); $this->assertSame([1, 2], differenceWith([1, 2], [3, 4])); } -} \ No newline at end of file +} diff --git a/tests/Array/DropRightTest.php b/tests/Array/DropRightTest.php index b0a6f14..d94d99c 100644 --- a/tests/Array/DropRightTest.php +++ b/tests/Array/DropRightTest.php @@ -21,4 +21,4 @@ public function testDropRight() $this->assertSame([], dropRight([1, 2, 3], 5)); $this->assertSame([1, 2, 3], dropRight([1, 2, 3], 0)); } -} \ No newline at end of file +} diff --git a/tests/Array/DropRightWhileTest.php b/tests/Array/DropRightWhileTest.php index 2ded769..f0ced1c 100644 --- a/tests/Array/DropRightWhileTest.php +++ b/tests/Array/DropRightWhileTest.php @@ -22,6 +22,8 @@ public function testDropRightWhile() ['user' => 'pebbles', 'active' => true], ]; - $this->assertSame([[ 'user' => 'barney', 'active' => false ]], dropRightWhile($users, function($user) { return $user['active']; })); + $this->assertSame([[ 'user' => 'barney', 'active' => false ]], dropRightWhile($users, function ($user) { + return $user['active']; + })); } -} \ No newline at end of file +} diff --git a/tests/Array/DropTest.php b/tests/Array/DropTest.php index 48a94d1..67320d9 100644 --- a/tests/Array/DropTest.php +++ b/tests/Array/DropTest.php @@ -21,4 +21,4 @@ public function testDrop() $this->assertSame([], drop([1, 2, 3], 5)); $this->assertSame([1, 2, 3], drop([1, 2, 3], 0)); } -} \ No newline at end of file +} diff --git a/tests/Array/DropWhileTest.php b/tests/Array/DropWhileTest.php index bae78bb..50bef5a 100644 --- a/tests/Array/DropWhileTest.php +++ b/tests/Array/DropWhileTest.php @@ -22,6 +22,28 @@ public function testDropWhile() ['user' => 'pebbles', 'active' => false], ]; - $this->assertSame([['user' => 'pebbles', 'active' => false]], dropWhile($users, function ($user) { return $user['active']; })); + $this->assertSame([['user' => 'pebbles', 'active' => false]], dropWhile($users, function ($user) { + return $user['active']; + })); + + $lines = [ + 'Processing report:', + 'Processed: 1', + 'Successful: 1', + '', + '', + ]; + + $lines = dropWhile($lines, static function ($x) { + return trim((string) $x) !== ''; + }); + + self::assertEquals(['', ''], $lines); + + $lines = dropWhile($lines, static function ($x) { + return trim((string) $x) === ''; + }); + + self::assertEmpty($lines); } -} \ No newline at end of file +} diff --git a/tests/Array/FindIndexTest.php b/tests/Array/FindIndexTest.php index df0d520..420032d 100644 --- a/tests/Array/FindIndexTest.php +++ b/tests/Array/FindIndexTest.php @@ -22,7 +22,9 @@ public function testFindIndex() ['user' => 'pebbles', 'active' => true], ]; - $this->assertSame(2, findIndex($users, function ($user) { return $user['user'] === 'pebbles'; })); + $this->assertSame(2, findIndex($users, function ($user) { + return $user['user'] === 'pebbles'; + })); $this->assertSame(1, findIndex($users, ['user' => 'fred', 'active' => false])); $this->assertSame(0, findIndex($users, ['active', false])); $this->assertSame(2, findIndex($users, 'active')); @@ -30,4 +32,4 @@ public function testFindIndex() $this->assertSame(2, findIndex($users, 'active', -2)); $this->assertSame(-1, findIndex($users, 'nothing')); } -} \ No newline at end of file +} diff --git a/tests/Array/FindLastIndexTest.php b/tests/Array/FindLastIndexTest.php index 61aacb7..df82393 100644 --- a/tests/Array/FindLastIndexTest.php +++ b/tests/Array/FindLastIndexTest.php @@ -22,8 +22,12 @@ public function testFindLastIndex() ['user' => 'pebbles', 'active' => false], ]; - $this->assertSame(2, findLastIndex($users, function ($user) { return $user['user'] === 'pebbles'; })); - $this->assertSame(-1, findLastIndex($users, function ($user) { return $user['user'] === 'wilma'; })); + $this->assertSame(2, findLastIndex($users, function ($user) { + return $user['user'] === 'pebbles'; + })); + $this->assertSame(-1, findLastIndex($users, function ($user) { + return $user['user'] === 'wilma'; + })); $this->assertSame(1, findLastIndex($users, ['user' => 'fred'], -1)); } -} \ No newline at end of file +} diff --git a/tests/Array/FlattenDeepTest.php b/tests/Array/FlattenDeepTest.php index 126d9f8..028529d 100644 --- a/tests/Array/FlattenDeepTest.php +++ b/tests/Array/FlattenDeepTest.php @@ -18,4 +18,4 @@ public function testFlattenDeep() { $this->assertSame([1, 2, 3, 4, 5], flattenDeep([1, [2, [3, [4]], 5]])); } -} \ No newline at end of file +} diff --git a/tests/Array/FlattenDepthTest.php b/tests/Array/FlattenDepthTest.php index d8a98e0..d796d64 100644 --- a/tests/Array/FlattenDepthTest.php +++ b/tests/Array/FlattenDepthTest.php @@ -20,4 +20,4 @@ public function testFlattenDepth() $this->assertSame([1, 2, [3, [4]], 5], flattenDepth($array, 1)); $this->assertSame([1, 2, 3, [4], 5], flattenDepth($array, 2)); } -} \ No newline at end of file +} diff --git a/tests/Array/FlattenTest.php b/tests/Array/FlattenTest.php index bf972c4..3c28d78 100644 --- a/tests/Array/FlattenTest.php +++ b/tests/Array/FlattenTest.php @@ -17,5 +17,7 @@ class FlattenTest extends TestCase public function testFlatten() { $this->assertSame([1, 2, [3, [4]], 5], flatten([1, [2, [3, [4]], 5]])); + + $this->assertSame([1, 2, 3, 4, 5, 6], flatten([[1, 2, 3], [], [4, 5, 6]])); } -} \ No newline at end of file +} diff --git a/tests/Array/FromPairsTest.php b/tests/Array/FromPairsTest.php index abb09c2..726db51 100644 --- a/tests/Array/FromPairsTest.php +++ b/tests/Array/FromPairsTest.php @@ -19,4 +19,4 @@ public function testFromPairs() $this->assertEquals((object) ['a' => 1, 'b' => 2], fromPairs([['a', 1], ['b', 2]])); $this->assertEquals(new \stdClass(), fromPairs([])); } -} \ No newline at end of file +} diff --git a/tests/Array/HeadTest.php b/tests/Array/HeadTest.php index bf2ed5f..6e6a54c 100644 --- a/tests/Array/HeadTest.php +++ b/tests/Array/HeadTest.php @@ -21,4 +21,4 @@ public function testHead() $this->assertSame(null, head([])); $this->assertSame(1, first([1, 2, 3])); // Test the alias } -} \ No newline at end of file +} diff --git a/tests/Array/IndexOfTest.php b/tests/Array/IndexOfTest.php index 203de6b..b93c795 100644 --- a/tests/Array/IndexOfTest.php +++ b/tests/Array/IndexOfTest.php @@ -20,4 +20,4 @@ public function testIndexOf() $this->assertSame(3, indexOf([1, 2, 1, 2], 2, 2)); $this->assertSame(3, indexOf([1, 2, 1, 2], 2, -1)); } -} \ No newline at end of file +} diff --git a/tests/Array/InitialTest.php b/tests/Array/InitialTest.php index 0ea8860..8aec608 100644 --- a/tests/Array/InitialTest.php +++ b/tests/Array/InitialTest.php @@ -18,4 +18,4 @@ public function testInitial() { $this->assertSame([1, 2], initial([1, 2, 3])); } -} \ No newline at end of file +} diff --git a/tests/Array/IntersectionByTest.php b/tests/Array/IntersectionByTest.php index 34c4cc0..f0bb695 100644 --- a/tests/Array/IntersectionByTest.php +++ b/tests/Array/IntersectionByTest.php @@ -19,4 +19,4 @@ public function testIntersectionBy() $this->assertSame([2.1], intersectionBy([2.1, 1.2], [2.3, 3.4], 'floor')); $this->assertSame([['x' => 1]], intersectionBy([['x' => 1]], [['x' => 2], ['x' => 1]], 'x')); } -} \ No newline at end of file +} diff --git a/tests/Array/IntersectionTest.php b/tests/Array/IntersectionTest.php index 7f89c8b..f8a9448 100644 --- a/tests/Array/IntersectionTest.php +++ b/tests/Array/IntersectionTest.php @@ -18,4 +18,4 @@ public function testIntersection() { $this->assertSame([2], intersection([2, 1], [2, 3])); } -} \ No newline at end of file +} diff --git a/tests/Array/IntersectionWithTest.php b/tests/Array/IntersectionWithTest.php index fa6d258..d8da175 100644 --- a/tests/Array/IntersectionWithTest.php +++ b/tests/Array/IntersectionWithTest.php @@ -19,7 +19,7 @@ public function testIntersectionWith() $objects = [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1]]; $others = [['x' => 1, 'y' => 1], ['x' => 1, 'y' => 2]]; - $this->assertSame([['x' => 1, 'y' => 2]], intersectionWith($objects, $others, '_::isEqual')); + $this->assertSame([['x' => 1, 'y' => 2]], intersectionWith($objects, $others, '_\isEqual')); $this->assertSame([['x' => 1, 'y' => 2]], intersectionWith($objects, $others)); } -} \ No newline at end of file +} diff --git a/tests/Array/LastIndexOfTest.php b/tests/Array/LastIndexOfTest.php index 09795f1..56da38a 100644 --- a/tests/Array/LastIndexOfTest.php +++ b/tests/Array/LastIndexOfTest.php @@ -20,4 +20,4 @@ public function testLastIndexOf() $this->assertSame(1, lastIndexOf([1, 2, 1, 2], 2, 2)); $this->assertSame(-1, lastIndexOf([1, 2, 1, 2], 12)); } -} \ No newline at end of file +} diff --git a/tests/Array/LastTest.php b/tests/Array/LastTest.php index c69020a..e41aa93 100644 --- a/tests/Array/LastTest.php +++ b/tests/Array/LastTest.php @@ -18,4 +18,4 @@ public function testLast() { $this->assertSame(3, last([1, 2, 3])); } -} \ No newline at end of file +} diff --git a/tests/Array/NthTest.php b/tests/Array/NthTest.php index e1978be..725f0d2 100644 --- a/tests/Array/NthTest.php +++ b/tests/Array/NthTest.php @@ -21,4 +21,4 @@ public function testNth() $this->assertSame('c', nth($array, -2)); $this->assertSame(null, nth($array, 12)); } -} \ No newline at end of file +} diff --git a/tests/Array/PullAllByTest.php b/tests/Array/PullAllByTest.php index 3714802..6682e3b 100644 --- a/tests/Array/PullAllByTest.php +++ b/tests/Array/PullAllByTest.php @@ -20,4 +20,4 @@ public function testPullAllBy() pullAllBy($array, [[ 'x' => 1 ], [ 'x' => 3 ]], 'x'); $this->assertSame([[ 'x' => 2 ]], $array); } -} \ No newline at end of file +} diff --git a/tests/Array/PullAllTest.php b/tests/Array/PullAllTest.php index cfec891..34aab23 100644 --- a/tests/Array/PullAllTest.php +++ b/tests/Array/PullAllTest.php @@ -20,4 +20,4 @@ public function testPullAll() pullAll($array, ['a', 'c']); $this->assertSame(['b', 'b'], $array); } -} \ No newline at end of file +} diff --git a/tests/Array/PullAllWithTest.php b/tests/Array/PullAllWithTest.php index cbabe04..7a0a4c1 100644 --- a/tests/Array/PullAllWithTest.php +++ b/tests/Array/PullAllWithTest.php @@ -20,4 +20,4 @@ public function testPullAllWith() pullAllWith($array, [['x' => 3, 'y' => 4]], '_\isEqual'); $this->assertSame([['x' => 1, 'y' => 2], ['x' => 5, 'y' => 6]], $array); } -} \ No newline at end of file +} diff --git a/tests/Array/PullAtTest.php b/tests/Array/PullAtTest.php index 4961244..14be4da 100644 --- a/tests/Array/PullAtTest.php +++ b/tests/Array/PullAtTest.php @@ -21,4 +21,4 @@ public function testPullAt() $this->assertSame(['a', 'c'], $array); $this->assertSame(['b', 'd'], $pulled); } -} \ No newline at end of file +} diff --git a/tests/Array/PullTest.php b/tests/Array/PullTest.php index 725f088..c3bda88 100644 --- a/tests/Array/PullTest.php +++ b/tests/Array/PullTest.php @@ -21,4 +21,4 @@ public function testPull() $this->assertSame(['b', 'b'], $array); } -} \ No newline at end of file +} diff --git a/tests/Array/RemoveTest.php b/tests/Array/RemoveTest.php index cfcd2a9..fa60b1a 100644 --- a/tests/Array/RemoveTest.php +++ b/tests/Array/RemoveTest.php @@ -17,8 +17,10 @@ class RemoveTest extends TestCase public function testRemove() { $array = [1, 2, 3, 4]; - $evens = remove($array, function ($n) { return $n % 2 === 0; }); + $evens = remove($array, function ($n) { + return $n % 2 === 0; + }); $this->assertSame([1, 3], $array); $this->assertSame([2, 4], $evens); } -} \ No newline at end of file +} diff --git a/tests/Array/SliceTest.php b/tests/Array/SliceTest.php index 3bc465f..0272dc1 100644 --- a/tests/Array/SliceTest.php +++ b/tests/Array/SliceTest.php @@ -19,4 +19,4 @@ public function testSlice() $array = ['a', 'b', 'c', 'd']; $this->assertSame(['c', 'd'], slice($array, 2)); } -} \ No newline at end of file +} diff --git a/tests/Array/TailTest.php b/tests/Array/TailTest.php new file mode 100644 index 0000000..13da9de --- /dev/null +++ b/tests/Array/TailTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\tail; +use PHPUnit\Framework\TestCase; + +class TailTest extends TestCase +{ + public function testTail() + { + $this->assertSame([2, 3], tail([1, 2, 3])); + } +} diff --git a/tests/Array/TakeRightTest.php b/tests/Array/TakeRightTest.php new file mode 100644 index 0000000..a749129 --- /dev/null +++ b/tests/Array/TakeRightTest.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\takeRight; + +class TakeRightTest extends TestCase +{ + public function testTakeRight() + { + $this->assertSame([3], takeRight([1, 2, 3])); + $this->assertSame([2, 3], takeRight([1, 2, 3], 2)); + $this->assertSame([1, 2, 3], takeRight([1, 2, 3], 5)); + $this->assertSame([], takeRight([1, 2, 3], 0)); + } +} diff --git a/tests/Array/TakeRightWhileTest.php b/tests/Array/TakeRightWhileTest.php new file mode 100644 index 0000000..7aea59e --- /dev/null +++ b/tests/Array/TakeRightWhileTest.php @@ -0,0 +1,29 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\takeRightWhile; + +class TakeRightWhileTest extends TestCase +{ + public function testTakeRightWhile() + { + $users = [ + ['user' => 'barney', 'active' => false], + ['user' => 'fred', 'active' => true], + ['user' => 'pebbles', 'active' => true], + ]; + + $this->assertSame([['user' => 'fred', 'active' => true], ['user' => 'pebbles', 'active' => true]], takeRightWhile($users, function ($value) { + return $value['active']; + })); + } +} diff --git a/tests/Array/TakeTest.php b/tests/Array/TakeTest.php new file mode 100644 index 0000000..8fda463 --- /dev/null +++ b/tests/Array/TakeTest.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\take; + +class TakeTest extends TestCase +{ + public function testTake() + { + $this->assertSame([1], take([1, 2, 3])); + $this->assertSame([1, 2], take([1, 2, 3], 2)); + $this->assertSame([1, 2, 3], take([1, 2, 3], 5)); + $this->assertSame([], take([1, 2, 3], 0)); + } +} diff --git a/tests/Array/TakeWhileTest.php b/tests/Array/TakeWhileTest.php new file mode 100644 index 0000000..2e05a33 --- /dev/null +++ b/tests/Array/TakeWhileTest.php @@ -0,0 +1,29 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\takeWhile; + +class TakeWhileTest extends TestCase +{ + public function testTakeWhile() + { + $users = [ + ['user' => 'barney', 'active' => true], + ['user' => 'fred', 'active' => true], + ['user' => 'pebbles', 'active' => false], + ]; + + $this->assertSame([['user' => 'barney', 'active' => true], ['user' => 'fred', 'active' => true]], takeWhile($users, function ($value) { + return $value['active']; + })); + } +} diff --git a/tests/Array/UnionByTest.php b/tests/Array/UnionByTest.php new file mode 100644 index 0000000..0dff8c9 --- /dev/null +++ b/tests/Array/UnionByTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\unionBy; +use PHPUnit\Framework\TestCase; + +class UnionByTest extends TestCase +{ + public function testUnionBy() + { + $this->assertSame([2.1, 1.2], unionBy([2.1], [1.2, 2.3], 'floor')); + $this->assertSame([['x' => 1], ['x' => 2]], unionBy([['x' => 1]], [['x' => 2], ['x' => 1]], 'x')); + } +} diff --git a/tests/Array/UnionTest.php b/tests/Array/UnionTest.php new file mode 100644 index 0000000..2d0a80b --- /dev/null +++ b/tests/Array/UnionTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\union; +use PHPUnit\Framework\TestCase; + +class UnionTest extends TestCase +{ + public function testUnion() + { + $this->assertSame([2, 1], union([2], [1, 2])); + } +} diff --git a/tests/Array/UnionWithTest.php b/tests/Array/UnionWithTest.php new file mode 100644 index 0000000..a95d2ee --- /dev/null +++ b/tests/Array/UnionWithTest.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\unionWith; + +class UnionWithTest extends TestCase +{ + public function testUnionWith() + { + $objects = [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1]]; + $others = [['x' => 1, 'y' => 1], ['x' => 1, 'y' => 2]]; + + $this->assertSame([['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1], ['x' => 1, 'y' => 1]], unionWith($objects, $others, '_::isEqual')); + } +} diff --git a/tests/Array/UniqByTest.php b/tests/Array/UniqByTest.php new file mode 100644 index 0000000..7ee23b4 --- /dev/null +++ b/tests/Array/UniqByTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\uniqBy; +use PHPUnit\Framework\TestCase; + +class UniqByTest extends TestCase +{ + public function testUniqBy() + { + $this->assertSame([2.1, 1.2], uniqBy([2.1, 1.2, 2.3], 'floor')); + } +} diff --git a/tests/Array/UniqTest.php b/tests/Array/UniqTest.php new file mode 100644 index 0000000..da47eca --- /dev/null +++ b/tests/Array/UniqTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\uniq; +use PHPUnit\Framework\TestCase; + +class UniqTest extends TestCase +{ + public function testUniq() + { + $this->assertSame([2, 1], uniq([2, 1, 2])); + } +} diff --git a/tests/Array/UniqWithTest.php b/tests/Array/UniqWithTest.php new file mode 100644 index 0000000..65b254b --- /dev/null +++ b/tests/Array/UniqWithTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\uniqWith; + +class UniqWithTest extends TestCase +{ + public function testUniqWith() + { + $objects = [['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1], ['x' => 1, 'y' => 2]]; + $this->assertSame([['x' => 1, 'y' => 2], ['x' => 2, 'y' => 1]], uniqWith($objects, '_::isEqual')); + } +} diff --git a/tests/Array/UnzipTest.php b/tests/Array/UnzipTest.php new file mode 100644 index 0000000..d376e53 --- /dev/null +++ b/tests/Array/UnzipTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\unzip; + +class UnzipTest extends TestCase +{ + public function testUnzip() + { + $zipped = [['a', 1, true], ['b', 2, false]]; + $this->assertSame([['a', 'b'], [1, 2], [true, false]], unzip($zipped)); + } +} diff --git a/tests/Array/UnzipWithTest.php b/tests/Array/UnzipWithTest.php new file mode 100644 index 0000000..512f1dc --- /dev/null +++ b/tests/Array/UnzipWithTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\unzipWith; +use PHPUnit\Framework\TestCase; + +class UnzipWithTest extends TestCase +{ + public function testUnzipWith() + { + $zipped = [[1, 10, 100], [2, 20, 200]]; + $this->assertSame([3, 30, 300], unzipWith($zipped, '_::add')); + } +} diff --git a/tests/Array/WithoutTest.php b/tests/Array/WithoutTest.php new file mode 100644 index 0000000..935d2e7 --- /dev/null +++ b/tests/Array/WithoutTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\without; + +class WithoutTest extends TestCase +{ + public function testWithout() + { + $this->assertSame([3], without([2, 1, 2, 3], 1, 2)); + } +} diff --git a/tests/Array/ZipObjectDeepTest.php b/tests/Array/ZipObjectDeepTest.php new file mode 100644 index 0000000..7bc582b --- /dev/null +++ b/tests/Array/ZipObjectDeepTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\zipObjectDeep; + +class ZipObjectDeepTest extends TestCase +{ + public function testZipObjectDeep() + { + $this->assertEquals((object) ['a' => (object) ['b' => [(object) ['c' => 1], (object) ['d' => 2]]]], zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2])); + } +} diff --git a/tests/Array/ZipObjectTest.php b/tests/Array/ZipObjectTest.php new file mode 100644 index 0000000..b4ac506 --- /dev/null +++ b/tests/Array/ZipObjectTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\zipObject; +use PHPUnit\Framework\TestCase; + +class ZipObjectTest extends TestCase +{ + public function testZipObject() + { + $this->assertEquals((object) ['a' => 1, 'b' => 2], zipObject(['a', 'b'], [1, 2])); + } +} diff --git a/tests/Array/ZipTest.php b/tests/Array/ZipTest.php new file mode 100644 index 0000000..18243cd --- /dev/null +++ b/tests/Array/ZipTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\zip; + +class ZipTest extends TestCase +{ + public function testZip() + { + $this->assertSame([['a', 1, true], ['b', 2, false]], zip(['a', 'b'], [1, 2], [true, false])); + } +} diff --git a/tests/Array/ZipWithTest.php b/tests/Array/ZipWithTest.php new file mode 100644 index 0000000..a3a06f3 --- /dev/null +++ b/tests/Array/ZipWithTest.php @@ -0,0 +1,23 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\zipWith; +use PHPUnit\Framework\TestCase; + +class ZipWithTest extends TestCase +{ + public function testZipWith() + { + $this->assertSame([111, 222], zipWith([1, 2], [10, 20], [100, 200], function ($a, $b, $c) { + return $a + $b + $c; + })); + } +} diff --git a/tests/Collection/CountByTest.php b/tests/Collection/CountByTest.php new file mode 100644 index 0000000..0f7d329 --- /dev/null +++ b/tests/Collection/CountByTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\countBy; +use PHPUnit\Framework\TestCase; + +class CountByTest extends TestCase +{ + public function testCountBy() + { + $this->assertSame(['6' => 2, '4' => 1], countBy([6.1, 4.2, 6.3], 'floor')); + $this->assertSame(['3' => 2, '5' => 1], countBy(['one', 'two', 'three'], 'strlen')); + } +} diff --git a/tests/Collection/EachRightTest.php b/tests/Collection/EachRightTest.php new file mode 100644 index 0000000..ee68693 --- /dev/null +++ b/tests/Collection/EachRightTest.php @@ -0,0 +1,31 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\eachRight; +use PHPUnit\Framework\TestCase; + +class EachRightTest extends TestCase +{ + public function testForEachRight() + { + $test = []; + eachRight([1, 2], function ($value) use (&$test) { + $test[$value] = true; + }); + $this->assertSame([2 => true, 1 => true], $test); + + $test = []; + eachRight((object) ['a' => 1, 'b' => 2], function ($value) use (&$test) { + $test[$value] = true; + }); + $this->assertSame([2 => true, 1 => true], $test); + } +} diff --git a/tests/Collection/EachTest.php b/tests/Collection/EachTest.php index 22507cd..bcd1340 100644 --- a/tests/Collection/EachTest.php +++ b/tests/Collection/EachTest.php @@ -19,11 +19,15 @@ public function testEach() $testFunc = new \stdClass(); $testFunc->total = 0; - each([1, 2], function ($value) use ($testFunc) { $testFunc->total += $value; }); + each([1, 2], function ($value) use ($testFunc) { + $testFunc->total += $value; + }); $this->assertSame(3, $testFunc->total); $testFunc->total = 0; - each((object) ['a' => 1, 'b' => 2], function ($value) use ($testFunc) { $testFunc->total += $value; }); + each((object) ['a' => 1, 'b' => 2], function ($value) use ($testFunc) { + $testFunc->total += $value; + }); $this->assertSame(3, $testFunc->total); } -} \ No newline at end of file +} diff --git a/tests/Collection/EveryTest.php b/tests/Collection/EveryTest.php new file mode 100644 index 0000000..bb118eb --- /dev/null +++ b/tests/Collection/EveryTest.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\every; + +class EveryTest extends TestCase +{ + public function testEvery() + { + $this->assertFalse(every([true, 1, null, 'yes'], function ($value) { + return is_bool($value); + })); + + $users = [ + ['user' => 'barney', 'age' => 36, 'active' => false], + ['user' => 'fred', 'age' => 40, 'active' => false], + ]; + + // The `matches` iteratee shorthand. + $this->assertFalse(every($users, ['user' => 'barney', 'active' => false])); + + // The `matchesProperty` iteratee shorthand. + $this->assertTrue(every($users, ['active', false])); + + // The `property` iteratee shorthand. + $this->assertFalse(every($users, 'active')); + } +} diff --git a/tests/Collection/FilterTest.php b/tests/Collection/FilterTest.php new file mode 100644 index 0000000..0adcc8c --- /dev/null +++ b/tests/Collection/FilterTest.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\filter; + +class FilterTest extends TestCase +{ + public function testFilter() + { + $users = [ + ['user' => 'barney', 'age' => 36, 'active' => true], + ['user' => 'fred', 'age' => 40, 'active' => false], + ]; + + $this->assertSame([['user' => 'fred', 'age' => 40, 'active' => false]], filter($users, function ($o) { + return !$o['active']; + })); + + // The `matches` iteratee shorthand. + $this->assertSame([['user' => 'barney', 'age' => 36, 'active' => true]], filter($users, ['age' => 36, 'active' => true])); + + // The `matchesProperty` iteratee shorthand. + $this->assertSame([['user' => 'fred', 'age' => 40, 'active' => false]], filter($users, ['active', false])); + + // The `property` iteratee shorthand. + $this->assertSame([['user' => 'barney', 'age' => 36, 'active' => true]], filter($users, 'active')); + } +} diff --git a/tests/Collection/FindLastTest.php b/tests/Collection/FindLastTest.php new file mode 100644 index 0000000..f018a06 --- /dev/null +++ b/tests/Collection/FindLastTest.php @@ -0,0 +1,23 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\findLast; + +class FindLastTest extends TestCase +{ + public function testFindLast() + { + $this->assertSame(3, findLast([1, 2, 3, 4], function ($n) { + return $n % 2 == 1; + })); + } +} diff --git a/tests/Collection/FindTest.php b/tests/Collection/FindTest.php new file mode 100644 index 0000000..7d28e6d --- /dev/null +++ b/tests/Collection/FindTest.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\find; + +class FindTest extends TestCase +{ + public function testFind() + { + $users = [ + ['user' => 'barney', 'age' => 36, 'active' => true], + ['user' => 'fred', 'age' => 40, 'active' => false], + ['user' => 'pebbles', 'age' => 1, 'active' => true], + ]; + + $this->assertSame(['user' => 'barney', 'age' => 36, 'active' => true], find($users, function ($o) { + return $o['age'] < 40; + })); + + // The `matches` iteratee shorthand. + $this->assertSame(['user' => 'pebbles', 'age' => 1, 'active' => true], find($users, ['age' => 1, 'active' => true])); + + // The `matchesProperty` iteratee shorthand. + $this->assertSame(['user' => 'fred', 'age' => 40, 'active' => false], find($users, ['active', false])); + + // The `property` iteratee shorthand. + $this->assertSame(['user' => 'barney', 'age' => 36, 'active' => true], find($users, 'active')); + } +} diff --git a/tests/Collection/FlatMapDeepTest.php b/tests/Collection/FlatMapDeepTest.php new file mode 100644 index 0000000..5a99382 --- /dev/null +++ b/tests/Collection/FlatMapDeepTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\flatMapDeep; + +class FlatMapDeepTest extends TestCase +{ + public function testFlatMapDeep() + { + $duplicate = function ($n) { + return [[[$n, $n]]]; + }; + + $this->assertSame([1, 1, 2, 2], flatMapDeep([1, 2], $duplicate)); + } +} diff --git a/tests/Collection/FlatMapDepthTest.php b/tests/Collection/FlatMapDepthTest.php new file mode 100644 index 0000000..0cfea01 --- /dev/null +++ b/tests/Collection/FlatMapDepthTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\flatMapDepth; +use PHPUnit\Framework\TestCase; + +class FlatMapDepthTest extends TestCase +{ + public function testFlatMapDepth() + { + $duplicate = function ($n) { + return [[[$n, $n]]]; + }; + + $this->assertSame([[1, 1], [2, 2]], flatMapDepth([1, 2], $duplicate, 2)); + } +} diff --git a/tests/Collection/FlatMapTest.php b/tests/Collection/FlatMapTest.php new file mode 100644 index 0000000..17bbcd6 --- /dev/null +++ b/tests/Collection/FlatMapTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\flatMap; + +class FlatMapTest extends TestCase +{ + public function testFlatMap() + { + $duplicate = function ($n) { + return [$n, $n]; + }; + + $this->assertSame([1, 1, 2, 2], flatMap([1, 2], $duplicate)); + } +} diff --git a/tests/Collection/GroupByTest.php b/tests/Collection/GroupByTest.php new file mode 100644 index 0000000..78cf4e7 --- /dev/null +++ b/tests/Collection/GroupByTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\groupBy; + +class GroupByTest extends TestCase +{ + public function testCountBy() + { + $this->assertSame(['6' => [6.1, 6.3], '4' => [4.2]], groupBy([6.1, 4.2, 6.3], 'floor')); + $this->assertSame(['3' => ['one', 'two'], '5' => ['three']], groupBy(['one', 'two', 'three'], 'strlen')); + } +} diff --git a/tests/Collection/InvokeMapTest.php b/tests/Collection/InvokeMapTest.php new file mode 100644 index 0000000..d1e4e11 --- /dev/null +++ b/tests/Collection/InvokeMapTest.php @@ -0,0 +1,42 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\invokeMap; +use PHPUnit\Framework\TestCase; + +class InvokeMapTest extends TestCase +{ + public function testInvokeMap() + { + $this->assertSame([[1, 5, 7], [1, 2, 3]], invokeMap([[5, 1, 7], [3, 2, 1]], function ($result) { + sort($result); + return $result; + })); + $this->assertSame([['1', '2', '3'], ['4', '5', '6']], invokeMap([123, 456], 'str_split')); + + $users = [ + new class() { + public function getCount() + { + return 12; + } + }, + new class() { + public function getCount() + { + return 24; + } + } + ]; + + $this->assertEquals([12, 24], invokeMap($users, 'getCount')); + } +} diff --git a/tests/Collection/KeyByTest.php b/tests/Collection/KeyByTest.php new file mode 100644 index 0000000..3ca8fc8 --- /dev/null +++ b/tests/Collection/KeyByTest.php @@ -0,0 +1,29 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\keyBy; + +class KeyByTest extends TestCase +{ + public function testKeyBy() + { + $array = [ + ['direction' => 'left', 'code' => 97], + ['direction' => 'right', 'code' => 100], + ]; + + $this->assertSame(['a' => ['direction' => 'left', 'code' => 97], 'd' => ['direction' => 'right', 'code' => 100]], keyBy($array, function ($o) { + return \chr($o['code']); + })); + $this->assertSame(['left' => ['direction' => 'left', 'code' => 97], 'right' => ['direction' => 'right', 'code' => 100]], keyBy($array, 'direction')); + } +} diff --git a/tests/Collection/MapTest.php b/tests/Collection/MapTest.php index 0ac9fc9..c17a7ac 100644 --- a/tests/Collection/MapTest.php +++ b/tests/Collection/MapTest.php @@ -31,4 +31,4 @@ public function testChunk() $this->assertSame(['barney', 'fred'], map($users, 'user')); $this->assertSame(['barney', 'fred'], map(new \ArrayIterator($users), 'user')); } -} \ No newline at end of file +} diff --git a/tests/Collection/OrderByTest.php b/tests/Collection/OrderByTest.php new file mode 100644 index 0000000..1164c0d --- /dev/null +++ b/tests/Collection/OrderByTest.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\orderBy; + +class OrderByTest extends TestCase +{ + public function testOrderBy() + { + $users = [ + ['user' => 'fred', 'age' => 48], + ['user' => 'barney', 'age' => 34], + ['user' => 'fred', 'age' => 40], + ['user' => 'barney', 'age' => 36], + ]; + + $this->assertSame([['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]], orderBy($users, ['user', 'age'], ['asc', 'desc'])); + } +} diff --git a/tests/Collection/PartitionTest.php b/tests/Collection/PartitionTest.php new file mode 100644 index 0000000..7c228ee --- /dev/null +++ b/tests/Collection/PartitionTest.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\partition; + +class PartitionTest extends TestCase +{ + public function testPartition() + { + $users = [ + ['user' => 'barney', 'age' => 36, 'active' => false], + ['user' => 'fred', 'age' => 40, 'active' => true], + ['user' => 'pebbles', 'age' => 1, 'active' => false], + ]; + + $result = [ + [ + ['user' => 'fred', 'age' => 40, 'active' => true], + ], + [ + ['user' => 'barney', 'age' => 36, 'active' => false], + ['user' => 'pebbles', 'age' => 1, 'active' => false], + ], + ]; + + $this->assertSame($result, partition($users, function ($user) { + return $user['active']; + })); + } +} diff --git a/tests/Collection/ReduceRightTest.php b/tests/Collection/ReduceRightTest.php new file mode 100644 index 0000000..905fafd --- /dev/null +++ b/tests/Collection/ReduceRightTest.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\reduceRight; + +class ReduceRightTest extends TestCase +{ + public function testReduceRight() + { + $array = [[0, 1], [2, 3], [4, 5]]; + $this->assertSame([4, 5, 2, 3, 0, 1], reduceRight($array, function ($flattened, $other) { + return \array_merge($flattened, $other); + }, [])); + } +} diff --git a/tests/Collection/ReduceTest.php b/tests/Collection/ReduceTest.php new file mode 100644 index 0000000..75e38e9 --- /dev/null +++ b/tests/Collection/ReduceTest.php @@ -0,0 +1,31 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\reduce; + +class ReduceTest extends TestCase +{ + public function testReduce() + { + $this->assertSame(3, reduce([1, 2], function ($sum, $n) { + return $sum + $n; + }, 0)); + $this->assertSame(['1' => ['a', 'c'], '2' => ['b']], reduce(['a' => 1, 'b' => 2, 'c' => 1], function ($result, $value, $key) { + if (!isset($result[$value])) { + $result[$value] = []; + } + $result[$value][] = $key; + + return $result; + }, [])); + } +} diff --git a/tests/Collection/RejectTest.php b/tests/Collection/RejectTest.php new file mode 100644 index 0000000..bbfe2e2 --- /dev/null +++ b/tests/Collection/RejectTest.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\reject; + +class RejectTest extends TestCase +{ + public function testReject() + { + $users = [ + ['user' => 'barney', 'active' => true], + ['user' => 'fred', 'active' => false], + ]; + + $this->assertSame([['user' => 'fred', 'active' => false]], reject($users, 'active')); + } +} diff --git a/tests/Collection/SampleSizeTest.php b/tests/Collection/SampleSizeTest.php new file mode 100644 index 0000000..b00bc98 --- /dev/null +++ b/tests/Collection/SampleSizeTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\sampleSize; + +class SampleSizeTest extends TestCase +{ + public function testSampleSize() + { + $this->assertCount(2, sampleSize([1, 2, 3], 2)); + $this->assertSame([], \array_diff(sampleSize([1, 2, 3], 2), [1, 2, 3])); + + $this->assertCount(3, sampleSize([1, 2, 3], 4)); + $this->assertSame([1, 2, 3], sampleSize([1, 2, 3], 4)); + } +} diff --git a/tests/Collection/SampleTest.php b/tests/Collection/SampleTest.php new file mode 100644 index 0000000..3730214 --- /dev/null +++ b/tests/Collection/SampleTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\sample; +use PHPUnit\Framework\TestCase; + +class SampleTest extends TestCase +{ + public function testSample() + { + $this->assertContains(sample([1, 2, 3, 4]), [1, 2, 3, 4]); + } +} diff --git a/tests/Collection/ShuffleTest.php b/tests/Collection/ShuffleTest.php new file mode 100644 index 0000000..76e48c7 --- /dev/null +++ b/tests/Collection/ShuffleTest.php @@ -0,0 +1,23 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\shuffle; +use PHPUnit\Framework\TestCase; + +class ShuffleTest extends TestCase +{ + public function testShuffle() + { + $arr = range(1, 10); + $this->assertNotSame($arr, shuffle($arr)); + $this->assertSame([], \array_diff($arr, shuffle($arr))); + } +} diff --git a/tests/Collection/SizeTest.php b/tests/Collection/SizeTest.php new file mode 100644 index 0000000..b750e29 --- /dev/null +++ b/tests/Collection/SizeTest.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\size; + +class SizeTest extends TestCase +{ + public function testSize() + { + $this->assertSame(3, size([1, 2, 3])); + $this->assertSame(2, size(new class { + public $a = 1; + + public $b = 2; + + private $c = 3; + })); + + $this->assertSame(12, size(new class implements \Countable { + #[\ReturnTypeWillChange] + public function count() + { + return 12; + } + })); + + $this->assertSame(4, size(new \ArrayIterator([1, 2, 3, 4]))); + + $this->assertSame(7, size('pebbles')); + } +} diff --git a/tests/Collection/SomeTest.php b/tests/Collection/SomeTest.php new file mode 100644 index 0000000..b8c0b99 --- /dev/null +++ b/tests/Collection/SomeTest.php @@ -0,0 +1,31 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\some; + +class SomeTest extends TestCase +{ + public function testSome() + { + $users = [ + ['user' => 'barney', 'active' => true], + ['user' => 'fred', 'active' => false], + ]; + + $this->assertTrue(some([null, 0, 'yes', false], function ($value) { + return \is_bool($value); + })); + $this->assertFalse(some($users, ['user' => 'barney', 'active' => false])); + $this->assertTrue(some($users, ['active', false])); + $this->assertTrue(some($users, 'active')); + } +} diff --git a/tests/Collection/SortByTest.php b/tests/Collection/SortByTest.php index f926edf..f1bb3c8 100644 --- a/tests/Collection/SortByTest.php +++ b/tests/Collection/SortByTest.php @@ -23,8 +23,10 @@ public function testSortBy() ['user' => 'barney', 'age' => 34], ]; - $this->assertSame([['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]], sortBy($users, function ($o) { return $o['user']; })); + $this->assertSame([['user' => 'barney', 'age' => 36], ['user' => 'barney', 'age' => 34], ['user' => 'fred', 'age' => 48], ['user' => 'fred', 'age' => 40]], sortBy($users, function ($o) { + return $o['user']; + })); $this->assertSame([['user' => 'barney', 'age' => 34], ['user' => 'barney', 'age' => 36], ['user' => 'fred', 'age' => 40], ['user' => 'fred', 'age' => 48]], sortBy($users, ['user', 'age'])); $this->assertSame([], sortBy(null, [])); } -} \ No newline at end of file +} diff --git a/tests/Date/NowTest.php b/tests/Date/NowTest.php index 578d1ec..3307998 100644 --- a/tests/Date/NowTest.php +++ b/tests/Date/NowTest.php @@ -19,4 +19,4 @@ public function testWords() // @TODO: This test is very volatile. Get a better way of testing a timestamp $this->assertSame((int) (\microtime(true) * 1000), now()); } -} \ No newline at end of file +} diff --git a/tests/Function/AfterTest.php b/tests/Function/AfterTest.php new file mode 100644 index 0000000..ed0c6bc --- /dev/null +++ b/tests/Function/AfterTest.php @@ -0,0 +1,50 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\after; +use PHPUnit\Framework\TestCase; + +class AfterTest extends TestCase +{ + public function testAfter() + { + $counter = 0; + + $after = after(12, function () use (&$counter) { + $counter++; + + return $counter; + }); + + for ($i = 0; $i < 12; $i++) { + $after(); + } + + $this->assertSame(1, $counter); + } + + public function testAfterNotCalled() + { + $counter = 0; + + $after = after(12, function () use (&$counter) { + $counter++; + + return $counter; + }); + + for ($i = 0; $i < 10; $i++) { + $after(); + } + + $this->assertSame(0, $counter); + } +} diff --git a/tests/Function/AryTest.php b/tests/Function/AryTest.php new file mode 100644 index 0000000..ef9ca60 --- /dev/null +++ b/tests/Function/AryTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\ary; +use function _\map; +use PHPUnit\Framework\TestCase; + +class AryTest extends TestCase +{ + public function testAry() + { + $this->assertSame([6, 8, 10], map(['6', '8', '10'], ary('intval', 1))); + } +} diff --git a/tests/Function/BeforeTest.php b/tests/Function/BeforeTest.php new file mode 100644 index 0000000..f43a968 --- /dev/null +++ b/tests/Function/BeforeTest.php @@ -0,0 +1,47 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\before; +use function _\map; +use function _\uniqBy; +use PHPUnit\Framework\TestCase; + +class BeforeTest extends TestCase +{ + public function testBefore() + { + $counter = 0; + $func = before(5, function () use (&$counter) { + $counter++; + + return $counter; + }); + + for ($i = 0; $i < 20; $i++) { + $func(); + } + + $this->assertSame(4, $counter); + $this->assertSame(4, $func()); + } + + public function testBeforeMap() + { + $users = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + $result = uniqBy(map($users, before(5, function (int $id) { + return [ + 'id' => $id + ]; + })), 'id'); + + $this->assertSame([['id' => 1],['id' => 2],['id' => 3],['id' => 4]], $result); + } +} diff --git a/tests/Function/BindKeyTest.php b/tests/Function/BindKeyTest.php new file mode 100644 index 0000000..7cfcc50 --- /dev/null +++ b/tests/Function/BindKeyTest.php @@ -0,0 +1,32 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\bindKey; + +class BindKeyTest extends TestCase +{ + public function testBindKey() + { + $object = new class { + private $user = 'fred'; + + public function greet($greeting, $punctuation) + { + return $greeting.' '.$this->user.$punctuation; + } + }; + + $bound = bindKey($object, 'greet', 'hi'); + + $this->assertSame('hi fred!', $bound('!')); + } +} diff --git a/tests/Function/BindTest.php b/tests/Function/BindTest.php new file mode 100644 index 0000000..a99a4f9 --- /dev/null +++ b/tests/Function/BindTest.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\bind; + +class BindTest extends TestCase +{ + public function testBind() + { + $object = new class { + public $user = 'fred'; + + private $age = 54; + }; + + $greet = function ($greeting, $punctuation) { + return $greeting.' '.$this->user.$punctuation; + }; + + $bound = bind($greet, $object, 'hi'); + + $this->assertSame('hi fred!', $bound('!')); + + $bound = bind(function ($prefix, $suffix) { + return $prefix.' '.$this->age.' '.$suffix; + }, $object, 'I\'m'); + + $this->assertSame('I\'m 54 years', $bound('years')); + } +} diff --git a/tests/Function/CurryTest.php b/tests/Function/CurryTest.php new file mode 100644 index 0000000..a267362 --- /dev/null +++ b/tests/Function/CurryTest.php @@ -0,0 +1,31 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\curry; + +class CurryTest extends TestCase +{ + public function testCurry() + { + $abc = function ($a, $b, $c = null) { + return [$a, $b, $c]; + }; + + $curried = curry($abc); + + $this->assertSame([1, 2, 3], $curried(1)(2)(3)); + $this->assertSame([1, 2, 3], $curried(1, 2)(3)); + $this->assertSame([1, 2, 3], $curried(1, 2, 3)); + $this->assertSame([1, 2, null], curry($abc, 2)(1)(2)); + $this->assertSame([1, 2, 3], $curried(1)(_, 3)(2)); + } +} diff --git a/tests/Function/DelayTest.php b/tests/Function/DelayTest.php new file mode 100644 index 0000000..65e1465 --- /dev/null +++ b/tests/Function/DelayTest.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\delay; +use PHPUnit\Framework\TestCase; + +class DelayTest extends TestCase +{ + public function testDelay() + { + $a = 1; + $time = microtime(true); + delay(function ($increment) use (&$a) { + $a += $increment; + }, 20, 2); + + $this->assertSame(3, $a); + $this->assertTrue(((microtime(true) - $time) * 1000) > 20); + } +} diff --git a/tests/Function/FlipTest.php b/tests/Function/FlipTest.php new file mode 100644 index 0000000..2e7ef83 --- /dev/null +++ b/tests/Function/FlipTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\flip; + +class FlipTest extends TestCase +{ + public function testFlip() + { + $flipped = flip(function () { + return func_get_args(); + }); + + $this->assertSame(['d', 'c', 'b', 'a'], $flipped('a', 'b', 'c', 'd')); + } +} diff --git a/tests/Function/MemoizeTest.php b/tests/Function/MemoizeTest.php new file mode 100644 index 0000000..26a8938 --- /dev/null +++ b/tests/Function/MemoizeTest.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\memoize; + +class MemoizeTest extends TestCase +{ + public function testMemoize() + { + $object = (object) ['a' => 1, 'b' => 2]; + $other = (object) ['c' => 3, 'd' => 4]; + + $values = memoize('get_object_vars'); + $this->assertSame(['a' => 1, 'b' => 2], $values($object)); + $this->assertSame(['c' => 3, 'd' => 4], $values($other)); + + $object->a = 2; + + $this->assertSame(['a' => 1, 'b' => 2], $values($object)); + + // Modify the result cache. + $values->cache->set($object, ['a', 'b']); + $this->assertSame(['a', 'b'], $values($object)); + } +} diff --git a/tests/Function/NegateTest.php b/tests/Function/NegateTest.php new file mode 100644 index 0000000..2e03b0f --- /dev/null +++ b/tests/Function/NegateTest.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\filter; +use function _\negate; + +class NegateTest extends TestCase +{ + public function testNegate() + { + $isEven = function ($n) { + return $n % 2 == 0; + }; + + $this->assertSame([1, 3, 5], filter([1, 2, 3, 4, 5, 6], negate($isEven))); + } +} diff --git a/tests/Function/OnceTest.php b/tests/Function/OnceTest.php new file mode 100644 index 0000000..96e057e --- /dev/null +++ b/tests/Function/OnceTest.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\once; + +class OnceTest extends TestCase +{ + public function testOnce() + { + $counter = 0; + + $func = once(function () use (&$counter) { + $counter++; + }); + + $func(); + $func(); + + $this->assertSame(1, $counter); + } +} diff --git a/tests/Function/OverArgsTest.php b/tests/Function/OverArgsTest.php new file mode 100644 index 0000000..197fea6 --- /dev/null +++ b/tests/Function/OverArgsTest.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\overArgs; + +class OverArgsTest extends TestCase +{ + public function testOverArgs() + { + function doubled($n) + { + return $n * 2; + } + + function square($n) + { + return $n * $n; + } + + $func = overArgs(function ($x, $y) { + return [$x, $y]; + }, ['square', 'doubled']); + + $this->assertSame([81, 6], $func(9, 3)); + + $this->assertSame([100, 10], $func(10, 5)); + } +} diff --git a/tests/Function/PartialTest.php b/tests/Function/PartialTest.php new file mode 100644 index 0000000..2a2c8c1 --- /dev/null +++ b/tests/Function/PartialTest.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\partial; + +class PartialTest extends TestCase +{ + public function testPartial() + { + $greet = function ($greeting, $name) { + return $greeting.' '.$name; + }; + + $sayHelloTo = partial($greet, 'hello'); + $this->assertSame('hello fred', $sayHelloTo('fred')); + } +} diff --git a/tests/Function/RestTest.php b/tests/Function/RestTest.php new file mode 100644 index 0000000..58ad8d0 --- /dev/null +++ b/tests/Function/RestTest.php @@ -0,0 +1,29 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\initial; +use function _\last; +use function _\rest; +use function _\size; + +class RestTest extends TestCase +{ + public function testRest() + { + $say = rest(function ($what, $names) { + return $what.' '.implode(', ', initial($names)). + (size($names) > 1 ? ', & ' : '').last($names); + }); + + $this->assertSame('hello fred, barney, & pebbles', $say('hello', 'fred', 'barney', 'pebbles')); + } +} diff --git a/tests/Function/SpreadTest.php b/tests/Function/SpreadTest.php new file mode 100644 index 0000000..b7b6cdb --- /dev/null +++ b/tests/Function/SpreadTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\spread; + +class SpreadTest extends TestCase +{ + public function testSpread() + { + $say = spread(function ($who, $what) { + return $who.' says '.$what; + }); + + $this->assertSame('fred says hello', $say(['fred', 'hello'])); + } +} diff --git a/tests/Function/UnaryTest.php b/tests/Function/UnaryTest.php new file mode 100644 index 0000000..b68c439 --- /dev/null +++ b/tests/Function/UnaryTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\map; +use function _\unary; +use PHPUnit\Framework\TestCase; + +class UnaryTest extends TestCase +{ + public function testUnary() + { + $this->assertSame([6, 8, 10], map(['6', '8', '10'], unary('intval'))); + } +} diff --git a/tests/Function/WrapTest.php b/tests/Function/WrapTest.php new file mode 100644 index 0000000..008485f --- /dev/null +++ b/tests/Function/WrapTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\wrap; +use PHPUnit\Framework\TestCase; + +class WrapTest extends TestCase +{ + public function testWrap() + { + $p = wrap('_\escape', function ($func, $text) { + return '

' . $func($text) . '

'; + }); + + $this->assertSame('

fred, barney, & pebbles

', $p('fred, barney, & pebbles')); + } +} diff --git a/tests/Lang/EqTest.php b/tests/Lang/EqTest.php new file mode 100644 index 0000000..01083d6 --- /dev/null +++ b/tests/Lang/EqTest.php @@ -0,0 +1,93 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\eq; + +class EqTest extends TestCase +{ + /** + * @dataProvider equalValues + */ + public function testIsEqual($value, $other) + { + $this->assertTrue(eq($value, $other)); + } + + /** + * @dataProvider notEqualValues + */ + public function testIsNotEqual($value, $other) + { + $this->assertFalse(eq($value, $other)); + } + + public function notEqualValues() + { + $ob1 = new StdClass(); + $ob1->a = 'b'; + $ob2 = new StdClass(); + + $ob3 = new StdClass(); + $ob4 = new StdClass(); + + return [ + [ + null, 'a', + ], + [ + 0, 1, + ], + [ + [1], [], + ], + [ + ['a' => 21], ['a' => 22], + ], + [ + ['a' => 21], ['a' => '21'], + ], + [ + $ob1, $ob2, + ], + + [ + $ob3, $ob4, + ], + [ + new DateTime('NOW'), new DateTime('NOW'), + ], + ]; + } + + public function equalValues() + { + $date = new DateTime('NOW'); + + return [ + [ + 'a', 'a', + ], + [ + 1, 1, + ], + [ + [], [], + ], + [ + $date, $date, + ], + [ + INF, INF + ] + ]; + } +} diff --git a/tests/Lang/IsEqualTest.php b/tests/Lang/IsEqualTest.php index 8ce630c..f054cfa 100644 --- a/tests/Lang/IsEqualTest.php +++ b/tests/Lang/IsEqualTest.php @@ -86,4 +86,4 @@ public function equalValues() ], ]; } -} \ No newline at end of file +} diff --git a/tests/Lang/IsErrorTest.php b/tests/Lang/IsErrorTest.php index be2af2a..4d09881 100644 --- a/tests/Lang/IsErrorTest.php +++ b/tests/Lang/IsErrorTest.php @@ -35,4 +35,4 @@ public function testIsError() $this->assertFalse(isError(new \stdClass())); $this->assertFalse(isError(\Exception::class)); } -} \ No newline at end of file +} diff --git a/tests/LodashTest.php b/tests/LodashTest.php new file mode 100644 index 0000000..fae6947 --- /dev/null +++ b/tests/LodashTest.php @@ -0,0 +1,25 @@ + 'barney', 'age' => 36], + ['user' => 'fred', 'age' => 40], + ['user' => 'pebbles', 'age' => 1], + ]; + + $youngest = __($users) + ->sortBy('age') + ->map(function ($o) { + return $o['user'] . ' is ' . $o['age']; + }) + ->head() + ->value(); + + $this->assertSame('pebbles is 1', $youngest); + } +} diff --git a/tests/Math/AddTest.php b/tests/Math/AddTest.php new file mode 100644 index 0000000..3c3e130 --- /dev/null +++ b/tests/Math/AddTest.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\add; + +class AddTest extends TestCase +{ + public function testAdd() + { + $this->assertSame(10, add(6, 4)); + } +} diff --git a/tests/Math/MaxByTest.php b/tests/Math/MaxByTest.php new file mode 100644 index 0000000..77deba1 --- /dev/null +++ b/tests/Math/MaxByTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\maxBy; + +class MaxByTest extends TestCase +{ + public function testMax() + { + $objects = [['n' => 1], ['n' => 2]]; + $this->assertSame(['n' => 2], maxBy($objects, function ($o) { + return $o['n']; + })); + $this->assertSame(['n' => 2], maxBy($objects, 'n')); + } +} diff --git a/tests/Math/MaxTest.php b/tests/Math/MaxTest.php new file mode 100644 index 0000000..ed8ddb0 --- /dev/null +++ b/tests/Math/MaxTest.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\max; + +class MaxTest extends TestCase +{ + public function testMax() + { + $this->assertSame(8, max([4, 2, 8, 6])); + $this->assertSame(null, max([])); + } +} diff --git a/tests/Number/ClampTest.php b/tests/Number/ClampTest.php index 09e5a1a..a116c7b 100644 --- a/tests/Number/ClampTest.php +++ b/tests/Number/ClampTest.php @@ -19,4 +19,4 @@ public function testClamp() $this->assertSame(-5, clamp(-10, -5, 5)); $this->assertSame(5, clamp(10, -5, 5)); } -} \ No newline at end of file +} diff --git a/tests/Number/InRangeTest.php b/tests/Number/InRangeTest.php index 3b73841..45fe4b3 100644 --- a/tests/Number/InRangeTest.php +++ b/tests/Number/InRangeTest.php @@ -28,4 +28,4 @@ public function testInRange() $this->assertTrue(inRange(-3, -2, -6)); $this->assertTrue(inRange(4, 12)); } -} \ No newline at end of file +} diff --git a/tests/Number/RandomTest.php b/tests/Number/RandomTest.php index 8e5e443..6ad6d00 100644 --- a/tests/Number/RandomTest.php +++ b/tests/Number/RandomTest.php @@ -25,4 +25,4 @@ public function testRandom() $this->assertTrue(is_float(random(true))); $this->assertTrue(inRange(random(10, 5), 5, 11)); } -} \ No newline at end of file +} diff --git a/tests/Object/GetTest.php b/tests/Object/GetTest.php new file mode 100644 index 0000000..4fb02bb --- /dev/null +++ b/tests/Object/GetTest.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2019 + */ + +use function _\get; +use PHPUnit\Framework\TestCase; + +class GetTest extends TestCase +{ + public function testGetArray() + { + $actualValue1 = "data"; + $sampleArray = ["key1" => ["key2" => ["key3" => $actualValue1, "key4" => ""]]]; + $defaultValue = "default"; + $this->assertSame($actualValue1, get($sampleArray, "key1.key2.key3", "default"), "Default To method 1 failed"); + $this->assertSame($defaultValue, get($sampleArray, "key2.key2.key3", $defaultValue), "Default To method 2 failed"); + $this->assertSame($actualValue1, get($sampleArray, ["key1","key2","key3"], $defaultValue), "Default To method 3 failed"); + $this->assertSame($defaultValue, get($sampleArray, "key1.key2.key3.key4", $defaultValue), "Default To method 4 failed"); + $this->assertSame($defaultValue, get($sampleArray, "key1.key2.key3.key4", $defaultValue), "Default To method 5 failed"); + $this->assertSame($defaultValue, get($sampleArray, ["key1","key2","key3","key4"], $defaultValue), "Default To method 6 failed"); + $this->assertSame("", get($sampleArray, "key1.key2.key4", $defaultValue), "Default To method 8 failed"); + + $this->assertSame($sampleArray["key1"]["key2"], _::get($sampleArray, "key1.key2", $defaultValue), "Default To method 9 failed"); + $this->assertSame($defaultValue, _::get($sampleArray, "key1.key3", $defaultValue), "Default To method 10 failed"); + } + + public function testDefaultToObject() + { + $actualValue1 = "data"; + $sampleArray = (object)["key1" => (object)["key2" => (object)["key3" => $actualValue1, "key4" => ""]]]; + $defaultValue = "default"; + $this->assertSame($actualValue1, get($sampleArray, "key1.key2.key3", $defaultValue), "Default To method object 1 failed"); + $this->assertSame($defaultValue, get($sampleArray, "key2.key2.key3", $defaultValue), "Default To method object 2 failed"); + $this->assertSame($actualValue1, get($sampleArray, ["key1","key2","key3"], $defaultValue), "Default To method object 3 failed"); + $this->assertSame($defaultValue, get($sampleArray, "key1.key2.key3.key4", $defaultValue), "Default To method object 4 failed"); + $this->assertSame($defaultValue, get($sampleArray, "key1.key2.key3.key4", $defaultValue), "Default To method object 5 failed"); + $this->assertSame($defaultValue, get($sampleArray, ["key1","key2","key3","key4"], $defaultValue), "Default To method object 6 failed"); + $this->assertSame("", get($sampleArray, "key1.key2.key4", $defaultValue), "Default To method object 8 failed"); + + $this->assertSame($sampleArray->key1->key2, _::get($sampleArray, "key1.key2", $defaultValue), "Default To method object 9 failed"); + $this->assertSame($defaultValue, _::get($sampleArray, "key1.key3", $defaultValue), "Default To method 10 failed"); + } +} diff --git a/tests/Object/PickByTest.php b/tests/Object/PickByTest.php new file mode 100644 index 0000000..5683f57 --- /dev/null +++ b/tests/Object/PickByTest.php @@ -0,0 +1,25 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\pickBy; + +class PickByTest extends TestCase +{ + public function testPick() + { + $object = (object) ['a' => 1, 'b' => '2', 'c' => 3]; + + $this->assertEquals((object) ['a' => 1, 'c' => 3], pickBy($object, function ($value) { + return \is_int($value); + })); + } +} diff --git a/tests/Object/PickTest.php b/tests/Object/PickTest.php new file mode 100644 index 0000000..3b1f9f4 --- /dev/null +++ b/tests/Object/PickTest.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2017 + */ + +use PHPUnit\Framework\TestCase; +use function _\pick; + +class PickTest extends TestCase +{ + public function testPick() + { + $object = (object) ['a' => 1, 'b' => '2', 'c' => 3]; + $this->assertEquals((object) ['a' => 1, 'c' => 3], pick($object, ['a', 'c'])); + + $this->assertEquals((object) ['a' => 1], pick($object, 'a')); + } +} diff --git a/tests/Seq/ChainTest.php b/tests/Seq/ChainTest.php new file mode 100644 index 0000000..f647cff --- /dev/null +++ b/tests/Seq/ChainTest.php @@ -0,0 +1,35 @@ + + * @copyright Copyright (c) 2017 + */ + +use function _\chain; +use PHPUnit\Framework\TestCase; + +class ChainTest extends TestCase +{ + public function testChain() + { + $users = [ + ['user' => 'barney', 'age' => 36], + ['user' => 'fred', 'age' => 40], + ['user' => 'pebbles', 'age' => 1], + ]; + + $youngest = chain($users) + ->sortBy('age') + ->map(function ($o) { + return $o['user'] . ' is ' . $o['age']; + }) + ->head() + ->value(); + + $this->assertSame('pebbles is 1', $youngest); + } +} diff --git a/tests/String/CamelCaseTest.php b/tests/String/CamelCaseTest.php index d387682..cedc95b 100644 --- a/tests/String/CamelCaseTest.php +++ b/tests/String/CamelCaseTest.php @@ -20,4 +20,4 @@ public function testWords() $this->assertSame('fooBar', camelCase('--foo-bar--')); $this->assertSame('fooBar', camelCase('__FOO_BAR__')); } -} \ No newline at end of file +} diff --git a/tests/String/CapitalizeTest.php b/tests/String/CapitalizeTest.php index 36b3762..a2f9b14 100644 --- a/tests/String/CapitalizeTest.php +++ b/tests/String/CapitalizeTest.php @@ -18,4 +18,4 @@ public function testWords() { $this->assertSame('Fred', capitalize('FRED')); } -} \ No newline at end of file +} diff --git a/tests/String/DeburrTest.php b/tests/String/DeburrTest.php index e269b0b..b5c2eac 100644 --- a/tests/String/DeburrTest.php +++ b/tests/String/DeburrTest.php @@ -18,4 +18,4 @@ public function testWords() { $this->assertSame('deja vu', deburr('déjà vu')); } -} \ No newline at end of file +} diff --git a/tests/String/EndsWithTest.php b/tests/String/EndsWithTest.php index 4e2748f..158c4c5 100644 --- a/tests/String/EndsWithTest.php +++ b/tests/String/EndsWithTest.php @@ -22,4 +22,4 @@ public function testWords() $this->assertFalse(endsWith('abc', 'b')); $this->assertTrue(endsWith('abc', 'b', 2)); } -} \ No newline at end of file +} diff --git a/tests/String/EscapeRegExpTest.php b/tests/String/EscapeRegExpTest.php index 7b37090..c26c866 100644 --- a/tests/String/EscapeRegExpTest.php +++ b/tests/String/EscapeRegExpTest.php @@ -18,4 +18,4 @@ public function testEscapeRegExp() { $this->assertSame('\[lodash\]\(https://lodash\.com/\)', escapeRegExp('[lodash](https://lodash.com/)')); } -} \ No newline at end of file +} diff --git a/tests/String/EscapeTest.php b/tests/String/EscapeTest.php index d772754..fb5671b 100644 --- a/tests/String/EscapeTest.php +++ b/tests/String/EscapeTest.php @@ -18,4 +18,4 @@ public function testEscape() { $this->assertSame('fred, barney, & pebbles', escape('fred, barney, & pebbles')); } -} \ No newline at end of file +} diff --git a/tests/String/KebabCaseTest.php b/tests/String/KebabCaseTest.php index bfcad01..d1b50c5 100644 --- a/tests/String/KebabCaseTest.php +++ b/tests/String/KebabCaseTest.php @@ -20,4 +20,4 @@ public function testKebabCase() $this->assertSame('foo-bar', kebabCase('fooBar')); $this->assertSame('foo-bar', kebabCase('__FOO_BAR__')); } -} \ No newline at end of file +} diff --git a/tests/String/LowerCaseTest.php b/tests/String/LowerCaseTest.php index 4d3d5ef..166654a 100644 --- a/tests/String/LowerCaseTest.php +++ b/tests/String/LowerCaseTest.php @@ -20,4 +20,4 @@ public function testLowerCase() $this->assertSame('foo bar', lowerCase('fooBar')); $this->assertSame('foo bar', lowerCase('__FOO_BAR__')); } -} \ No newline at end of file +} diff --git a/tests/String/LowerFirstTest.php b/tests/String/LowerFirstTest.php index bb817ff..6dedbc0 100644 --- a/tests/String/LowerFirstTest.php +++ b/tests/String/LowerFirstTest.php @@ -19,4 +19,4 @@ public function testLowerFirst() $this->assertSame('fred', lowerFirst('Fred')); $this->assertSame('fRED', lowerFirst('FRED')); } -} \ No newline at end of file +} diff --git a/tests/String/PadEndTest.php b/tests/String/PadEndTest.php index a31d30e..5ee4056 100644 --- a/tests/String/PadEndTest.php +++ b/tests/String/PadEndTest.php @@ -20,4 +20,4 @@ public function testPadEnd() $this->assertSame('abc_-_', padEnd('abc', 6, '_-')); $this->assertSame('abc', padEnd('abc', 2)); } -} \ No newline at end of file +} diff --git a/tests/String/PadStartTest.php b/tests/String/PadStartTest.php index 3679329..c208936 100644 --- a/tests/String/PadStartTest.php +++ b/tests/String/PadStartTest.php @@ -20,4 +20,4 @@ public function testPadStart() $this->assertSame('_-_abc', padStart('abc', 6, '_-')); $this->assertSame('abc', padStart('abc', 2)); } -} \ No newline at end of file +} diff --git a/tests/String/PadTest.php b/tests/String/PadTest.php index 62da82a..720c1a0 100644 --- a/tests/String/PadTest.php +++ b/tests/String/PadTest.php @@ -20,4 +20,4 @@ public function testPad() $this->assertSame('_-abc_-_', pad('abc', 8, '_-')); $this->assertSame('abc', pad('abc', 2)); } -} \ No newline at end of file +} diff --git a/tests/String/ParseIntTest.php b/tests/String/ParseIntTest.php index d47f53b..9a0147e 100644 --- a/tests/String/ParseIntTest.php +++ b/tests/String/ParseIntTest.php @@ -19,4 +19,4 @@ public function testParseInt() $this->assertSame(8, parseInt('08')); $this->assertSame(8, parseInt('08', 10)); } -} \ No newline at end of file +} diff --git a/tests/String/RepeatTest.php b/tests/String/RepeatTest.php index 316b013..e6f2aea 100644 --- a/tests/String/RepeatTest.php +++ b/tests/String/RepeatTest.php @@ -20,4 +20,4 @@ public function testRepeat() $this->assertSame('abcabc', repeat('abc', 2)); $this->assertSame('', repeat('abc', 0)); } -} \ No newline at end of file +} diff --git a/tests/String/ReplaceTest.php b/tests/String/ReplaceTest.php index 3a65730..e1bba2f 100644 --- a/tests/String/ReplaceTest.php +++ b/tests/String/ReplaceTest.php @@ -23,4 +23,4 @@ public function testReplace() return implode(' - ', [$p1, $p2, $p3]); })); } -} \ No newline at end of file +} diff --git a/tests/String/SnakeCaseTest.php b/tests/String/SnakeCaseTest.php index 307ff7e..bffefc0 100644 --- a/tests/String/SnakeCaseTest.php +++ b/tests/String/SnakeCaseTest.php @@ -20,4 +20,4 @@ public function testSnakeCase() $this->assertSame('foo_bar', snakeCase('fooBar')); $this->assertSame('foo_bar', snakeCase('--FOO-BAR--')); } -} \ No newline at end of file +} diff --git a/tests/String/SplitTest.php b/tests/String/SplitTest.php index 7d42e39..56826c7 100644 --- a/tests/String/SplitTest.php +++ b/tests/String/SplitTest.php @@ -22,4 +22,4 @@ public function testSplit() $this->assertSame(['Hello', 'World.', 'How'], split('Hello World. How are you doing?', ' ', 3)); $this->assertSame(['Hello ', '1', ' word. Sentence number ', '2', '.'], split('Hello 1 word. Sentence number 2.', '/(\d)/')); } -} \ No newline at end of file +} diff --git a/tests/String/StartCaseTest.php b/tests/String/StartCaseTest.php index 199cdcc..7bd7a95 100644 --- a/tests/String/StartCaseTest.php +++ b/tests/String/StartCaseTest.php @@ -20,4 +20,4 @@ public function testStartCase() $this->assertSame('Foo Bar', startCase('fooBar')); $this->assertSame('FOO BAR', startCase('__FOO_BAR__')); } -} \ No newline at end of file +} diff --git a/tests/String/StartsWithTest.php b/tests/String/StartsWithTest.php index 7cf0fd9..e4c15a3 100644 --- a/tests/String/StartsWithTest.php +++ b/tests/String/StartsWithTest.php @@ -22,4 +22,4 @@ public function testStartsWith() $this->assertFalse(startsWith('abc', 'b')); $this->assertTrue(startsWith('abc', 'b', 1)); } -} \ No newline at end of file +} diff --git a/tests/String/TemplateTest.php b/tests/String/TemplateTest.php index 17a1a3b..a51403d 100644 --- a/tests/String/TemplateTest.php +++ b/tests/String/TemplateTest.php @@ -42,4 +42,4 @@ public function testTemplate() $compiled = template('hello {{ user }}!'); $this->assertSame('hello mustache!', $compiled(['user' => 'mustache'])); } -} \ No newline at end of file +} diff --git a/tests/String/ToLowerTest.php b/tests/String/ToLowerTest.php index 7feffd3..74c6e4a 100644 --- a/tests/String/ToLowerTest.php +++ b/tests/String/ToLowerTest.php @@ -20,4 +20,4 @@ public function testToLower() $this->assertSame('foobar', toLower('fooBar')); $this->assertSame('__foo_bar__', toLower('__FOO_BAR__')); } -} \ No newline at end of file +} diff --git a/tests/String/ToUpperTest.php b/tests/String/ToUpperTest.php index 5522ca7..a8eab10 100644 --- a/tests/String/ToUpperTest.php +++ b/tests/String/ToUpperTest.php @@ -20,4 +20,4 @@ public function testToUpper() $this->assertSame('FOOBAR', toUpper('fooBar')); $this->assertSame('__FOO_BAR__', toUpper('__foo_bar__')); } -} \ No newline at end of file +} diff --git a/tests/String/TrimEndTest.php b/tests/String/TrimEndTest.php index 2c3d909..e387e7d 100644 --- a/tests/String/TrimEndTest.php +++ b/tests/String/TrimEndTest.php @@ -19,4 +19,4 @@ public function testTrimEnd() $this->assertSame(' abc', trimEnd(' abc ')); $this->assertSame('-_-abc', trimEnd('-_-abc-_-', '_-')); } -} \ No newline at end of file +} diff --git a/tests/String/TrimStartTest.php b/tests/String/TrimStartTest.php index d962fd3..ad31ace 100644 --- a/tests/String/TrimStartTest.php +++ b/tests/String/TrimStartTest.php @@ -19,4 +19,4 @@ public function testTrimStart() $this->assertSame('abc ', trimStart(' abc ')); $this->assertSame('abc-_-', trimStart('-_-abc-_-', '_-')); } -} \ No newline at end of file +} diff --git a/tests/String/TrimTest.php b/tests/String/TrimTest.php index 026955c..ee5ce95 100644 --- a/tests/String/TrimTest.php +++ b/tests/String/TrimTest.php @@ -19,4 +19,4 @@ public function testTrim() $this->assertSame('abc', trim(' abc ')); $this->assertSame('abc', trim('-_-abc-_-', '_-')); } -} \ No newline at end of file +} diff --git a/tests/String/TruncateTest.php b/tests/String/TruncateTest.php index bd6ed4d..c0837d1 100644 --- a/tests/String/TruncateTest.php +++ b/tests/String/TruncateTest.php @@ -25,4 +25,4 @@ public function testTruncate() $this->assertSame("hi-diddly-ho there,\u{e800}neighbo...", truncate("hi-diddly-ho there,\u{e800}neighborino")); $this->assertSame("hi-diddly-ho there, \u{e800} neigh...", truncate("hi-diddly-ho there, \u{e800} neighborino", ['separator' => '/\s?+/'])); } -} \ No newline at end of file +} diff --git a/tests/String/UnescapeTest.php b/tests/String/UnescapeTest.php index 4dca468..1e41638 100644 --- a/tests/String/UnescapeTest.php +++ b/tests/String/UnescapeTest.php @@ -18,4 +18,4 @@ public function testUnescape() { $this->assertSame('fred, barney, & pebbles', unescape('fred, barney, & pebbles')); } -} \ No newline at end of file +} diff --git a/tests/String/UpperCaseTest.php b/tests/String/UpperCaseTest.php index 41f488c..af860d8 100644 --- a/tests/String/UpperCaseTest.php +++ b/tests/String/UpperCaseTest.php @@ -20,4 +20,4 @@ public function testUpperCase() $this->assertSame('FOO BAR', upperCase('fooBar')); $this->assertSame('FOO BAR', upperCase('__foo_bar__')); } -} \ No newline at end of file +} diff --git a/tests/String/UpperFirstTest.php b/tests/String/UpperFirstTest.php index cd5cd43..ef2222b 100644 --- a/tests/String/UpperFirstTest.php +++ b/tests/String/UpperFirstTest.php @@ -19,4 +19,4 @@ public function testUpperFirst() $this->assertSame('Fred', upperFirst('fred')); $this->assertSame('FRED', upperFirst('FRED')); } -} \ No newline at end of file +} diff --git a/tests/String/WordsTest.php b/tests/String/WordsTest.php index 603301c..7a3b28c 100644 --- a/tests/String/WordsTest.php +++ b/tests/String/WordsTest.php @@ -21,4 +21,4 @@ public function testWords() $this->assertSame([], words('fred, barney, & pebbles', '/[\d]+/')); $this->assertSame([], words("\u{e800}")); } -} \ No newline at end of file +} diff --git a/tests/Util/AttemptTest.php b/tests/Util/AttemptTest.php index feaa167..b384e03 100644 --- a/tests/Util/AttemptTest.php +++ b/tests/Util/AttemptTest.php @@ -28,4 +28,4 @@ public function testAttempt() return 'one'; }))); } -} \ No newline at end of file +} diff --git a/tests/Util/DefaultToTest.php b/tests/Util/DefaultToTest.php new file mode 100644 index 0000000..f428473 --- /dev/null +++ b/tests/Util/DefaultToTest.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2019 + */ + +use function _\defaultTo; +use PHPUnit\Framework\TestCase; + +class DefaultToTest extends TestCase +{ + public function testDefaultTo() + { + $null = null; + $default = "defaultValue"; + $realValue = "string"; + $this->assertSame($default, defaultTo($null, $default), "DefaultTo 1 failed"); + $this->assertSame($default, defaultTo(NAN, $default), "DefaultTo 2 failed"); + $this->assertSame($realValue, defaultTo($realValue, $default), "DefaultTo 3 failed"); + $this->assertSame("", defaultTo("", $default), "DefaultTo 4 failed"); + } +} diff --git a/tests/Util/IdentityTest.php b/tests/Util/IdentityTest.php index 4ee9406..a2c35bd 100644 --- a/tests/Util/IdentityTest.php +++ b/tests/Util/IdentityTest.php @@ -20,4 +20,4 @@ public function testIdentity() $this->assertSame($object, identity($object)); } -} \ No newline at end of file +} diff --git a/tests/Util/PropertyTest.php b/tests/Util/PropertyTest.php index d07eca5..e0d7afa 100644 --- a/tests/Util/PropertyTest.php +++ b/tests/Util/PropertyTest.php @@ -26,4 +26,4 @@ public function testIdentity() $this->assertSame([2, 1], map($objects, property('a.b'))); $this->assertSame([1, 2], map(sortBy($objects, property(['a', 'b'])), 'a.b')); } -} \ No newline at end of file +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy