Skip to content

Commit f918b11

Browse files
committed
Merge branch 'master' into 3.x
2 parents cdce8de + 0e02f46 commit f918b11

3 files changed

Lines changed: 288 additions & 2 deletions

File tree

README.md

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ will return:
195195
<a href="#isobject">isObject</a>
196196
<a href="#isnumeric">isNumeric</a>
197197
<a href="#isscalar">isScalar</a>
198+
<a href="#issole">isSole</a>
198199
<a href="#isstring">isString</a>
199200
<a href="#join">join</a>
200201
<a href="#jsonserialize">jsonSerialize</a>
@@ -237,6 +238,7 @@ will return:
237238
<a href="#rekey">rekey</a>
238239
<a href="#remove">remove</a>
239240
<a href="#replace">replace</a>
241+
<a href="#restrict">restrict</a>
240242
<a href="#reverse">reverse</a>
241243
<a href="#reversed">reversed</a>
242244
<a href="#rsort">rsort</a>
@@ -341,6 +343,7 @@ will return:
341343
* [random()](#random) : Returns random elements preserving keys
342344
* [search()](#search) : Find the key of an element
343345
* [shift()](#shift) : Returns and removes the first element
346+
* [sole()](#sole) : Returns the matching item if it's the only one
344347
* [string()](#string) : Returns an element by key and casts it to string
345348
* [to()](#to) : Returns the plain array
346349
* [toArray()](#toarray) : Returns the plain array
@@ -427,6 +430,7 @@ will return:
427430
* [pull()](#pull) : Returns and removes an element by key
428431
* [reject()](#reject) : Removes all matched elements
429432
* [remove()](#remove) : Removes an element by key
433+
* [restrict()](#restrict) : Returns only the items matching the value (and key)
430434
* [shift()](#shift) : Returns and removes the first element
431435
* [skip()](#skip) : Skips the given number of items and return the rest
432436
* [slice()](#slice) : Returns a slice of the map
@@ -455,6 +459,7 @@ will return:
455459
* [isNumeric()](#isnumeric) : Tests if all entries are numeric values
456460
* [isObject()](#isobject) : Tests if all entries are objects
457461
* [isScalar()](#isscalar) : Tests if all entries are scalar values.
462+
* [isSole()](#issole) : Returns only true if exactly one item is matching.
458463
* [isString()](#isstring) : Tests if all entries are string values.
459464
* [implements()](#implements) : Tests if all entries are objects implementing the interface
460465
* [none()](#none) : Tests if none of the elements are part of the map
@@ -3728,6 +3733,45 @@ Map::from( [[1]] )->isScalar();
37283733
* [isString()](#isstring) - Determines if all entries are string values
37293734

37303735

3736+
### isSole()
3737+
3738+
Tests for the matching item, but is true only if exactly one item is matching.
3739+
3740+
```php
3741+
public function isSole( $value = null, $key = null ) : bool
3742+
```
3743+
3744+
* @param **\Closure&#124;mixed** `$value` Closure with (item, key) parameter or element to test against
3745+
* @param **string&#124;int&#124;null** `$key` Key to compare the value to if `$value` is not a closure
3746+
* @return **bool** TRUE if exactly one item matches, FALSE if no or more than one item matches
3747+
3748+
**Examples:**
3749+
3750+
```php
3751+
Map::from( ['a', 'b'] )->isSole( 'a' );
3752+
// true
3753+
3754+
Map::from( ['a', 'b', 'a'] )->isSole( fn( $v, $k ) => $v === 'a' && $k < 2 );
3755+
// true
3756+
3757+
Map::from( [['name' => 'test'], ['name' => 'user']] )->isSole( fn( $v, $k ) => $v['name'] === 'user' );
3758+
// true
3759+
3760+
Map::from( ['b', 'c'] )->isSole( 'a' );
3761+
// false
3762+
3763+
Map::from( ['a', 'b', 'a'] )->isSole( 'a' );
3764+
// false
3765+
3766+
Map::from( [['name' => 'test'], ['name' => 'user'], ['name' => 'test']] )->isSole( 'test', 'name' );
3767+
// false
3768+
```
3769+
3770+
**See also:**
3771+
3772+
* [sole()]#sole) - Returns only the items matching the value (and key) from the map
3773+
3774+
37313775
### isString()
37323776

37333777
Determines if all entries are string values.
@@ -5091,6 +5135,43 @@ Map::from( ['a' => 1, 'b' => ['c' => 3, 'd' => 4]] )->replace( ['b' => ['c' => 9
50915135
```
50925136

50935137

5138+
### restrict()
5139+
5140+
Returns only the items matching the value (and key) from the map.
5141+
5142+
```php
5143+
public function restrict( $value = null, $key = null ) : self
5144+
```
5145+
5146+
* @param **\Closure&#124;mixed** `$value` Closure with (item, key) parameter or element to test against
5147+
* @param **string&#124;int&#124;null** `$key` Key to compare the value to if `$value` is not a closure
5148+
* @return **self&#60;int&#124;string,mixed&#62;** New map with matching items only
5149+
5150+
The keys are preserved in the returned map.
5151+
5152+
**Examples:**
5153+
5154+
```php
5155+
Map::from( ['a', 'b', 'a'] )->restrict( 'a' );
5156+
// [0 => 'a', 2 => 'a']
5157+
5158+
Map::from( [['name' => 'test'], ['name' => 'user'], ['name' => 'test']] )->restrict( 'test', 'name' );
5159+
// [0 => ['name' => 'test'], 2 => ['name' => 'test']]
5160+
5161+
Map::from( [['name' => 'test'], ['name' => 'user']] )->restrict( fn( $v, $k ) => $v['name'] === 'user' );
5162+
// [1 => ['name' => 'user']]
5163+
5164+
Map::from( ['a', 'b', 'a'] )->restrict( fn( $v, $k ) => $v === 'a' && $k < 2 );
5165+
// [0 => 'a']
5166+
```
5167+
5168+
**See also:**
5169+
5170+
* [filter()](#filter) - Applies a filter to all elements
5171+
* [only()](#only) - Returns only those elements specified by the keys
5172+
* [where()]#where) - Filters the list of elements by a given condition
5173+
5174+
50945175
### reverse()
50955176

50965177
Reverses the element order without returning a new map.
@@ -5527,6 +5608,49 @@ Map::from( [1, 2, 3, 4] )->sliding( 3, 2 );
55275608
```
55285609

55295610

5611+
### sole()
5612+
5613+
Returns the matching item, but only if one matching item exists.
5614+
5615+
```php
5616+
public function sole( $value = null, $key = null )
5617+
```
5618+
5619+
* @param **\Closure&#124;mixed** `$value` Closure with (item, key) parameter or element to test against
5620+
* @param **string&#124;int&#124;null** `$key` Key to compare the value to if `$value` is not a closure
5621+
* @return **mixed** Value from map if exactly one matching item exists
5622+
* @throws **\LengthException** If no items or more than one item is found
5623+
5624+
The keys are preserved in the returned map.
5625+
5626+
**Examples:**
5627+
5628+
```php
5629+
Map::from( ['a', 'b'] )->sole( 'a' );
5630+
// "a"
5631+
5632+
Map::from( ['a', 'b', 'a'] )->restrict( fn( $v, $k ) => $v === 'a' && $k < 2 );
5633+
// "a"
5634+
5635+
Map::from( [['name' => 'test'], ['name' => 'user']] )->restrict( fn( $v, $k ) => $v['name'] === 'user' );
5636+
// [['name' => 'user']]
5637+
5638+
Map::from( ['b', 'c'] )->sole( 'a' );
5639+
// LengthException
5640+
5641+
Map::from( ['a', 'b', 'a'] )->sole( 'a' );
5642+
// LengthException
5643+
5644+
Map::from( [['name' => 'test'], ['name' => 'user'], ['name' => 'test']] )->restrict( 'test', 'name' );
5645+
// LengthException
5646+
```
5647+
5648+
**See also:**
5649+
5650+
* [isSole()]#issole) - Tests for the matching item, but is true only if exactly one item is matching
5651+
* [restrict()]#restrict) - Returns only the items matching the value (and key) from the map
5652+
5653+
55305654
### some()
55315655

55325656
Tests if at least one element passes the test or is part of the map.
@@ -7440,7 +7564,7 @@ Returns a copy of the map with the element at the given index replaced with the
74407564
public function with( $key, $value ) : self
74417565
```
74427566

7443-
* @param **int|string** `$key` Array key to set or replace
7567+
* @param **int&#124;string** `$key` Array key to set or replace
74447568
* @param **mixed** `$value` New value for the given key
74457569
* @return **self&#60;int&#124;string,mixed&#62;** New map of the values
74467570

src/Map.php

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ public function __construct( $elements = [] )
6767
* @param string $name Method name
6868
* @param array<mixed> $params List of parameters
6969
* @return mixed Result from called function or new map with results from the element methods
70-
*
7170
* @throws \BadMethodCallException
7271
*/
7372
public static function __callStatic( string $name, array $params )
@@ -2893,6 +2892,30 @@ public function isScalar() : bool
28932892
}
28942893

28952894

2895+
/**
2896+
* Tests for the matching item, but is true only if exactly one item is matching.
2897+
*
2898+
* Examples:
2899+
* Map::from( ['a', 'b'] )->isSole( 'a' );
2900+
* Map::from( ['a', 'b', 'a'] )->isSole( fn( $v, $k ) => $v === 'a' && $k < 2 );
2901+
* Map::from( [['name' => 'test'], ['name' => 'user']] )->isSole( fn( $v, $k ) => $v['name'] === 'user' );
2902+
* Map::from( ['b', 'c'] )->isSole( 'a' );
2903+
* Map::from( ['a', 'b', 'a'] )->isSole( 'a' );
2904+
* Map::from( [['name' => 'test'], ['name' => 'user'], ['name' => 'test']] )->isSole( 'test', 'name' );
2905+
*
2906+
* Results:
2907+
* The first three examples will return TRUE while all others will return FALSE.
2908+
*
2909+
* @param \Closure|mixed $values Closure with (item, key) parameter or element to test against
2910+
* @param string|int|null $key Key to compare the value for if $values is not a closure
2911+
* @return bool TRUE if exactly one item matches, FALSE if no or more than one item matches
2912+
*/
2913+
public function isSole( $value = null, $key = null ) : bool
2914+
{
2915+
return $this->restrict( $value, $key )->count() === 1;
2916+
}
2917+
2918+
28962919
/**
28972920
* Determines if all entries are string values.
28982921
*
@@ -4121,6 +4144,39 @@ public function replace( iterable $elements, bool $recursive = true ) : self
41214144
}
41224145

41234146

4147+
/**
4148+
* Returns only the items matching the value (and key) from the map.
4149+
*
4150+
* Examples:
4151+
* Map::from( ['a', 'b', 'a'] )->restrict( 'a' );
4152+
* Map::from( [['name' => 'test'], ['name' => 'user'], ['name' => 'test']] )->restrict( 'test', 'name' );
4153+
* Map::from( [['name' => 'test'], ['name' => 'user']] )->restrict( fn( $v, $k ) => $v['name'] === 'user' );
4154+
* Map::from( ['a', 'b', 'a'] )->restrict( fn( $v, $k ) => $v === 'a' && $k < 2 );
4155+
*
4156+
* Results:
4157+
* [0 => 'a', 2 => 'a']
4158+
* [0 => ['name' => 'test'], 2 => ['name' => 'test']]
4159+
* [1 => ['name' => 'user']]
4160+
* [0 => 'a']
4161+
*
4162+
* The keys are preserved in the returned map.
4163+
*
4164+
* @param \Closure|mixed $value Closure with (item, key) parameter or element to test against
4165+
* @param string|int|null $key Key to compare the value to if $value is not a closure
4166+
* @return self<int|string,mixed> New map with matching items only
4167+
*/
4168+
public function restrict( $value = null, $key = null ) : self
4169+
{
4170+
if( !( $value instanceof \Closure ) ) {
4171+
$filter = $key === null ? fn( $v ) => $v === $value : fn( $v, $k ) => ( $v[$key] ?? null ) === $value;
4172+
} else {
4173+
$filter = $value;
4174+
}
4175+
4176+
return $this->filter( $filter );
4177+
}
4178+
4179+
41244180
/**
41254181
* Reverses the element order with keys without returning a new map.
41264182
*
@@ -4510,6 +4566,38 @@ public function sliding( int $size = 2, int $step = 1 ) : self
45104566
}
45114567

45124568

4569+
/**
4570+
* Returns the matching item, but only if one matching item exists.
4571+
*
4572+
* Examples:
4573+
* Map::from( ['a', 'b'] )->sole( 'a' );
4574+
* Map::from( ['a', 'b', 'a'] )->restrict( fn( $v, $k ) => $v === 'a' && $k < 2 );
4575+
* Map::from( [['name' => 'test'], ['name' => 'user']] )->restrict( fn( $v, $k ) => $v['name'] === 'user' );
4576+
* Map::from( ['b', 'c'] )->sole( 'a' );
4577+
* Map::from( ['a', 'b', 'a'] )->sole( 'a' );
4578+
* Map::from( [['name' => 'test'], ['name' => 'user'], ['name' => 'test']] )->restrict( 'test', 'name' );
4579+
*
4580+
* Results:
4581+
* The first two examples will return "a" while the third one will return [1 => ['name' => 'user']].
4582+
* All other examples throw a LengthException because more than one item matches the test.
4583+
*
4584+
* @param \Closure|mixed $values Closure with (item, key) parameter or element to test against
4585+
* @param string|int|null $key Key to compare the value for if $values is not a closure
4586+
* @return mixed Value from map if exactly one matching item exists
4587+
* @throws \LengthException If no items or more than one item is found
4588+
*/
4589+
public function sole( $value = null, $key = null )
4590+
{
4591+
$items = $this->restrict( $value, $key );
4592+
4593+
if( $items->count() > 1 ) {
4594+
throw new \LengthException( 'Multiple items found' );
4595+
}
4596+
4597+
return $items->first( new \LengthException( 'No items found' ) );
4598+
}
4599+
4600+
45134601
/**
45144602
* Tests if at least one element passes the test or is part of the map.
45154603
*

0 commit comments

Comments
 (0)