GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Enumerable::intersect()   A
last analyzed

Complexity

Conditions 4
Paths 1

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 1
nop 2
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Enumerable class.
5
 * @author Alexander Prokhorov
6
 * @license Simplified BSD
7
 * @link https://github.com/Athari/YaLinqo YaLinqo on GitHub
8
 */
9
10
namespace YaLinqo;
11
12
// Differences: preserving keys and toSequental, *Enum for keywords, no (el,i) overloads, string lambda args (v,k,a,b,e etc.), toArray/toList/toDictionary, objects as keys, docs copied and may be incorrect, elementAt uses key instead of index, @throws doc incomplete, aggregater default seed is null not undefined, call/each, InvalidOperationException => UnexpectedValueException
13
14
/**
15
 * A sequence of values indexed by keys, the primary class of YaLinqo.
16
 * <p>A sequence of values indexed by keys, which supports various operations: generation, projection, filtering, ordering, joining, grouping, aggregation etc.
17
 * <p>To create a Enumerable, call {@link Enumerable::from} (aliased as a global function {@link from}) or any of the generation functions. To convert to array, call {@link Enumerable::toArrayDeep} or any of the conversion functions.
18
 * @see from
19
 * @package YaLinqo
20
 */
21
class Enumerable implements \IteratorAggregate
22
{
23
    use EnumerableGeneration;
24
    use EnumerablePagination;
25
26
    /**
27
     * Wrapped iterator.
28
     * @var \Iterator
29
     */
30
    private $iterator;
31
32
    /**
33
     * @internal
34
     * @param \Closure|\Iterator $iterator
35
     * @param bool $isClosure
36
     */
37
    private function __construct($iterator, $isClosure = true)
38
    {
39
        $this->iterator = $isClosure ? $iterator() : $iterator;
40
    }
41
42
    /**
43
     * Retrieve an external iterator.
44
     * {@inheritdoc}
45
     * @return \Iterator
46
     */
47
    public function getIterator(): \Traversable
48
    {
49
        return $this->iterator;
50
    }
51
52
    /**
53
     * If the sequence wraps an array directly, return it, otherwise return null.
54
     * @return array|null Wrapped array, null otherwise.
55
     */
56
    protected function tryGetArrayCopy()
57
    {
58
        /** @var $it \Iterator|\ArrayIterator */
59
        $it = $this->iterator;
60
        return $it instanceof \ArrayIterator ? $it->getArrayCopy() : null;
61
    }
62
63
    #region Projection and filtering
64
65
    /**
66
     * Casts the elements of a sequence to the specified type.
67
     * <p><b>Syntax</b>: cast (type)
68
     * <p>The cast method causes an error if an element cannot be cast (exact error depends on the implementation of PHP casting), to get only elements of the specified type, use {@link ofType}.
69
     * @param string $type The type to cast the elements to. Can be one of the built-in types: array, int (integer, long), float (real, double), null (unset), object, string.
70
     * @return Enumerable An sequence that contains each element of the source sequence cast to the specified type.
71
     * @link http://php.net/manual/language.types.type-juggling.php Type Juggling
72
     * @package YaLinqo\Projection and filtering
73
     */
74
    public function cast(string $type): Enumerable
75
    {
76
        switch ($type) {
77
            case 'array':
78
                return $this->select(function($v) { return (array)$v; });
79
            case 'int':
80
            case 'integer':
81
            case 'long':
82
                return $this->select(function($v) { return (int)$v; });
83
            case 'float':
84
            case 'real':
85
            case 'double':
86
                return $this->select(function($v) { return (float)$v; });
87
            case 'null':
88
            case 'unset':
89
                return $this->select(function($v) { return null; });
0 ignored issues
show
Unused Code introduced by
The parameter $v is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
90
            case 'object':
91
                return $this->select(function($v) { return (object)$v; });
92
            case 'string':
93
                return $this->select(function($v) { return (string)$v; });
94
            default:
95
                throw new \InvalidArgumentException(Errors::UNSUPPORTED_BUILTIN_TYPE);
96
        }
97
    }
98
99
    /**
100
     * Filters the elements of a sequence based on a specified type.
101
     * <p><b>Syntax</b>: ofType (type)
102
     * <p>The ofType method returns only elements of the specified type. To instead receive an error if an element cannot be cast, use {@link cast}.
103
     * @param string $type The type to filter the elements of the sequence on. Can be either class name or one of the predefined types: array, int (integer, long), callable (callable), float (real, double), null, string, object, numeric, scalar.
104
     * @return Enumerable A sequence that contains elements from the input sequence of the specified type.
105
     * @package YaLinqo\Projection and filtering
106
     */
107
    public function ofType(string $type): Enumerable
108
    {
109
        switch ($type) {
110
            case 'array':
111
                return $this->where(function($v) { return is_array($v); });
112
            case 'int':
113
            case 'integer':
114
            case 'long':
115
                return $this->where(function($v) { return is_int($v); });
116
            case 'callable':
117
            case 'callback':
118
                return $this->where(function($v) { return is_callable($v); });
119
            case 'float':
120
            case 'real':
121
            case 'double':
122
                return $this->where(function($v) { return is_float($v); });
123
            case 'null':
124
                return $this->where(function($v) { return is_null($v); });
125
            case 'numeric':
126
                return $this->where(function($v) { return is_numeric($v); });
127
            case 'object':
128
                return $this->where(function($v) { return is_object($v); });
129
            case 'scalar':
130
                return $this->where(function($v) { return is_scalar($v); });
131
            case 'string':
132
                return $this->where(function($v) { return is_string($v); });
133
            default:
134
                return $this->where(function($v) use ($type) { return is_object($v) && get_class($v) === $type; });
135
        }
136
    }
137
138
    /**
139
     * Projects each element of a sequence into a new form.
140
     * <p><b>Syntax</b>: select (selectorValue {(v, k) ==> result} [, selectorKey {(v, k) ==> result}])
141
     * <p>This projection method requires the transform functions, selectorValue and selectorKey, to produce one key-value pair for each value in the source sequence. If selectorValue returns a value that is itself a collection, it is up to the consumer to traverse the subsequences manually. In such a situation, it might be better for your query to return a single coalesced sequence of values. To achieve this, use the {@link selectMany()} method instead of select. Although selectMany works similarly to select, it differs in that the transform function returns a collection that is then expanded by selectMany before it is returned.
142
     * @param callable $selectorValue {(v, k) ==> value} A transform function to apply to each value.
143
     * @param callable|null $selectorKey {(v, k) ==> key} A transform function to apply to each key. Default: key.
144
     * @return Enumerable A sequence whose elements are the result of invoking the transform functions on each element of source.
145
     * @package YaLinqo\Projection and filtering
146
     */
147
    public function select($selectorValue, $selectorKey = null): Enumerable
148
    {
149
        $selectorValue = Utils::createLambda($selectorValue, 'v,k');
150
        $selectorKey = Utils::createLambda($selectorKey, 'v,k', Functions::$key);
151
152
        return new self(function() use ($selectorValue, $selectorKey) {
153
            foreach ($this as $k => $v)
154
                yield $selectorKey($v, $k) => $selectorValue($v, $k);
155
        });
156
    }
157
158
    /**
159
     * Projects each element of a sequence to a sequence and flattens the resulting sequences into one sequence.
160
     * <p><b>Syntax</b>: selectMany ()
161
     * <p>The selectMany method enumerates the input sequence, where each element is a sequence, and then enumerates and yields the elements of each such sequence. That is, for each element of source, selectorValue and selectorKey are invoked and a sequence of key-value pairs is returned. selectMany then flattens this two-dimensional collection of collections into a one-dimensional sequence and returns it. For example, if a query uses selectMany to obtain the orders for each customer in a database, the result is a sequence of orders. If instead the query uses {@link select} to obtain the orders, the collection of collections of orders is not combined and the result is a sequence of sequences of orders.
162
     * <p><b>Syntax</b>: selectMany (collectionSelector {(v, k) ==> enum})
163
     * <p>The selectMany method enumerates the input sequence, uses transform functions to map each element to a sequence, and then enumerates and yields the elements of each such sequence.
164
     * <p><b>Syntax</b>: selectMany (collectionSelector {(v, k) ==> enum} [, resultSelectorValue {(v, k1, k2) ==> value} [, resultSelectorKey {(v, k1, k2) ==> key}]])
165
     * <p>Projects each element of a sequence to a sequence, flattens the resulting sequences into one sequence, and invokes a result selector functions on each element therein.
166
     * <p>The selectMany method is useful when you have to keep the elements of source in scope for query logic that occurs after the call to selectMany. If there is a bidirectional relationship between objects in the source sequence and objects returned from collectionSelector, that is, if a sequence returned from collectionSelector provides a property to retrieve the object that produced it, you do not need this overload of selectMany. Instead, you can use simpler selectMany overload and navigate back to the source object through the returned sequence.
167
     * @param callable $collectionSelector {(v, k) ==> enum} A transform function to apply to each element.
168
     * @param callable|null $resultSelectorValue {(v, k1, k2) ==> value} A transform function to apply to each value of the intermediate sequence. Default: {(v, k1, k2) ==> v}.
169
     * @param callable|null $resultSelectorKey {(v, k1, k2) ==> key} A transform function to apply to each key of the intermediate sequence. Default: increment.
170
     * @return Enumerable A sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence.
171
     * @package YaLinqo\Projection and filtering
172
     */
173
    public function selectMany($collectionSelector = null, $resultSelectorValue = null, $resultSelectorKey = null): Enumerable
174
    {
175
        $collectionSelector = Utils::createLambda($collectionSelector, 'v,k', Functions::$value);
176
        $resultSelectorValue = Utils::createLambda($resultSelectorValue, 'v,k1,k2', Functions::$value);
177
        $resultSelectorKey = Utils::createLambda($resultSelectorKey, 'v,k1,k2', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a callable|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
178
        if ($resultSelectorKey === false)
179
            $resultSelectorKey = Functions::increment();
180
181
        return new self(function() use ($collectionSelector, $resultSelectorValue, $resultSelectorKey) {
182
            foreach ($this as $ok => $ov)
183
                foreach ($collectionSelector($ov, $ok) as $ik => $iv)
184
                    yield $resultSelectorKey($iv, $ok, $ik) => $resultSelectorValue($iv, $ok, $ik);
185
        });
186
    }
187
188
    /**
189
     * Filters a sequence of values based on a predicate.
190
     * <p><b>Syntax</b>: where (predicate {(v, k) ==> result})
191
     * @param callable $predicate {(v, k) ==> result} A function to test each element for a condition.
192
     * @return Enumerable A sequence that contains elements from the input sequence that satisfy the condition.
193
     * @package YaLinqo\Projection and filtering
194
     */
195
    public function where($predicate): Enumerable
196
    {
197
        $predicate = Utils::createLambda($predicate, 'v,k');
198
199
        return new self(function() use ($predicate) {
200
            foreach ($this as $k => $v)
201
                if ($predicate($v, $k))
202
                    yield $k => $v;
203
        });
204
    }
205
206
    #endregion
207
208
    #region Ordering
209
210
    /**
211
     * Sorts the elements of a sequence in a particular direction (ascending, descending) according to a key.
212
     * <p><b>Syntax</b>: orderByDir (false|true [, {(v, k) ==> key} [, {(a, b) ==> diff}]])
213
     * <p>Three methods are defined to extend the type {@link OrderedEnumerable}, which is the return type of this method. These three methods, namely {@link OrderedEnumerable::thenBy thenBy}, {@link OrderedEnumerable::thenByDescending thenByDescending} and {@link OrderedEnumerable::thenByDir thenByDir}, enable you to specify additional sort criteria to sort a sequence. These methods also return an OrderedEnumerable, which means any number of consecutive calls to thenBy, thenByDescending or thenByDir can be made.
214
     * <p>Because OrderedEnumerable inherits from Enumerable, you can call {@link orderBy}, {@link orderByDescending} or {@link orderByDir} on the results of a call to orderBy, orderByDescending, orderByDir, thenBy, thenByDescending or thenByDir. Doing this introduces a new primary ordering that ignores the previously established ordering.
215
     * <p>This method performs an unstable sort; that is, if the keys of two elements are equal, the order of the elements is not preserved. In contrast, a stable sort preserves the order of elements that have the same key. Internally, {@link usort} is used.
216
     * @param int|bool $sortOrder A direction in which to order the elements: false or SORT_DESC for ascending (by increasing value), true or SORT_ASC for descending (by decreasing value).
217
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from an element. Default: value.
218
     * @param callable|int|null $comparer {(a, b) ==> diff} Difference between a and b: &lt;0 if a&lt;b; 0 if a==b; &gt;0 if a&gt;b. Can also be a combination of SORT_ flags.
219
     * @return OrderedEnumerable
220
     * @package YaLinqo\Ordering
221
     */
222
    public function orderByDir($sortOrder, $keySelector = null, $comparer = null): OrderedEnumerable
223
    {
224
        $sortFlags = Utils::lambdaToSortFlagsAndOrder($comparer, $sortOrder);
225
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
226
        $isReversed = $sortOrder == SORT_DESC;
227
        $comparer = Utils::createComparer($comparer, $sortOrder, $isReversed);
0 ignored issues
show
Bug introduced by
It seems like $sortOrder defined by parameter $sortOrder on line 222 can also be of type boolean; however, YaLinqo\Utils::createComparer() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
228
        return new OrderedEnumerable($this, $sortOrder, $sortFlags, $isReversed, $keySelector, $comparer);
0 ignored issues
show
Bug introduced by
It seems like $sortFlags defined by \YaLinqo\Utils::lambdaTo...($comparer, $sortOrder) on line 224 can also be of type callable or null; however, YaLinqo\OrderedEnumerable::__construct() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $keySelector defined by \YaLinqo\Utils::createLa...inqo\Functions::$value) on line 225 can also be of type null; however, YaLinqo\OrderedEnumerable::__construct() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $comparer defined by \YaLinqo\Utils::createCo...sortOrder, $isReversed) on line 227 can also be of type null; however, YaLinqo\OrderedEnumerable::__construct() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
229
    }
