diff --git a/README.md b/README.md index 1803287..741e16b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Supported PHP versions: * [Add](#add) * [Aggregate](#aggregate) * [Debug](#debug) - * [Order](#orderby) + * [Order](#order-by) * [Shorten](#shorten) * [Test](#test) * [Transform](#transform) @@ -231,6 +231,7 @@ will return: remove replace reverse +reversed rsort rtrim search @@ -238,10 +239,12 @@ will return: set shift shuffle +shuffled skip slice some sort +sorted splice split strAfter @@ -262,7 +265,10 @@ will return: times toArray toJson +toReversed +toSorted toUrl +transform transpose traverse tree @@ -330,9 +336,9 @@ will return: * [insertBefore()](#insertbefore) : Inserts the value before the given element * [merge()](#merge) : Combines elements overwriting existing ones * [pad()](#pad) : Fill up to the specified length with the given value -* [prepend()](#prepend) : Adds an element at the beginning +* [prepend()](#prepend) : Adds an element at the beginning (alias) * [push()](#push) : Adds an element to the end -* [put()](#put) : Sets the given key and value in the map +* [put()](#put) : Sets the given key and value in the map (alias) * [set()](#set) : Overwrites or adds an element * [union()](#union) : Adds the elements without overwriting existing ones * [unshift()](#unshift) : Adds an element at the beginning @@ -354,7 +360,7 @@ will return: * [dump()](#dump) : Prints the map content * [tap()](#tap) : Passes a clone of the map to the given callback -### OrderBy +### Order By * [arsort()](#arsort) : Reverse sort elements preserving keys * [asort()](#asort) : Sort elements preserving keys @@ -362,9 +368,14 @@ will return: * [ksort()](#ksort) : Sort elements by keys * [order()](#order) : Orders elements by the passed keys * [reverse()](#reverse) : Reverses the array order preserving keys +* [reversed()](#reversed) : Reverses the element order in a copy of the map +* [toReversed()](#toreversed) : Reverses the element order in a copy of the map (alias) * [rsort()](#rsort) : Reverse sort elements using new keys * [shuffle()](#shuffle) : Randomizes the element order -* [sort()](#sort) : Sorts the elements assigning new keys +* [shuffled()](#shuffled) : Randomizes the element order in a copy of the map +* [sort()](#sort) : Sorts the elements in-place assigning new keys +* [sorted()](#sorted) : Sorts the elements in a copy of the map using new keys +* [toSorted()](#tosorted) : Sorts the elements in a copy of the map using new keys (alias) * [uasort()](#uasort) : Sorts elements preserving keys using callback * [uksort()](#uksort) : Sorts elements by keys using callback * [usort()](#usort) : Sorts elements using callback assigning new keys @@ -442,7 +453,7 @@ will return: * [map()](#map) : Applies a callback to each element and returns the results * [partition()](#partition) : Breaks the list into the given number of groups * [pipe()](#pipe) : Applies a callback to the whole map -* [pluck()](#pluck) : Creates a key/value mapping +* [pluck()](#pluck) : Creates a key/value mapping (alias) * [prefix()](#prefix) : Adds a prefix to each map entry * [reduce()](#reduce) : Computes a single value from the map content * [rekey()](#rekey) : Changes the keys according to the passed function @@ -456,6 +467,7 @@ will return: * [suffix()](#suffix) : Adds a suffix to each map entry * [toJson()](#tojson) : Returns the elements in JSON format * [toUrl()](#tourl) : Creates a HTTP query string +* [transfrom()](#transfrom) : Applies a callback to each element which creates new key/value pairs * [transpose()](#transpose) : Exchanges rows and columns for a two dimensional map * [traverse()](#traverse) : Traverses trees of nested items passing each item to the callback * [trim()](#trim) : Removes the passed characters from the left/right of all strings @@ -537,6 +549,11 @@ map( function() { } ); ``` +**See also:** + +* [rekey()](#rekey) - Changes the keys according to the passed function +* [transform()](#transform) - Creates new key/value pairs using the passed function and returns a new map for the result + ### __construct() @@ -2523,7 +2540,7 @@ Map::from( ['1', '2'] )->in( 2, true ); ### includes() -Tests if the passed element or elements are part of the map. +Tests if the passed element or elements are part of the map (alias). ```php public function includes( $element, bool $strict = false ) : bool @@ -2536,24 +2553,9 @@ public function includes( $element, bool $strict = false ) : bool This method is an alias for [in()](#in). For performance reasons, `in()` should be preferred because it uses one method call less than `includes()`. -**Examples:** - -```php -Map::from( ['a', 'b'] )->includes( 'a' ); -// true - -Map::from( ['a', 'b'] )->includes( ['a', 'b'] ); -// true - -Map::from( ['a', 'b'] )->includes( 'x' ); -// false +**See also:** -Map::from( ['a', 'b'] )->includes( ['a', 'x'] ); -// false - -Map::from( ['1', '2'] )->includes( 2, true ); -// false -``` +* [in()](#in) - Underlying method with same parameters and return value but better performance ### index() @@ -3354,7 +3356,7 @@ Map::from( ["a b c", "cbxa"] )->ltrim( 'abc' ); ### map() -Calls the passed function once for each element and returns a new map for the result. +Maps new values to the existing keys using the passed function and returns a new map for the result. ```php public function map( callable $callback ) : self @@ -3861,7 +3863,7 @@ Map::from( ['a', 'b'] )->pipe( function( $map ) { ### pluck() -Returns the values of a single column/property from an array of arrays or list of elements in a new map. +Returns the values of a single column/property from an array of arrays or list of elements in a new map (alias). ```php public function pluck( string $valuecol = null, string $indexcol = null ) : self @@ -3874,6 +3876,9 @@ public function pluck( string $valuecol = null, string $indexcol = null ) : self This method is an alias for [col()](#col). For performance reasons, `col()` should be preferred because it uses one method call less than `pluck()`. +**See also:** + +* [col()](#col) - Underlying method with same parameters and return value but better performance ### pop() @@ -3956,7 +3961,7 @@ Map::from( ['a', 'b'] )->prefix( function( $item, $key ) { ### prepend() -Pushes an element onto the beginning of the map without returning a new map. +Pushes an element onto the beginning of the map without returning a new map (alias). ```php public function prepend( $value, $key = null ) : self @@ -3966,17 +3971,12 @@ public function prepend( $value, $key = null ) : self * @param **int|string|null** `$key` Key for the item or NULL to reindex all numerical keys * @return **self<int|string,mixed>** Updated map for fluid interface -This method is an alias for the [unshift()](#unshift) method. +This method is an alias for the [unshift()](#unshift) method. For performance reasons, `unshift()` should +be preferred because it uses one method call less than `prepend()`. -**Examples:** +**See also:** -```php -Map::from( ['a', 'b'] )->prepend( 'd' ); -// ['d', 'a', 'b'] - -Map::from( ['a', 'b'] )->prepend( 'd', 'first' ); -// ['first' => 'd', 0 => 'a', 1 => 'b'] -``` +* [unshift()](#unshift) - Underlying method with same parameters and return value but better performance ### pull() @@ -4023,28 +4023,22 @@ Map::from( ['a', 'b'] )->push( 'aa' ); ### put() -Sets the given key and value in the map without returning a new map. +Sets the given key and value in the map without returning a new map (alias). ```php public function put( $key, $value ) : self ``` -This method is an alias for `set()`. For performance reasons, `set()` should be -preferred because it uses one method call less than `put()`. - * @param **int|string** `$key` Key to set the new value for * @param **mixed** `$value` New element that should be set * @return **self<int|string,mixed>** Updated map for fluid interface -**Examples:** +This method is an alias for [set()](#set). For performance reasons, `set()` should be +preferred because it uses one method call less than `put()`. -```php -Map::from( ['a'] )->put( 1, 'b' ); -// [0 => 'a', 1 => 'b'] +**See also:** -Map::from( ['a'] )->put( 0, 'b' ); -// [0 => 'b'] -``` +* [set()](#set) - Underlying method with same parameters and return value but better performance ### random() @@ -4155,6 +4149,11 @@ Map::from( ['a' => 2, 'b' => 4] )->rekey( function( $value, $key ) { // ['key-a' => 2, 'key-b' => 4] ``` +**See also:** + +* [map()](#map) - Maps new values to the existing keys using the passed function and returns a new map for the result +* [transform()](#transform) - Creates new key/value pairs using the passed function and returns a new map for the result + ### remove() @@ -4228,6 +4227,38 @@ Map::from( ['name' => 'test', 'last' => 'user'] )->reverse(); // ['last' => 'user', 'name' => 'test'] ``` +**See also:** + +* [reversed()](#reversed) - Reverses the element order in a copy of the map + + +### reversed() + +Reverses the element order in a copy of the map. + +```php +public function reversed() : self +``` + +* @return **self<int|string,mixed>** New map with a reversed copy of the elements + +The keys are preserved using this method and a new map is created before reversing the elements. +Thus, [reverse()](#reverse) should be preferred for performance reasons if possible. + +**Examples:** + +```php +Map::from( ['a', 'b'] )->reversed(); +// ['b', 'a'] + +Map::from( ['name' => 'test', 'last' => 'user'] )->reversed(); +// ['last' => 'user', 'name' => 'test'] +``` + +**See also:** + +* [reverse()](#reverse) - Reverses the element order without returning a new map + ### rsort() @@ -4417,6 +4448,36 @@ Map::from( [2 => 'a', 4 => 'b'] )->shuffle( true ); // [2 => 'a', 4 => 'b'] in random order with keys preserved ``` +**See also:** + +* [shuffled()](#shuffled) - Shuffles the elements in a copy of the map. + + +### shuffled() + +Shuffles the elements in a copy of the map. + +```php +public function shuffled( bool $assoc = false ) : self +``` + +* @param **bool** `$assoc` True to preserve keys, false to assign new keys +* @return **self<int|string,mixed>** New map with a shuffled copy of the elements + +**Examples:** + +```php +Map::from( [2 => 'a', 4 => 'b'] )->shuffled(); +// ['a', 'b'] in random order with new keys + +Map::from( [2 => 'a', 4 => 'b'] )->shuffled( true ); +// [2 => 'a', 4 => 'b'] in random order with keys preserved +``` + +**See also:** + +* [shuffle()](#shuffle) - Shuffles the elements in the map without returning a new map + ### skip() @@ -4524,7 +4585,7 @@ Sorts all elements without maintaining the key association. public function sort( int $options = SORT_REGULAR ) : self ``` -* @param **int** `$options` Sort options for `sort()` +* @param **int** `$options` Sort options for PHP `sort()` * @return **self<int|string,mixed>** Updated map for fluid interface The parameter modifies how the values are compared. Possible parameter values are: @@ -4548,6 +4609,43 @@ Map::from( [0 => 'b', 1 => 'a'] )->sort(); ``` +### sorted() + +Sorts the elements in a copy of the map using new keys. + +```php +public function sorted( int $options = SORT_REGULAR ) : self +``` + +* @param **int** `$options` Sort options for PHP `sort()` +* @return **self<int|string,mixed>** New map with a sorted copy of the elements + +The parameter modifies how the values are compared. Possible parameter values are: +- SORT_REGULAR : compare elements normally (don't change types) +- SORT_NUMERIC : compare elements numerically +- SORT_STRING : compare elements as strings +- SORT_LOCALE_STRING : compare elements as strings, based on the current locale or changed by `setlocale()` +- SORT_NATURAL : compare elements as strings using "natural ordering" like `natsort()` +- SORT_FLAG_CASE : use SORT_STRING|SORT_FLAG_CASE and SORT_NATURAL|SORT_FLAG_CASE to sort strings case-insensitively + +The keys aren't preserved and elements get a new index and a new map is created before sorting the elements. +Thus, [sort()](#sort) should be preferred for performance reasons if possible. + +**Examples:** + +```php +Map::from( ['a' => 1, 'b' => 0] )->sorted(); +// [0 => 0, 1 => 1] + +Map::from( [0 => 'b', 1 => 'a'] )->sorted(); +// [0 => 'a', 1 => 'b'] +``` + +**See also:** + +* [sort()](#sort) - Sorts elements in-place in the original map + + ### splice() Removes a portion of the map and replace it with the given replacement, then return the updated map. @@ -5367,6 +5465,43 @@ Map::from( ['a', 'b'] )->toJson( JSON_FORCE_OBJECT ); ``` +### toReversed() + +Reverses the element order in a copy of the map (alias). + +```php +public function toReversed() : self +``` + +* @return **self<int|string,mixed>** New map with a reversed copy of the elements + +This method is an alias for [reversed()](#reversed). For performance reasons, reversed() should be +preferred because it uses one method call less than toReversed(). + +**See also:** + +* [reversed()](#reversed) - Underlying method with same parameters and return value but better performance + + +### toSorted() + +Sorts the elements in a copy of the map using new keys (alias). + +```php +public function toSorted( int $options = SORT_REGULAR ) : self +``` + +* @param **int** `$options` Sort options for PHP `sort()` +* @return **self<int|string,mixed>** New map with a sorted copy of the elements + +This method is an alias for [sorted()](#sorted). For performance reasons, sorted() should be +preferred because it uses one method call less than toSorted(). + +**See also:** + +* [sorted()](#sorted) - Underlying method with same parameters and return value but better performance + + ### toUrl() Creates a HTTP query string from the map elements. @@ -5388,6 +5523,49 @@ Map::from( ['a' => ['b' => 'abc', 'c' => 'def'], 'd' => 123] )->toUrl(); ``` +### transform() + +Creates new key/value pairs using the passed function and returns a new map for the result. + +```php +public function transform( \Closure $callback ) : self +``` + +* @param **\Closure** `$callback` Function with (value, key) parameters and returns an array of new key/value pair(s) +* @return **self<int|string,mixed>** New map with the new key/value pairs + +If a key is returned twice, the last value will overwrite previous values. + +**Examples:** + +```php +Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + return [$key . '-2' => $value * 2]; +} ); +// ['a-2' => 4, 'b-2' => 8] + +Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + return [$key => $value * 2, $key . $key => $value * 4]; +} ); +// ['a' => 4, 'aa' => 8, 'b' => 8, 'bb' => 16] + +Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + return $key < 'b' ? [$key => $value * 2] : null; +} ); +// ['a' => 4] + +Map::from( ['la' => 2, 'le' => 4, 'li' => 6] )->transform( function( $value, $key ) { + return [$key[0] => $value * 2]; +} ); +// ['l' => 12] +``` + +**See also:** + +* [map()](#map) - Maps new values to the existing keys using the passed function and returns a new map for the result +* [rekey()](#rekey) - Changes the keys according to the passed function + + ### transpose() Exchanges rows and columns for a two dimensional map. diff --git a/composer.json b/composer.json index ac52558..b5a66d5 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "require-dev": { "squizlabs/php_codesniffer": "^3.5", "php-coveralls/php-coveralls": "~2.0", - "phpunit/phpunit": "~7.0||~8.0||~9.0" + "phpunit/phpunit": "~7.0||~8.0||~9.0||~10.0||~11.0" }, "autoload": { "psr-4": { diff --git a/src/Map.php b/src/Map.php index 515d7cb..9c7ad55 100644 --- a/src/Map.php +++ b/src/Map.php @@ -317,7 +317,7 @@ public static function fromJson( string $json, int $options = JSON_BIGINT_AS_STR * @param \Closure|null $fcn Anonymous function or NULL to return the closure if available * @return \Closure|null Registered anonymous function or NULL if none has been registered */ - public static function method( string $method, \Closure $fcn = null ) : ?\Closure + public static function method( string $method, ?\Closure $fcn = null ) : ?\Closure { if( $fcn ) { self::$methods[$method] = $fcn; @@ -810,7 +810,7 @@ public function clone() : self * @param string|null $indexcol Name or path of the index property * @return self New map with mapped entries */ - public function col( string $valuecol = null, string $indexcol = null ) : self + public function col( ?string $valuecol = null, ?string $indexcol = null ) : self { $vparts = explode( $this->sep, (string) $valuecol ); $iparts = explode( $this->sep, (string) $indexcol ); @@ -864,7 +864,7 @@ public function col( string $valuecol = null, string $indexcol = null ) : self * @return self New map with all sub-array elements added into it recursively, up to the specified depth * @throws \InvalidArgumentException If depth must be greater or equal than 0 or NULL */ - public function collapse( int $depth = null ) : self + public function collapse( ?int $depth = null ) : self { if( $depth < 0 ) { throw new \InvalidArgumentException( 'Depth must be greater or equal than 0 or NULL' ); @@ -984,7 +984,7 @@ public function concat( iterable $elements ) : self * @param mixed $value Value used for comparison * @return bool TRUE if at least one element is available in map, FALSE if the map contains none of them */ - public function contains( $key, string $operator = null, $value = null ) : bool + public function contains( $key, ?string $operator = null, $value = null ) : bool { if( $operator === null ) { return $this->some( $key ); @@ -1046,7 +1046,7 @@ public function count() : int * @param callable|null $callback Function with (value, key) parameters which returns the value to use for counting * @return self New map with values as keys and their count as value */ - public function countBy( callable $callback = null ) : self + public function countBy( ?callable $callback = null ) : self { $callback = $callback ?: function( $value ) { return (string) $value; @@ -1075,7 +1075,7 @@ public function countBy( callable $callback = null ) : self * * @param callable|null $callback Function receiving the map elements as parameter (optional) */ - public function dd( callable $callback = null ) : void + public function dd( ?callable $callback = null ) : void { $this->dump( $callback ); exit( 1 ); @@ -1111,7 +1111,7 @@ public function dd( callable $callback = null ) : void * @param callable|null $callback Function with (valueA, valueB) parameters and returns -1 (<), 0 (=) and 1 (>) * @return self New map */ - public function diff( iterable $elements, callable $callback = null ) : self + public function diff( iterable $elements, ?callable $callback = null ) : self { if( $callback ) { return new static( array_udiff( $this->list(), $this->array( $elements ), $callback ) ); @@ -1152,7 +1152,7 @@ public function diff( iterable $elements, callable $callback = null ) : self * @param callable|null $callback Function with (valueA, valueB) parameters and returns -1 (<), 0 (=) and 1 (>) * @return self New map */ - public function diffAssoc( iterable $elements, callable $callback = null ) : self + public function diffAssoc( iterable $elements, ?callable $callback = null ) : self { if( $callback ) { return new static( array_diff_uassoc( $this->list(), $this->array( $elements ), $callback ) ); @@ -1192,7 +1192,7 @@ public function diffAssoc( iterable $elements, callable $callback = null ) : sel * @param callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>) * @return self New map */ - public function diffKeys( iterable $elements, callable $callback = null ) : self + public function diffKeys( iterable $elements, ?callable $callback = null ) : self { if( $callback ) { return new static( array_diff_ukey( $this->list(), $this->array( $elements ), $callback ) ); @@ -1227,7 +1227,7 @@ public function diffKeys( iterable $elements, callable $callback = null ) : self * @param callable|null $callback Function receiving the map elements as parameter (optional) * @return self Same map for fluid interface */ - public function dump( callable $callback = null ) : self + public function dump( ?callable $callback = null ) : self { $callback ? $callback( $this->list() ) : print_r( $this->list() ); return $this; @@ -1260,7 +1260,7 @@ public function dump( callable $callback = null ) : self * @param string|null $key Key or path of the nested array or object to check for * @return self New map */ - public function duplicates( string $key = null ) : self + public function duplicates( ?string $key = null ) : self { $list = $this->list(); $items = ( $key !== null ? $this->col( $key )->toArray() : $list ); @@ -1422,7 +1422,7 @@ public function except( $keys ) : self * @param callable|null $callback Function with (item, key) parameters and returns TRUE/FALSE * @return self New map */ - public function filter( callable $callback = null ) : self + public function filter( ?callable $callback = null ) : self { if( $callback ) { return new static( array_filter( $this->list(), $callback, ARRAY_FILTER_USE_BOTH ) ); @@ -1563,7 +1563,7 @@ public function firstKey() * @return self New map with all sub-array elements added into it recursively, up to the specified depth * @throws \InvalidArgumentException If depth must be greater or equal than 0 or NULL */ - public function flat( int $depth = null ) : self + public function flat( ?int $depth = null ) : self { if( $depth < 0 ) { throw new \InvalidArgumentException( 'Depth must be greater or equal than 0 or NULL' ); @@ -1899,7 +1899,7 @@ public function has( $key ) : bool * @param \Closure|null $else Function with (map, condition) parameter (optional) * @return self New map */ - public function if( $condition, \Closure $then = null, \Closure $else = null ) : self + public function if( $condition, ?\Closure $then = null, ?\Closure $else = null ) : self { if( $condition instanceof \Closure ) { $condition = $condition( $this ); @@ -1952,7 +1952,7 @@ public function if( $condition, \Closure $then = null, \Closure $else = null ) : * @param \Closure|null $else Function with (map, condition) parameter (optional) * @return self New map */ - public function ifAny( \Closure $then = null, \Closure $else = null ) : self + public function ifAny( ?\Closure $then = null, ?\Closure $else = null ) : self { return $this->if( !empty( $this->list() ), $then, $else ); } @@ -1990,7 +1990,7 @@ public function ifAny( \Closure $then = null, \Closure $else = null ) : self * @param \Closure|null $else Function with (map, condition) parameter (optional) * @return self New map */ - public function ifEmpty( \Closure $then = null, \Closure $else = null ) : self + public function ifEmpty( ?\Closure $then = null, ?\Closure $else = null ) : self { return $this->if( empty( $this->list() ), $then, $else ); } @@ -2073,22 +2073,13 @@ public function in( $element, bool $strict = false ) : bool /** * Tests if the passed element or elements are part of the map. * - * Examples: - * Map::from( ['a', 'b'] )->includes( 'a' ); - * Map::from( ['a', 'b'] )->includes( ['a', 'b'] ); - * Map::from( ['a', 'b'] )->includes( 'x' ); - * Map::from( ['a', 'b'] )->includes( ['a', 'x'] ); - * Map::from( ['1', '2'] )->includes( 2, true ); - * - * Results: - * The first and second example will return TRUE while the other ones will return FALSE - * * This method is an alias for in(). For performance reasons, in() should be * preferred because it uses one method call less than includes(). * * @param mixed|array $element Element or elements to search for in the map * @param bool $strict TRUE to check the type too, using FALSE '1' and 1 will be the same * @return bool TRUE if all elements are available in map, FALSE if not + * @see in() - Underlying method with same parameters and return value but better performance */ public function includes( $element, bool $strict = false ) : bool { @@ -2355,7 +2346,7 @@ public function int( $key, $default = 0 ) : int * @param callable|null $callback Function with (valueA, valueB) parameters and returns -1 (<), 0 (=) and 1 (>) * @return self New map */ - public function intersect( iterable $elements, callable $callback = null ) : self + public function intersect( iterable $elements, ?callable $callback = null ) : self { $list = $this->list(); $elements = $this->array( $elements ); @@ -2401,7 +2392,7 @@ public function intersect( iterable $elements, callable $callback = null ) : sel * @param callable|null $callback Function with (valueA, valueB) parameters and returns -1 (<), 0 (=) and 1 (>) * @return self New map */ - public function intersectAssoc( iterable $elements, callable $callback = null ) : self + public function intersectAssoc( iterable $elements, ?callable $callback = null ) : self { $elements = $this->array( $elements ); @@ -2444,7 +2435,7 @@ public function intersectAssoc( iterable $elements, callable $callback = null ) * @param callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>) * @return self New map */ - public function intersectKeys( iterable $elements, callable $callback = null ) : self + public function intersectKeys( iterable $elements, ?callable $callback = null ) : self { $list = $this->list(); $elements = $this->array( $elements ); @@ -2850,7 +2841,7 @@ public function ltrim( string $chars = " \n\r\t\v\x00" ) : self /** - * Calls the passed function once for each element and returns a new map for the result. + * Maps new values to the existing keys using the passed function and returns a new map for the result. * * Examples: * Map::from( ['a' => 2, 'b' => 4] )->map( function( $value, $key ) { @@ -2864,6 +2855,8 @@ public function ltrim( string $chars = " \n\r\t\v\x00" ) : self * * @param callable $callback Function with (value, key) parameters and returns computed result * @return self New map with the original keys and the computed values + * @see rekey() - Changes the keys according to the passed function + * @see transform() - Creates new key/value pairs using the passed function and returns a new map for the result */ public function map( callable $callback ) : self { @@ -3354,8 +3347,9 @@ public function pipe( \Closure $callback ) * @param string|null $valuecol Name or path of the value property * @param string|null $indexcol Name or path of the index property * @return self New map with mapped entries + * @see col() - Underlying method with same parameters and return value but better performance */ - public function pluck( string $valuecol = null, string $indexcol = null ) : self + public function pluck( ?string $valuecol = null, ?string $indexcol = null ) : self { return $this->col( $valuecol, $indexcol ); } @@ -3449,7 +3443,7 @@ public function pos( $value ) : ?int * @param int|null $depth Maximum depth to dive into multi-dimensional arrays starting from "1" * @return self Updated map for fluid interface */ - public function prefix( $prefix, int $depth = null ) : self + public function prefix( $prefix, ?int $depth = null ) : self { $fcn = function( array $list, $prefix, int $depth ) use ( &$fcn ) { @@ -3478,6 +3472,7 @@ public function prefix( $prefix, int $depth = null ) : self * @param mixed $value Item to add at the beginning * @param int|string|null $key Key for the item or NULL to reindex all numerical keys * @return self Updated map for fluid interface + * @see unshift() - Underlying method with same parameters and return value but better performance */ public function prepend( $value, $key = null ) : self { @@ -3536,19 +3531,13 @@ public function push( $value ) : self /** * Sets the given key and value in the map without returning a new map. * - * Examples: - * Map::from( ['a'] )->put( 1, 'b' ); - * Map::from( ['a'] )->put( 0, 'b' ); - * - * Results: - * The first example results in ['a', 'b'] while the second one produces ['b'] - * * This method is an alias for set(). For performance reasons, set() should be * preferred because it uses one method call less than put(). * * @param int|string $key Key to set the new value for * @param mixed $value New element that should be set * @return self Updated map for fluid interface + * @see set() - Underlying method with same parameters and return value but better performance */ public function put( $key, $value ) : self { @@ -3668,6 +3657,8 @@ public function reject( $callback = true ) : self * * @param callable $callback Function with (value, key) parameters and returns new key * @return self New map with new keys and original values + * @see map() - Maps new values to the existing keys using the passed function and returns a new map for the result + * @see transform() - Creates new key/value pairs using the passed function and returns a new map for the result */ public function rekey( callable $callback ) : self { @@ -3749,6 +3740,7 @@ public function replace( iterable $elements, bool $recursive = true ) : self * The keys are preserved using this method. * * @return self Updated map for fluid interface + * @see reversed() - Reverses the element order in a copy of the map */ public function reverse() : self { @@ -3757,6 +3749,29 @@ public function reverse() : self } + /** + * Reverses the element order in a copy of the map. + * + * Examples: + * Map::from( ['a', 'b'] )->reversed(); + * Map::from( ['name' => 'test', 'last' => 'user'] )->reversed(); + * + * Results: + * ['b', 'a'] + * ['last' => 'user', 'name' => 'test'] + * + * The keys are preserved using this method and a new map is created before reversing the elements. + * Thus, reverse() should be preferred for performance reasons if possible. + * + * @return self New map with a reversed copy of the elements + * @see reverse() - Reverses the element order with keys without returning a new map + */ + public function reversed() : self + { + return ( clone $this )->reverse(); + } + + /** * Sorts all elements in reverse order using new keys. * @@ -3927,6 +3942,7 @@ public function shift() * * @param bool $assoc True to preserve keys, false to assign new keys * @return self Updated map for fluid interface + * @see shuffled() - Shuffles the elements in a copy of the map */ public function shuffle( bool $assoc = false ) : self { @@ -3948,11 +3964,32 @@ public function shuffle( bool $assoc = false ) : self shuffle( $this->list() ); } - return $this; } + /** + * Shuffles the elements in a copy of the map. + * + * Examples: + * Map::from( [2 => 'a', 4 => 'b'] )->shuffled(); + * Map::from( [2 => 'a', 4 => 'b'] )->shuffled( true ); + * + * Results: + * The map in the first example will contain "a" and "b" in random order and + * with new keys assigned. The second call will also return all values in + * random order but preserves the keys of the original list. + * + * @param bool $assoc True to preserve keys, false to assign new keys + * @return self New map with a shuffled copy of the elements + * @see shuffle() - Shuffles the elements in the map without returning a new map + */ + public function shuffled( bool $assoc = false ) : self + { + return ( clone $this )->shuffle( $assoc ); + } + + /** * Returns a new map with the given number of items skipped. * @@ -4027,7 +4064,7 @@ public function skip( $offset ) : self * @param int|null $length Number of elements to return or NULL for no limit * @return self New map */ - public function slice( int $offset, int $length = null ) : self + public function slice( int $offset, ?int $length = null ) : self { return new static( array_slice( $this->list(), $offset, $length, true ) ); } @@ -4086,7 +4123,7 @@ public function some( $values, bool $strict = false ) : bool /** - * Sorts all elements using new keys. + * Sorts all elements in-place using new keys. * * Examples: * Map::from( ['a' => 1, 'b' => 0] )->sort(); @@ -4106,8 +4143,9 @@ public function some( $values, bool $strict = false ) : bool * * The keys aren't preserved and elements get a new index. No new map is created. * - * @param int $options Sort options for sort() + * @param int $options Sort options for PHP sort() * @return self Updated map for fluid interface + * @see sorted() - Sorts elements in a copy of the map */ public function sort( int $options = SORT_REGULAR ) : self { @@ -4116,6 +4154,38 @@ public function sort( int $options = SORT_REGULAR ) : self } + /** + * Sorts the elements in a copy of the map using new keys. + * + * Examples: + * Map::from( ['a' => 1, 'b' => 0] )->sorted(); + * Map::from( [0 => 'b', 1 => 'a'] )->sorted(); + * + * Results: + * [0 => 0, 1 => 1] + * [0 => 'a', 1 => 'b'] + * + * The parameter modifies how the values are compared. Possible parameter values are: + * - SORT_REGULAR : compare elements normally (don't change types) + * - SORT_NUMERIC : compare elements numerically + * - SORT_STRING : compare elements as strings + * - SORT_LOCALE_STRING : compare elements as strings, based on the current locale or changed by setlocale() + * - SORT_NATURAL : compare elements as strings using "natural ordering" like natsort() + * - SORT_FLAG_CASE : use SORT_STRING|SORT_FLAG_CASE and SORT_NATURALSORT_FLAG_CASE to sort strings case-insensitively + * + * The keys aren't preserved and elements get a new index and a new map is created before sorting the elements. + * Thus, sort() should be preferred for performance reasons if possible. + * + * @param int $options Sort options for PHP sort() + * @return self New map with a sorted copy of the elements + * @see sort() - Sorts elements in-place in the original map + */ + public function sorted( int $options = SORT_REGULAR ) : self + { + return ( clone $this )->sort( $options ); + } + + /** * Removes a portion of the map and replace it with the given replacement, then return the updated map. * @@ -4146,7 +4216,7 @@ public function sort( int $options = SORT_REGULAR ) : self * @param mixed $replacement List of elements to insert * @return self New map */ - public function splice( int $offset, int $length = null, $replacement = [] ) : self + public function splice( int $offset, ?int $length = null, $replacement = [] ) : self { if( $length === null ) { $length = count( $this->list() ); @@ -4716,7 +4786,7 @@ public function strUpper( string $encoding = 'UTF-8' ) :self * @param int|null $depth Maximum depth to dive into multi-dimensional arrays starting from "1" * @return self Updated map for fluid interface */ - public function suffix( $suffix, int $depth = null ) : self + public function suffix( $suffix, ?int $depth = null ) : self { $fcn = function( $list, $suffix, $depth ) use ( &$fcn ) { @@ -4890,6 +4960,37 @@ public function toJson( int $options = 0 ) : ?string } + /** + * Reverses the element order in a copy of the map (alias). + * + * This method is an alias for reversed(). For performance reasons, reversed() should be + * preferred because it uses one method call less than toReversed(). + * + * @return self New map with a reversed copy of the elements + * @see reversed() - Underlying method with same parameters and return value but better performance + */ + public function toReversed() : self + { + return $this->reversed(); + } + + + /** + * Sorts the elements in a copy of the map using new keys (alias). + * + * This method is an alias for sorted(). For performance reasons, sorted() should be + * preferred because it uses one method call less than toSorted(). + * + * @param int $options Sort options for PHP sort() + * @return self New map with a sorted copy of the elements + * @see sorted() - Underlying method with same parameters and return value but better performance + */ + public function toSorted( int $options = SORT_REGULAR ) : self + { + return $this->sorted( $options ); + } + + /** * Creates a HTTP query string from the map elements. * @@ -4909,6 +5010,51 @@ public function toUrl() : string } + /** + * Creates new key/value pairs using the passed function and returns a new map for the result. + * + * Examples: + * Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + * return [$key . '-2' => $value * 2]; + * } ); + * Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + * return [$key => $value * 2, $key . $key => $value * 4]; + * } ); + * Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + * return $key < 'b' ? [$key => $value * 2] : null; + * } ); + * Map::from( ['la' => 2, 'le' => 4, 'li' => 6] )->transform( function( $value, $key ) { + * return [$key[0] => $value * 2]; + * } ); + * + * Results: + * ['a-2' => 4, 'b-2' => 8] + * ['a' => 4, 'aa' => 8, 'b' => 8, 'bb' => 16] + * ['a' => 4] + * ['l' => 12] + * + * If a key is returned twice, the last value will overwrite previous values. + * + * @param \Closure $callback Function with (value, key) parameters and returns an array of new key/value pair(s) + * @return self New map with the new key/value pairs + * @see map() - Maps new values to the existing keys using the passed function and returns a new map for the result + * @see rekey() - Changes the keys according to the passed function + */ + public function transform( \Closure $callback ) : self + { + $result = []; + + foreach( $this->list() as $key => $value ) + { + foreach( (array) $callback( $value, $key ) as $newkey => $newval ) { + $result[$newkey] = $newval; + } + } + + return new static( $result ); + } + + /** * Exchanges rows and columns for a two dimensional map. * @@ -5018,7 +5164,7 @@ public function transpose() : self * @param string $nestKey Key to the children of each item * @return self New map with all items as flat list */ - public function traverse( \Closure $callback = null, string $nestKey = 'children' ) : self + public function traverse( ?\Closure $callback = null, string $nestKey = 'children' ) : self { $result = []; $this->visit( $this->list(), $result, 0, $callback, $nestKey ); @@ -5233,7 +5379,7 @@ public function union( iterable $elements ) : self * @param string|null $key Key or path of the nested array or object to check for * @return self New map */ - public function unique( string $key = null ) : self + public function unique( ?string $key = null ) : self { if( $key !== null ) { return $this->col( null, $key )->values(); diff --git a/tests/MapTest.php b/tests/MapTest.php index a19fc66..1bc6fc6 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -1247,20 +1247,14 @@ public function testGrepException() { set_error_handler( function( $errno, $str, $file, $line ) { return true; } ); - $this->expectException( \RuntimeException::class ); - Map::from( [] )->grep( 'b' ); - } - - - public function testGrepWarning() - { - if( method_exists( $this, 'expectWarning' ) ) { - $this->expectWarning(); // PHPUnit 8+ - } else { - $this->expectException( \PHPUnit\Framework\Error\Warning::class ); // PHP 7.1 + try { + Map::from( [] )->grep( 'b' ); + $this->fail( 'An expected RuntimeException has not been raised' ); + } catch( \RuntimeException $e ) { + $this->assertStringContainsString( 'Regular expression error', $e->getMessage() ); + } finally { + restore_error_handler(); } - - Map::from( [] )->grep( 'b' ); } @@ -2606,6 +2600,17 @@ public function testReverseKeys() } + public function testReversed() + { + $m = new Map( ['hello', 'world'] ); + $r = $m->reversed(); + + $this->assertNotSame( $r, $m ); + $this->assertInstanceOf( Map::class, $r ); + $this->assertSame( [1 => 'world', 0 => 'hello'], $r->toArray() ); + } + + public function testRsortNummeric() { $m = ( new Map( [-1, -3, -2, -4, -5, 0, 5, 3, 1, 2, 4] ) )->rsort(); @@ -2719,6 +2724,17 @@ public function testShuffleAssoc() } + public function testShuffled() + { + $m = new Map( range( 0, 100, 10 ) ); + $r = $m->shuffled(); + + $this->assertNotSame( $r, $m ); + $this->assertInstanceOf( Map::class, $r ); + $this->assertNotEquals( $r->toArray(), $m->toArray() ); + } + + public function testSkip() { $this->assertSame( [2 => 3, 3 => 4], Map::from( [1, 2, 3, 4] )->skip( 2 )->toArray() ); @@ -2835,6 +2851,17 @@ public function testSomeCallback() } + public function testSorted() + { + $m = new Map( [-1, -3, -2, -4, -5, 0, 5, 3, 1, 2, 4] ); + $n = $m->sorted(); + + $this->assertNotSame( $n, $m ); + $this->assertInstanceOf( Map::class, $n ); + $this->assertSame( [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5], $n->toArray() ); + } + + public function testSortNummeric() { $m = ( new Map( [-1, -3, -2, -4, -5, 0, 5, 3, 1, 2, 4] ) )->sort(); @@ -3204,6 +3231,72 @@ public function testTimesObjects() } + public function testToReversed() + { + $m = new Map( ['hello', 'world'] ); + $r = $m->toReversed(); + + $this->assertNotSame( $r, $m ); + $this->assertInstanceOf( Map::class, $r ); + $this->assertSame( [1 => 'world', 0 => 'hello'], $r->toArray() ); + } + + + public function testToSorted() + { + $m = new Map( [-1, -3, -2, -4, -5, 0, 5, 3, 1, 2, 4] ); + $n = $m->toSorted(); + + $this->assertNotSame( $n, $m ); + $this->assertInstanceOf( Map::class, $n ); + $this->assertSame( [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5], $n->toArray() ); + } + + + public function testTransform() + { + $m = Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + return [$key . '-2' => $value * 2]; + } ); + + $this->assertInstanceOf( Map::class, $m ); + $this->assertSame( ['a-2' => 4, 'b-2' => 8], $m->toArray() ); + } + + + public function testTransformExtend() + { + $m = Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + return [$key => $value * 2, $key . $key => $value * 4]; + } ); + + $this->assertInstanceOf( Map::class, $m ); + $this->assertSame( ['a' => 4, 'aa' => 8, 'b' => 8, 'bb' => 16], $m->toArray() ); + } + + + public function testTransformShorten() + { + $m = Map::from( ['a' => 2, 'b' => 4] )->transform( function( $value, $key ) { + return $key < 'b' ? [$key => $value * 2] : null; + } ); + + $this->assertInstanceOf( Map::class, $m ); + $this->assertSame( ['a' => 4], $m->toArray() ); + } + + + public function testTransformOverwrite() + { + $m = Map::from( ['la' => 2, 'le' => 4, 'li' => 6] )->transform( function( $value, $key ) { + return [$key[0] => $value * 2]; + } ); + + $this->assertInstanceOf( Map::class, $m ); + $this->assertSame( ['l' => 12], $m->toArray() ); + } + + public function testTranspose() { $m = Map::from( [