230
231
    /**
232
     * Sorts the elements of a sequence in ascending order according to a key.
233
     * <p><b>Syntax</b>: orderBy ([{(v, k) ==> key} [, {(a, b) ==> diff}]])
234
     * <p>Three methods are defined to extend the type {@link OrderedEnumerable}, which is the return type of this method. These three methods, namely {@link OrderedEnumerable::thenBy thenBy}, {@link OrderedEnumerable::thenByDescending thenByDescending} and {@link OrderedEnumerable::thenByDir thenByDir}, enable you to specify additional sort criteria to sort a sequence. These methods also return an OrderedEnumerable, which means any number of consecutive calls to thenBy, thenByDescending or thenByDir can be made.
235
     * <p>Because OrderedEnumerable inherits from Enumerable, you can call {@link orderBy}, {@link orderByDescending} or {@link orderByDir} on the results of a call to orderBy, orderByDescending, orderByDir, thenBy, thenByDescending or thenByDir. Doing this introduces a new primary ordering that ignores the previously established ordering.
236
     * <p>This method performs an unstable sort; that is, if the keys of two elements are equal, the order of the elements is not preserved. In contrast, a stable sort preserves the order of elements that have the same key. Internally, {@link usort} is used.
237
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from an element. Default: value.
238
     * @param callable|int|null $comparer {(a, b) ==> diff} Difference between a and b: &lt;0 if a&lt;b; 0 if a==b; &gt;0 if a&gt;b. Can also be a combination of SORT_ flags.
239
     * @return OrderedEnumerable
240
     * @package YaLinqo\Ordering
241
     */
242
    public function orderBy($keySelector = null, $comparer = null): OrderedEnumerable
243
    {
244
        return $this->orderByDir(false, $keySelector, $comparer);
245
    }
246
247
    /**
248
     * Sorts the elements of a sequence in descending order according to a key.
249
     * <p><b>Syntax</b>: orderByDescending ([{(v, k) ==> key} [, {(a, b) ==> diff}]])
250
     * <p>Three methods are defined to extend the type {@link OrderedEnumerable}, which is the return type of this method. These three methods, namely {@link OrderedEnumerable::thenBy thenBy}, {@link OrderedEnumerable::thenByDescending thenByDescending} and {@link OrderedEnumerable::thenByDir thenByDir}, enable you to specify additional sort criteria to sort a sequence. These methods also return an OrderedEnumerable, which means any number of consecutive calls to thenBy, thenByDescending or thenByDir can be made.
251
     * <p>Because OrderedEnumerable inherits from Enumerable, you can call {@link orderBy}, {@link orderByDescending} or {@link orderByDir} on the results of a call to orderBy, orderByDescending, orderByDir, thenBy, thenByDescending or thenByDir. Doing this introduces a new primary ordering that ignores the previously established ordering.
252
     * <p>This method performs an unstable sort; that is, if the keys of two elements are equal, the order of the elements is not preserved. In contrast, a stable sort preserves the order of elements that have the same key. Internally, {@link usort} is used.
253
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from an element. Default: value.
254
     * @param callable|int|null $comparer {(a, b) ==> diff} Difference between a and b: &lt;0 if a&lt;b; 0 if a==b; &gt;0 if a&gt;b. Can also be a combination of SORT_ flags.
255
     * @return OrderedEnumerable
256
     * @package YaLinqo\Ordering
257
     */
258
    public function orderByDescending($keySelector = null, $comparer = null): OrderedEnumerable
259
    {
260
        return $this->orderByDir(true, $keySelector, $comparer);
261
    }
262
263
    #endregion
264
265
    #region Joining and grouping
266
267
    /**
268
     * Correlates the elements of two sequences based on equality of keys and groups the results.
269
     * <p><b>Syntax</b>: groupJoin (inner [, outerKeySelector {(v, k) ==> key} [, innerKeySelector {(v, k) ==> key} [, resultSelectorValue {(v, e, k) ==> value} [, resultSelectorKey {(v, e, k) ==> key}]]]])
270
     * <p>GroupJoin produces hierarchical results, which means that elements from outer are paired with collections of matching elements from inner. GroupJoin enables you to base your results on a whole set of matches for each element of outer. If there are no correlated elements in inner for a given element of outer, the sequence of matches for that element will be empty but will still appear in the results.
271
     * <p>The resultSelectorValue and resultSelectorKey functions are called only one time for each outer element together with a collection of all the inner elements that match the outer element. This differs from the {@link join} method, in which the result selector function is invoked on pairs that contain one element from outer and one element from inner. GroupJoin preserves the order of the elements of outer, and for each element of outer, the order of the matching elements from inner.
272
     * <p>GroupJoin has no direct equivalent in traditional relational database terms. However, this method does implement a superset of inner joins and left outer joins. Both of these operations can be written in terms of a grouped join.
273
     * @param array|\Iterator|\IteratorAggregate|Enumerable $inner The second (inner) sequence to join to the first (source, outer) sequence.
274
     * @param callable|null $outerKeySelector {(v, k) ==> key} A function to extract the join key from each element of the first sequence. Default: key.
275
     * @param callable|null $innerKeySelector {(v, k) ==> key} A function to extract the join key from each element of the second sequence. Default: key.
276
     * @param callable|null $resultSelectorValue {(v, e, k) ==> value} A function to create a result value from an element from the first sequence and a collection of matching elements from the second sequence. Default: {(v, e, k) ==> array(v, e)}.
277
     * @param callable|null $resultSelectorKey {(v, e, k) ==> key} A function to create a result key from an element from the first sequence and a collection of matching elements from the second sequence. Default: {(v, e, k) ==> k} (keys returned by outerKeySelector and innerKeySelector functions).
278
     * @return Enumerable A sequence that contains elements that are obtained by performing a grouped join on two sequences.
279
     * @package YaLinqo\Joining and grouping
280
     */
281
    public function groupJoin($inner, $outerKeySelector = null, $innerKeySelector = null, $resultSelectorValue = null, $resultSelectorKey = null): Enumerable
282
    {
283
        $inner = self::from($inner);
284
        $outerKeySelector = Utils::createLambda($outerKeySelector, 'v,k', Functions::$key);
285
        $innerKeySelector = Utils::createLambda($innerKeySelector, 'v,k', Functions::$key);
286
        /** @noinspection PhpUnusedParameterInspection */
287
        $resultSelectorValue = Utils::createLambda($resultSelectorValue, 'v,e,k', function($v, $e, $k) { return [ $v, $e ]; });
288
        /** @noinspection PhpUnusedParameterInspection */
289
        $resultSelectorKey = Utils::createLambda($resultSelectorKey, 'v,e,k', function($v, $e, $k) { return $k; });
290
291
        return new self(function() use ($inner, $outerKeySelector, $innerKeySelector, $resultSelectorValue, $resultSelectorKey) {
292
            $lookup = $inner->toLookup($innerKeySelector);
293
            foreach ($this as $k => $v) {
294
                $key = $outerKeySelector($v, $k);
295
                $e = isset($lookup[$key]) ? self::from($lookup[$key]) : self::emptyEnum();
296
                yield $resultSelectorKey($v, $e, $key) => $resultSelectorValue($v, $e, $key);
297
            }
298
        });
299
    }
300
301
    /**
302
     * Correlates the elements of two sequences based on matching keys.
303
     * <p><b>Syntax</b>: join (inner [, outerKeySelector {(v, k) ==> key} [, innerKeySelector {(v, k) ==> key} [, resultSelectorValue {(v1, v2, k) ==> value} [, resultSelectorKey {(v1, v2, k) ==> key}]]]])
304
     * <p>A join refers to the operation of correlating the elements of two sources of information based on a common key. Join brings the two information sources and the keys by which they are matched together in one method call. This differs from the use of {@link selectMany}, which requires more than one method call to perform the same operation.
305
     * <p>Join preserves the order of the elements of the source, and for each of these elements, the order of the matching elements of inner.
306
     * <p>In relational database terms, the Join method implements an inner equijoin. 'Inner' means that only elements that have a match in the other sequence are included in the results. An 'equijoin' is a join in which the keys are compared for equality. A left outer join operation has no dedicated standard query operator, but can be performed by using the {@link groupJoin} method.
307
     * @param array|\Iterator|\IteratorAggregate|Enumerable $inner The sequence to join to the source sequence.
308
     * @param callable|null $outerKeySelector {(v, k) ==> key} A function to extract the join key from each element of the source sequence. Default: key.
309
     * @param callable|null $innerKeySelector {(v, k) ==> key} A function to extract the join key from each element of the second sequence. Default: key.
310
     * @param callable|null $resultSelectorValue {(v1, v2, k) ==> result} A function to create a result value from two matching elements. Default: {(v1, v2, k) ==> array(v1, v2)}.
311
     * @param callable|null $resultSelectorKey {(v1, v2, k) ==> result} A function to create a result key from two matching elements. Default: {(v1, v2, k) ==> k} (keys returned by outerKeySelector and innerKeySelector functions).
312
     * @return Enumerable
313
     * @package YaLinqo\Joining and grouping
314
     */
315
    public function join($inner, $outerKeySelector = null, $innerKeySelector = null, $resultSelectorValue = null, $resultSelectorKey = null): Enumerable
316
    {
317
        $inner = self::from($inner);
318
        $outerKeySelector = Utils::createLambda($outerKeySelector, 'v,k', Functions::$key);
319
        $innerKeySelector = Utils::createLambda($innerKeySelector, 'v,k', Functions::$key);
320
        /** @noinspection PhpUnusedParameterInspection */
321
        $resultSelectorValue = Utils::createLambda($resultSelectorValue, 'v1,v2,k', function($v1, $v2, $k) { return [ $v1, $v2 ]; });
322
        /** @noinspection PhpUnusedParameterInspection */
323
        $resultSelectorKey = Utils::createLambda($resultSelectorKey, 'v1,v2,k', function($v1, $v2, $k) { return $k; });
324
325
        return new self(function() use ($inner, $outerKeySelector, $innerKeySelector, $resultSelectorValue, $resultSelectorKey) {
326
            $lookup = $inner->toLookup($innerKeySelector);
327
            foreach ($this as $ok => $ov) {
328
                $key = $outerKeySelector($ov, $ok);
329
                if (isset($lookup[$key]))
330
                    foreach ($lookup[$key] as $iv)
331
                        yield $resultSelectorKey($ov, $iv, $key) => $resultSelectorValue($ov, $iv, $key);
332
            }
333
        });
334
    }
335
336
    /**
337
     * Groups the elements of a sequence by its keys or a specified key selector function.
338
     * <p><b>Syntax</b>: groupBy ()
339
     * <p>Groups the elements of a sequence by its keys.
340
     * <p><b>Syntax</b>: groupBy (keySelector {(v, k) ==> key})
341
     * <p>Groups the elements of a sequence according to a specified key selector function.
342
     * <p><b>Syntax</b>: groupBy (keySelector {(v, k) ==> key}, valueSelector {(v, k) ==> value})
343
     * <p>Groups the elements of a sequence according to a specified key selector function and projects the elements for each group by using a specified function.
344
     * <p><b>Syntax</b>: groupBy (keySelector {(v, k) ==> key}, valueSelector {(v, k) ==> value}, resultSelectorValue {(e, k) ==> value} [, resultSelectorKey {(e, k) ==> key}])
345
     * <p>Groups the elements of a sequence according to a specified key selector function and creates a result value from each group and its key.
346
     * <p>For all overloads except the last: the groupBy method returns a sequence of sequences, one inner sequence for each distinct key that was encountered. The outer sequence is yielded in an order based on the order of the elements in source that produced the first key of each inner sequence. Elements in a inner sequence are yielded in the order they appear in source.
347
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the key for each element. Default: key.
348
     * @param callable|null $valueSelector {(v, k) ==> value} A function to map each source element to a value in the inner sequence.
349
     * @param callable|null $resultSelectorValue {(e, k) ==> value} A function to create a result value from each group.
350
     * @param callable|null $resultSelectorKey {(e, k) ==> key} A function to create a result key from each group.
351
     * @return Enumerable A sequence of sequences indexed by a key.
352
     * @package YaLinqo\Joining and grouping
353
     */
354
    public function groupBy($keySelector = null, $valueSelector = null, $resultSelectorValue = null, $resultSelectorKey = null): Enumerable
355
    {
356
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$key);
357
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
358
        $resultSelectorValue = Utils::createLambda($resultSelectorValue, 'e,k', Functions::$value);
359
        $resultSelectorKey = Utils::createLambda($resultSelectorKey, 'e,k', Functions::$key);
360
361
        return self::from($this->toLookup($keySelector, $valueSelector))
362
            ->select($resultSelectorValue, $resultSelectorKey);
0 ignored issues
show
Bug introduced by
It seems like $resultSelectorValue defined by \YaLinqo\Utils::createLa...inqo\Functions::$value) on line 358 can also be of type null; however, YaLinqo\Enumerable::select() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
363
    }
364
365
    #endregion
366
367
    #region Aggregation
368
369
    /**
370
     * Applies an accumulator function over a sequence. If seed is not null, its value is used as the initial accumulator value.
371
     * <p><b>Syntax</b>: aggregate (func {(a, v, k) ==> accum} [, seed])
372
     * <p>Aggregate method makes it simple to perform a calculation over a sequence of values. This method works by calling func one time for each element in source. Each time func is called, aggregate passes both the element from the sequence and an aggregated value (as the first argument to func). If seed is null, the first element of source is used as the initial aggregate value. The result of func replaces the previous aggregated value. Aggregate returns the final result of func.
373
     * <p>To simplify common aggregation operations, the standard query operators also include a general purpose count method, {@link count}, and four numeric aggregation methods, namely {@link min}, {@link max}, {@link sum}, and {@link average}.
374
     * @param callable $func {(a, v, k) ==> accum} An accumulator function to be invoked on each element.
375
     * @param mixed $seed If seed is not null, the first element is used as seed. Default: null.
376
     * @throws \UnexpectedValueException If seed is null and sequence contains no elements.
377
     * @return mixed The final accumulator value.
378
     * @package YaLinqo\Aggregation
379
     */
380
    public function aggregate($func, $seed = null)
381
    {
382
        $func = Utils::createLambda($func, 'a,v,k');
383
384
        $result = $seed;
385
        if ($seed !== null) {
386
            foreach ($this as $k => $v) {
387
                $result = $func($result, $v, $k);
388
            }
389
        }
390
        else {
391
            $assigned = false;
392
            foreach ($this as $k => $v) {
393
                if ($assigned) {
394
                    $result = $func($result, $v, $k);
395
                }
396
                else {
397
                    $result = $v;
398
                    $assigned = true;
399
                }
400
            }
401
            if (!$assigned)
402
                throw new \UnexpectedValueException(Errors::NO_ELEMENTS);
403
        }
404
        return $result;
405
    }
406
407
    /**
408
     * Applies an accumulator function over a sequence. If seed is not null, its value is used as the initial accumulator value.
409
     * <p>aggregateOrDefault (func {(a, v, k) ==> accum} [, seed [, default]])
410
     * <p>Aggregate method makes it simple to perform a calculation over a sequence of values. This method works by calling func one time for each element in source. Each time func is called, aggregate passes both the element from the sequence and an aggregated value (as the first argument to func). If seed is null, the first element of source is used as the initial aggregate value. The result of func replaces the previous aggregated value. Aggregate returns the final result of func. If source sequence is empty, default is returned.
411
     * <p>To simplify common aggregation operations, the standard query operators also include a general purpose count method, {@link count}, and four numeric aggregation methods, namely {@link min}, {@link max}, {@link sum}, and {@link average}.
412
     * @param callable $func {(a, v, k) ==> accum} An accumulator function to be invoked on each element.
413
     * @param mixed $seed If seed is not null, the first element is used as seed. Default: null.
414
     * @param mixed $default Value to return if sequence is empty. Default: null.
415
     * @return mixed The final accumulator value, or default if sequence is empty.
416
     * @package YaLinqo\Aggregation
417
     */
418
    public function aggregateOrDefault($func, $seed = null, $default = null)
419
    {
420
        $func = Utils::createLambda($func, 'a,v,k');
421
        $result = $seed;
422
        $assigned = false;
423
424
        if ($seed !== null) {
425
            foreach ($this as $k => $v) {
426
                $result = $func($result, $v, $k);
427
                $assigned = true;
428
            }
429
        }
430
        else {
431
            foreach ($this as $k => $v) {
432
                if ($assigned) {
433
                    $result = $func($result, $v, $k);
434
                }
435
                else {
436
                    $result = $v;
437
                    $assigned = true;
438
                }
439
            }
440
        }
441
        return $assigned ? $result : $default;
442
    }
443
444
    /**
445
     * Computes the average of a sequence of numeric values.
446
     * <p><b>Syntax</b>: average ()
447
     * <p>Computes the average of a sequence of numeric values.
448
     * <p><b>Syntax</b>: average (selector {(v, k) ==> result})
449
     * <p>Computes the average of a sequence of numeric values that are obtained by invoking a transform function on each element of the input sequence.
450
     * @param callable|null $selector {(v, k) ==> result} A transform function to apply to each element. Default: value.
451
     * @throws \UnexpectedValueException If sequence contains no elements.
452
     * @return number The average of the sequence of values.
453
     * @package YaLinqo\Aggregation
454
     */
455
    public function average($selector = null)
456
    {
457
        $selector = Utils::createLambda($selector, 'v,k', Functions::$value);
458
        $sum = $count = 0;
459
460
        foreach ($this as $k => $v) {
461
            $sum += $selector($v, $k);
462
            $count++;
463
        }
464
        if ($count === 0)
465
            throw new \UnexpectedValueException(Errors::NO_ELEMENTS);
466
        return $sum / $count;
467
    }
468
469
    /**
470
     * Returns the number of elements in a sequence.
471
     * <p><b>Syntax</b>: count ()
472
     * <p>If source iterator implements {@link Countable}, that implementation is used to obtain the count of elements. Otherwise, this method determines the count.
473
     * <p><b>Syntax</b>: count (predicate {(v, k) ==> result})
474
     * <p>Returns a number that represents how many elements in the specified sequence satisfy a condition.
475
     * @param callable|null $predicate {(v, k) ==> result} A function to test each element for a condition. Default: null.
476
     * @return int The number of elements in the input sequence.
477
     * @package YaLinqo\Aggregation
478
     */
479
    public function count($predicate = null): int
480
    {
481
        $it = $this->getIterator();
482
483
        if ($it instanceof \Countable && $predicate === null)
484
            return count($it);
485
486
        $predicate = Utils::createLambda($predicate, 'v,k', Functions::$value);
487
        $count = 0;
488
489
        foreach ($it as $k => $v)
490
            if ($predicate($v, $k))
491
                $count++;
492
        return $count;
493
    }
494
495
    /**
496
     * Returns the maximum value in a sequence of values.
497
     * <p><b>Syntax</b>: max ()
498
     * <p>Returns the maximum value in a sequence of values.
499
     * <p><b>Syntax</b>: max (selector {(v, k) ==> value})
500
     * <p>Invokes a transform function on each element of a sequence and returns the maximum value.
501
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
502
     * @throws \UnexpectedValueException If sequence contains no elements.
503
     * @return number The maximum value in the sequence.
504
     * @package YaLinqo\Aggregation
505
     */
506
    public function max($selector = null)
507
    {
508
        $selector = Utils::createLambda($selector, 'v,k', Functions::$value);
509
510
        $max = -PHP_INT_MAX;
511
        $assigned = false;
512
        foreach ($this as $k => $v) {
513
            $max = max($max, $selector($v, $k));
514
            $assigned = true;
515
        }
516
        if (!$assigned)
517
            throw new \UnexpectedValueException(Errors::NO_ELEMENTS);
518
        return $max;
519
    }
520
521
    /**
522
     * Returns the maximum value in a sequence of values, using specified comparer.
523
     * <p><b>Syntax</b>: maxBy (comparer {(a, b) ==> diff})
524
     * <p>Returns the maximum value in a sequence of values, using specified comparer.
525
     * <p><b>Syntax</b>: maxBy (comparer {(a, b) ==> diff}, selector {(v, k) ==> value})
526
     * <p>Invokes a transform function on each element of a sequence and returns the maximum value, using specified comparer.
527
     * @param callable $comparer {(a, b) ==> diff} Difference between a and b: &lt;0 if a&lt;b; 0 if a==b; &gt;0 if a&gt;b
528
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
529
     * @throws \UnexpectedValueException If sequence contains no elements.
530
     * @return number The maximum value in the sequence.
531
     * @package YaLinqo\Aggregation
532
     */
533
    public function maxBy($comparer, $selector = null)
534
    {
535
        $comparer = Utils::createLambda($comparer, 'a,b', Functions::$compareStrict);
536
        $enum = $this;
537
538
        if ($selector !== null)
539
            $enum = $enum->select($selector);
540
        return $enum->aggregate(function($a, $b) use ($comparer) { return $comparer($a, $b) > 0 ? $a : $b; });
541
    }
542
543
    /**
544
     * Returns the minimum value in a sequence of values.
545
     * <p><b>Syntax</b>: min ()
546
     * <p>Returns the minimum value in a sequence of values.
547
     * <p><b>Syntax</b>: min (selector {(v, k) ==> value})
548
     * <p>Invokes a transform function on each element of a sequence and returns the minimum value.
549
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
550
     * @throws \UnexpectedValueException If sequence contains no elements.
551
     * @return number The minimum value in the sequence.
552
     * @package YaLinqo\Aggregation
553
     */
554
    public function min($selector = null)
555
    {
556
        $selector = Utils::createLambda($selector, 'v,k', Functions::$value);
557
558
        $min = PHP_INT_MAX;
559
        $assigned = false;
560
        foreach ($this as $k => $v) {
561
            $min = min($min, $selector($v, $k));
562
            $assigned = true;
563
        }
564
        if (!$assigned)
565
            throw new \UnexpectedValueException(Errors::NO_ELEMENTS);
566
        return $min;
567
    }
568
569
    /**
570
     * Returns the minimum value in a sequence of values, using specified comparer.
571
     * <p><b>Syntax</b>: minBy (comparer {(a, b) ==> diff})
572
     * <p>Returns the minimum value in a sequence of values, using specified comparer.
573
     * <p><b>Syntax</b>: minBy (comparer {(a, b) ==> diff}, selector {(v, k) ==> value})
574
     * <p>Invokes a transform function on each element of a sequence and returns the minimum value, using specified comparer.
575
     * @param callable $comparer {(a, b) ==> diff} Difference between a and b: &lt;0 if a&lt;b; 0 if a==b; &gt;0 if a&gt;b
576
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
577
     * @throws \UnexpectedValueException If sequence contains no elements.
578
     * @return number The minimum value in the sequence.
579
     * @package YaLinqo\Aggregation
580
     */
581
    public function minBy($comparer, $selector = null)
582
    {
583
        $comparer = Utils::createLambda($comparer, 'a,b', Functions::$compareStrict);
584
        $enum = $this;
585
586
        if ($selector !== null)
587
            $enum = $enum->select($selector);
588
        return $enum->aggregate(function($a, $b) use ($comparer) { return $comparer($a, $b) < 0 ? $a : $b; });
589
    }
590
591
    /**
592
     * Computes the sum of a sequence of values.
593
     * <p><b>Syntax</b>: sum ()
594
     * <p>Computes the sum of a sequence of values.
595
     * <p><b>Syntax</b>: sum (selector {(v, k) ==> result})
596
     * <p>Computes the sum of the sequence of values that are obtained by invoking a transform function on each element of the input sequence.
597
     * <p>This method returns zero if source contains no elements.
598
     * @param callable|null $selector {(v, k) ==> result} A transform function to apply to each element.
599
     * @return number The sum of the values in the sequence.
600
     * @package YaLinqo\Aggregation
601
     */
602
    public function sum($selector = null)
603
    {
604
        $selector = Utils::createLambda($selector, 'v,k', Functions::$value);
605
606
        $sum = 0;
607
        foreach ($this as $k => $v)
608
            $sum += $selector($v, $k);
609
        return $sum;
610
    }
611
612
    #endregion
613
614
    #region Sets
615
616
    /**
617
     * Determines whether all elements of a sequence satisfy a condition.
618
     * <p><b>Syntax</b>: all (predicate {(v, k) ==> result})
619
     * <p>Determines whether all elements of a sequence satisfy a condition. The enumeration of source is stopped as soon as the result can be determined.
620
     * @param callable $predicate {(v, k) ==> result} A function to test each element for a condition.
621
     * @return bool true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false.
622
     * @package YaLinqo\Sets
623
     */
624
    public function all($predicate): bool
625
    {
626
        $predicate = Utils::createLambda($predicate, 'v,k');
627
628
        foreach ($this as $k => $v) {
629
            if (!$predicate($v, $k))
630
                return false;
631
        }
632
        return true;
633
    }
634
635
    /**
636
     * Determines whether a sequence contains any elements.
637
     * <p><b>Syntax</b>: any ()
638
     * <p>Determines whether a sequence contains any elements. The enumeration of source is stopped as soon as the result can be determined.
639
     * <p><b>Syntax</b>: any (predicate {(v, k) ==> result})
640
     * <p>Determines whether any element of a sequence exists or satisfies a condition. The enumeration of source is stopped as soon as the result can be determined.
641
     * @param callable|null $predicate {(v, k) ==> result} A function to test each element for a condition. Default: null.
642
     * @return bool If predicate is null: true if the source sequence contains any elements; otherwise, false. If predicate is not null: true if any elements in the source sequence pass the test in the specified predicate; otherwise, false.
643
     * @package YaLinqo\Sets
644
     */
645
    public function any($predicate = null): bool
646
    {
647
        $predicate = Utils::createLambda($predicate, 'v,k', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a callable|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
648
649
        if ($predicate) {
650
            foreach ($this as $k => $v) {
651
                if ($predicate($v, $k))
652
                    return true;
653
            }
654
            return false;
655
        }
656
        else {
657
            $it = $this->getIterator();
658
            if ($it instanceof \Countable)
659
                return count($it) > 0;
660
            $it->rewind();
661
            return $it->valid();
662
        }
663
    }
664
665
    /**
666
     * Appends a value to the end of the sequence.
667
     * <p><b>Syntax</b>: append (other, value)
668
     * <p>Appends a value to the end of the sequence with null key.
669
     * <p><b>Syntax</b>: append (other, value, key)
670
     * <p>Appends a value to the end of the sequence with the specified key.
671
     * @param mixed $value The value to append.
672
     * @param mixed $key The key of the value to append.
673
     * @return Enumerable A new sequence that ends with the value.
674
     * @package YaLinqo\Sets
675
     */
676
    public function append($value, $key = null): Enumerable
677
    {
678
        return new self(function() use ($value, $key) {
679
            yield from $this;
680
            yield $key => $value;
681
        });
682
    }
683
684
    /**
685
     * Concatenates two sequences.
686
     * <p>This method differs from the {@link union} method because the concat method returns all the original elements in the input sequences. The union method returns only unique elements.
687
     * <p><b>Syntax</b>: concat (other)
688
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other The sequence to concatenate to the source sequence.
689
     * @return Enumerable A sequence that contains the concatenated elements of the two input sequences.
690
     * @package YaLinqo\Sets
691
     */
692
    public function concat($other): Enumerable
693
    {
694
        $other = self::from($other);
695
696
        return new self(function() use ($other) {
697
            yield from $this;
698
            yield from $other;
699
        });
700
    }
701
702
    /**
703
     * Determines whether a sequence contains a specified element.
704
     * <p><b>Syntax</b>: contains (value)
705
     * <p>Determines whether a sequence contains a specified element. Enumeration is terminated as soon as a matching element is found.
706
     * @param $value mixed The value to locate in the sequence.
707
     * @return bool true if the source sequence contains an element that has the specified value; otherwise, false.
708
     * @package YaLinqo\Sets
709
     */
710
    public function contains($value): bool
711
    {
712
        foreach ($this as $v) {
713
            if ($v === $value)
714
                return true;
715
        }
716
        return false;
717
    }
718
719
    /**
720
     * Returns distinct elements from a sequence.
721
     * <p>Element keys are values identifying elements. They are used as array keys and are subject to the same rules as array keys, for example, integer 100 and string "100" are considered equal.
722
     * <p><b>Syntax</b>: distinct ()
723
     * <p>Returns distinct elements from a sequence using values as element keys.
724
     * <p><b>Syntax</b>: distinct (keySelector {(v, k) ==> value})
725
     * <p>Returns distinct elements from a sequence using values produced by keySelector as element keys.
726
     * @param callable|null $keySelector {(v, k) ==> value} A function to extract the element key from each element. Default: value.
727
     * @return Enumerable A sequence that contains distinct elements of the input sequence.
728
     * @package YaLinqo\Sets
729
     */
730
    public function distinct($keySelector = null): Enumerable
731
    {
732
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
733
734
        return new self(function() use ($keySelector) {
735
            $set = [];
736
            foreach ($this as $k => $v) {
737
                $key = $keySelector($v, $k);
738
                if (isset($set[$key]))
739
                    continue;
740
                $set[$key] = true;
741
                yield $k => $v;
742
            }
743
        });
744
    }
745
746
    /**
747
     * Produces the set difference of two sequences. The set difference of two sets is defined as the members of the first set that do not appear in the second set.
748
     * <p>Element keys are values identifying elements. They are used as array keys and are subject to the same rules as array keys, for example, integer 100 and string "100" are considered equal.
749
     * <p><b>Syntax</b>: distinct (other)
750
     * <p>Produces the set difference of two sequences using values as element keys.
751
     * <p><b>Syntax</b>: distinct (other, keySelector {(v, k) ==> value})
752
     * <p>Produces the set difference of two sequences using values produced by keySelector as element keys.
753
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other A sequence whose elements that also occur in the source sequence will cause those elements to be removed from the returned sequence.
754
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the element key from each element. Default: value.
755
     * @return Enumerable A sequence that contains the set difference of the elements of two sequences.
756
     * @package YaLinqo\Sets
757
     */
758
    public function except($other, $keySelector = null): Enumerable
759
    {
760
        $other = self::from($other);
761
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
762
763
        return new self(function() use ($other, $keySelector) {
764
            $set = [];
765
            foreach ($other as $k => $v) {
766
                $key = $keySelector($v, $k);
767
                $set[$key] = true;
768
            }
769
            foreach ($this as $k => $v) {
770
                $key = $keySelector($v, $k);
771
                if (isset($set[$key]))
772
                    continue;
773
                $set[$key] = true;
774
                yield $k => $v;
775
            }
776
        });
777
    }
778
779
    /**
780
     * Produces the set intersection of two sequences. The intersection of two sets is defined as the set that contains all the elements of the first set that also appear in the second set, but no other elements.
781
     * <p>Element keys are values identifying elements. They are used as array keys and are subject to the same rules as array keys, for example, integer 100 and string "100" are considered equal.
782
     * <p><b>Syntax</b>: intersect (other)
783
     * <p>Produces the set intersection of two sequences using values as element keys.
784
     * <p><b>Syntax</b>: intersect (other, keySelector {(v, k) ==> value})
785
     * <p>Produces the set intersection of two sequences using values produced by keySelector as element keys.
786
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other A sequence whose distinct elements that also appear in the first sequence will be returned.
787
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the element key from each element. Default: value.
788
     * @return Enumerable A sequence that contains the elements that form the set intersection of two sequences.
789
     * @package YaLinqo\Sets
790
     */
791
    public function intersect($other, $keySelector = null): Enumerable
792
    {
793
        $other = self::from($other);
794
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
795
796
        return new self(function() use ($other, $keySelector) {
797
            $set = [];
798
            foreach ($other as $k => $v) {
799
                $key = $keySelector($v, $k);
800
                $set[$key] = true;
801
            }
802
            foreach ($this as $k => $v) {
803
                $key = $keySelector($v, $k);
804
                if (!isset($set[$key]))
805
                    continue;
806
                unset($set[$key]);
807
                yield $k => $v;
808
            }
809
        });
810
    }
811
812
    /**
813
     * Adds a value to the beginning of the sequence.
814
     * <p><b>Syntax</b>: prepend (other, value)
815
     * <p>Adds a value to the beginning of the sequence with null key.
816
     * <p><b>Syntax</b>: prepend (other, value, key)
817
     * <p>Adds a value to the beginning of the sequence with the specified key.
818
     * @param mixed $value The value to prepend.
819
     * @param mixed $key The key of the value to prepend.
820
     * @return Enumerable A new sequence that begins with the value.
821
     * @package YaLinqo\Sets
822
     */
823
    public function prepend($value, $key = null): Enumerable
824
    {
825
        return new self(function() use ($value, $key) {
826
            yield $key => $value;
827
            yield from $this;
828
        });
829
    }
830
831
    /**
832
     * Produces the set union of two sequences.
833
     * <p>Element keys are values identifying elements. They are used as array keys and are subject to the same rules as array keys, for example, integer 100 and string "100" are considered equal.
834
     * <p>This method excludes duplicates from the return set. This is different behavior to the {@link concat} method, which returns all the elements in the input sequences including duplicates.
835
     * <p><b>Syntax</b>: union (other)
836
     * <p>Produces the set union of two sequences using values as element keys.
837
     * <p><b>Syntax</b>: union (other, keySelector {(v, k) ==> value})
838
     * <p>Produces the set union of two sequences using values produced by keySelector as element keys.
839
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other A sequence whose distinct elements form the second set for the union.
840
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the element key from each element. Default: value.
841
     * @return Enumerable A sequence that contains the elements from both input sequences, excluding duplicates.
842
     * @package YaLinqo\Sets
843
     */
844
    public function union($other, $keySelector = null): Enumerable
845
    {
846
        $other = self::from($other);
847
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
848
849
        return new self(function() use ($other, $keySelector) {
850
            $set = [];
851
            foreach ($this as $k => $v) {
852
                $key = $keySelector($v, $k);
853
                if (isset($set[$key]))
854
                    continue;
855
                $set[$key] = true;
856
                yield $k => $v;
857
            }
858
            foreach ($other as $k => $v) {
859
                $key = $keySelector($v, $k);
860
                if (isset($set[$key]))
861
                    continue;
862
                $set[$key] = true;
863
                yield $k => $v;
864
            }
865
        });
866
    }
867
868
    #endregion
869
870
    #region Conversion
871
872
    /**
873
     * Creates an array from a sequence.
874
     * <p><b>Syntax</b>: toArray ()
875
     * <p>The toArray method forces immediate query evaluation and returns an array that contains the query results.
876
     * <p>The toArray method does not traverse into elements of the sequence, only the sequence itself is converted. That is, if elements of the sequence are {@link Traversable} or arrays containing Traversable values, they will remain as is. To traverse deeply, you can use {@link toArrayDeep} method.
877
     * <p>Keys from the sequence are preserved. If the source sequence contains multiple values with the same key, the result array will only contain the latter value. To discard keys, you can use {@link toList} method. To preserve all values and keys, you can use {@link toLookup} method.
878
     * @return array An array that contains the elements from the input sequence.
879
     * @package YaLinqo\Conversion
880
     */
881
    public function toArray(): array
882
    {
883
        /** @var $it \Iterator|\ArrayIterator */
884
        $it = $this->getIterator();
885
        if ($it instanceof \ArrayIterator)
886
            return $it->getArrayCopy();
887
888
        $array = [];
889
        foreach ($it as $k => $v)
890
            $array[$k] = $v;
891
        return $array;
892
    }
893
894
    /**
895
     * Creates an array from a sequence, traversing deeply.
896
     * <p><b>Syntax</b>: toArrayDeep ()
897
     * <p>The toArrayDeep method forces immediate query evaluation and returns an array that contains the query results.
898
     * <p>The toArrayDeep method traverses into elements of the sequence. That is, if elements of the sequence are {@link Traversable} or arrays containing Traversable values, they will be converted to arrays too. To convert only the sequence itself, you can use {@link toArray} method.
899
     * <p>Keys from the sequence are preserved. If the source sequence contains multiple values with the same key, the result array will only contain the latter value. To discard keys, you can use {@link toListDeep} method. To preserve all values and keys, you can use {@link toLookup} method.
900
     * @return array An array that contains the elements from the input sequence.
901
     * @package YaLinqo\Conversion
902
     */
903
    public function toArrayDeep(): array
904
    {
905
        return $this->toArrayDeepProc($this);
906
    }
907
908
    /**
909
     * Proc for {@link toArrayDeep}.
910
     * @param $enum \Traversable Source sequence.
911
     * @return array An array that contains the elements from the input sequence.
912
     * @package YaLinqo\Conversion
913
     */
914
    protected function toArrayDeepProc($enum): array
915
    {
916
        $array = [];
917
        foreach ($enum as $k => $v)
918
            $array[$k] = $v instanceof \Traversable || is_array($v) ? $this->toArrayDeepProc($v) : $v;
919
        return $array;
920
    }
921
922
    /**
923
     * Creates an array from a sequence, with sequental integer keys.
924
     * <p><b>Syntax</b>: toList ()
925
     * <p>The toList method forces immediate query evaluation and returns an array that contains the query results.
926
     * <p>The toList method does not traverse into elements of the sequence, only the sequence itself is converted. That is, if elements of the sequence are {@link Traversable} or arrays containing Traversable values, they will remain as is. To traverse deeply, you can use {@link toListDeep} method.
927
     * <p>Keys from the sequence are discarded. To preserve keys and lose values with the same keys, you can use {@link toArray} method. To preserve all values and keys, you can use {@link toLookup} method.
928
     * @return array An array that contains the elements from the input sequence.
929
     * @package YaLinqo\Conversion
930
     */
931
    public function toList(): array
932
    {
933
        /** @var $it \Iterator|\ArrayIterator */
934
        $it = $this->getIterator();
935
        if ($it instanceof \ArrayIterator)
936
            return array_values($it->getArrayCopy());
937
938
        $array = [];
939
        foreach ($it as $v)
940
            $array[] = $v;
941
        return $array;
942
    }
943
944
    /**
945
     * Creates an array from a sequence, with sequental integer keys.
946
     * <p><b>Syntax</b>: toListDeep ()
947
     * <p>The toListDeep method forces immediate query evaluation and returns an array that contains the query results.
948
     * <p>The toListDeep method traverses into elements of the sequence. That is, if elements of the sequence are {@link Traversable} or arrays containing Traversable values, they will be converted to arrays too. To convert only the sequence itself, you can use {@link toList} method.
949
     * <p>Keys from the sequence are discarded. To preserve keys and lose values with the same keys, you can use {@link toArrayDeep} method. To preserve all values and keys, you can use {@link toLookup} method.
950
     * @return array An array that contains the elements from the input sequence.
951
     * @package YaLinqo\Conversion
952
     */
953
    public function toListDeep(): array
954
    {
955
        return $this->toListDeepProc($this);
956
    }
957
958
    /**
959
     * Proc for {@link toListDeep}.
960
     * @param $enum \Traversable Source sequence.
961
     * @return array An array that contains the elements from the input sequence.
962
     * @package YaLinqo\Conversion
963
     */
964
    protected function toListDeepProc($enum): array
965
    {
966
        $array = [];
967
        foreach ($enum as $v)
968
            $array[] = $v instanceof \Traversable || is_array($v) ? $this->toListDeepProc($v) : $v;
969
        return $array;
970
    }
971
972
    /**
973
     * Creates an array from a sequence according to specified key selector and value selector functions.
974
     * <p><b>Syntax</b>: toDictionary ([keySelector {(v, k) ==> key} [, valueSelector {(v, k) ==> value}]])
975
     * <p>The toDictionary method returns an array, a one-to-one dictionary that maps keys to values. If the source sequence contains multiple values with the same key, the result array will only contain the latter value.
976
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from each element. Default: key.
977
     * @param callable|null $valueSelector {(v, k) ==> value} A transform function to produce a result value from each element. Default: value.
978
     * @return array An array that contains keys and values selected from the input sequence.
979
     * @package YaLinqo\Conversion
980
     */
981
    public function toDictionary($keySelector = null, $valueSelector = null): array
982
    {
983
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$key);
984
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
985
986
        $dic = [];
987
        foreach ($this as $k => $v)
988
            $dic[$keySelector($v, $k)] = $valueSelector($v, $k);
989
        return $dic;
990
    }
991
992
    /**
993
     * Returns a string containing the JSON representation of sequence (converted to array).
994
     * <p><b>Syntax</b>: toJSON ([options])
995
     * <p>This function only works with UTF-8 encoded data.
996
     * @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_UNESCAPED_UNICODE. Default: 0.
997
     * @return string A JSON encoded string on success or false on failure.
998
     * @see json_encode
999
     * @package YaLinqo\Conversion
1000
     */
1001
    public function toJSON(int $options = 0): string
1002
    {
1003
        return json_encode($this->toArrayDeep(), $options);
1004
    }
1005
1006
    /**
1007
     * Creates an array from a sequence according to specified key selector and value selector functions.
1008
     * <p><b>Syntax</b>: toLookup ([keySelector {(v, k) ==> key} [, valueSelector {(v, k) ==> value}]])
1009
     * <p>The toLookup method returns an array, a one-to-many dictionary that maps keys to arrays of values.
1010
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from each element. Default: key.
1011
     * @param callable|null $valueSelector {(v, k) ==> value} A transform function to produce a result value from each element. Default: value.
1012
     * @return array An array that contains keys and value arrays selected from the input sequence.
1013
     * @package YaLinqo\Conversion
1014
     */
1015
    public function toLookup($keySelector = null, $valueSelector = null): array
1016
    {
1017
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$key);
1018
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
1019
1020
        $lookup = [];
1021
        foreach ($this as $k => $v)
1022
            $lookup[$keySelector($v, $k)][] = $valueSelector($v, $k);
1023
        return $lookup;
1024
    }
1025
1026
    /**
1027
     * Returns a sequence of keys from the source sequence.
1028
     * <p><b>Syntax</b>: toKeys ()
1029
     * @return Enumerable A sequence with keys from the source sequence as values and sequental integers as keys.
1030
     * @see array_keys
1031
     * @package YaLinqo\Conversion
1032
     */
1033
    public function toKeys(): Enumerable
1034
    {
1035
        return $this->select(Functions::$key, Functions::increment());
1036
    }
1037
1038
    /**
1039
     * Returns a sequence of values from the source sequence; keys are discarded.
1040
     * <p><b>Syntax</b>: toValues ()
1041
     * @return Enumerable A sequence with the same values and sequental integers as keys.
1042
     * @see array_values
1043
     * @package YaLinqo\Conversion
1044
     */
1045
    public function toValues(): Enumerable
1046
    {
1047
        return $this->select(Functions::$value, Functions::increment());
1048
    }
1049
1050
    /**
1051
     * Transform the sequence to an object.
1052
     * <p><b>Syntax</b>: toObject ([propertySelector {(v, k) ==> name} [, valueSelector {(v, k) ==> value}]])
1053
     * @param callable|null $propertySelector {(v, k) ==> name} A function to extract a property name from an element. Must return a valid PHP identifier. Default: key.
1054
     * @param callable|null $valueSelector {(v, k) ==> value} A function to extract a property value from an element. Default: value.
1055
     * @return \stdClass
1056
     * @package YaLinqo\Conversion
1057
     */
1058
    public function toObject($propertySelector = null, $valueSelector = null): \stdClass
1059
    {
1060
        $propertySelector = Utils::createLambda($propertySelector, 'v,k', Functions::$key);
1061
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
1062
1063
        $obj = new \stdClass();
1064
        foreach ($this as $k => $v)
1065
            $obj->{$propertySelector($v, $k)} = $valueSelector($v, $k);
1066
        return $obj;
1067
    }
1068
1069
    /**
1070
     * Returns a string containing a string representation of all the sequence values, with the separator string between each element.
1071
     * <p><b>Syntax</b>: toString ([separator [, selector]])
1072
     * @param string $separator A string separating values in the result string. Default: ''.
1073
     * @param callable|null $valueSelector {(v, k) ==> value} A transform function to apply to each element. Default: value.
1074
     * @return string
1075
     * @see implode
1076
     * @package YaLinqo\Conversion
1077
     */
1078
    public function toString(string $separator = '', $valueSelector = null): string
1079
    {
1080
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a callable|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1081
        $array = $valueSelector ? $this->select($valueSelector)->toList() : $this->toList();
1082
        return implode($separator, $array);
1083
    }
1084
1085
    #endregion
1086
1087
    #region Actions
1088
1089
    /**
1090
     * Invokes an action for each element in the sequence.
1091
     * <p><b>Syntax</b>: process (action {(v, k) ==> void})
1092
     * <p>Process method does not start enumeration itself. To force enumeration, you can use {@link each} method.
1093
     * <p>Original LINQ method name: do.
1094
     * @param callable $action {(v, k) ==> void} The action to invoke for each element in the sequence.
1095
     * @return Enumerable The source sequence with the side-effecting behavior applied.
1096
     * @package YaLinqo\Actions
1097
     */
1098
    public function call($action): Enumerable
1099
    {
1100
        $action = Utils::createLambda($action, 'v,k');
1101
1102
        return new self(function() use ($action) {
1103
            foreach ($this as $k => $v) {
1104
                $action($v, $k);
1105
                yield $k => $v;
1106
            }
1107
        });
1108
    }
1109
1110
    /**
1111
     * Invokes an action for each element in the sequence.
1112
     * <p><b>Syntax</b>: each (action {(v, k) ==> void})
1113
     * <p>Each method forces enumeration. To just add side-effect without enumerating, you can use {@link process} method.
1114
     * <p>Original LINQ method name: foreach.
1115
     * @param callable $action {(v, k) ==> void} The action to invoke for each element in the sequence.
1116
     * @package YaLinqo\Actions
1117
     */
1118
    public function each($action = null)
1119
    {
1120
        $action = Utils::createLambda($action, 'v,k', Functions::$blank);
1121
1122
        foreach ($this as $k => $v)
1123
            $action($v, $k);
1124
    }
1125
1126
    /**
1127
     * Output the result of calling {@link toString} method.
1128
     * <p><b>Syntax</b>: write ([separator [, selector]])
1129
     * @param string $separator A string separating values in the result string. Default: ''.
1130
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
1131
     * @see implode, echo
1132
     * @package YaLinqo\Actions
1133
     */
1134
    public function write(string $separator = '', $selector = null)
1135
    {
1136
        echo $this->toString($separator, $selector);
1137
    }
1138
1139
    /**
1140
     * Output all the sequence values, with a new line after each element.
1141
     * <p><b>Syntax</b>: writeLine ([selector])
1142
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
1143
     * @return string
1144
     * @see echo, PHP_EOL
1145
     * @package YaLinqo\Actions
1146
     */
1147
    public function writeLine($selector = null)
1148
    {
1149
        $selector = Utils::createLambda($selector, 'v,k', Functions::$value);
1150
1151
        foreach ($this as $k => $v) {
1152
            echo $selector($v, $k), PHP_EOL;
1153
        }
1154
    }
1155
1156
    #endregion
1157
}