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.
Completed
Push — master ( 8f7b41...740e05 )
by Alexander
03:45
created

Enumerable::append()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 1
nop 2
dl 0
loc 12
rs 9.4285
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()
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($type)
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($type)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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 an automatic sequental integer 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 = Utils::UNDEFINED)
677
    {
678
        return new self(function() use ($value, $key) {
679
            // TODO Switch to 'yield from' when support for PHP<7.0 is dropped.
680
            foreach ($this as $k => $v)
681
                yield $k => $v;
682
            if ($key !== Utils::UNDEFINED)
683
                yield $key => $value;
684
            else
685
                yield $value;
686
        });
687
    }
688
689
    /**
690
     * Concatenates two sequences.
691
     * <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.
692
     * <p><b>Syntax</b>: concat (other)
693
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other The sequence to concatenate to the source sequence.
694
     * @return Enumerable A sequence that contains the concatenated elements of the two input sequences.
695
     * @package YaLinqo\Sets
696
     */
697
    public function concat($other)
698
    {
699
        $other = self::from($other);
700
701
        return new self(function() use ($other) {
702
            // TODO Switch to 'yield from' when support for PHP<7.0 is dropped.
703
            foreach ($this as $k => $v)
704
                yield $k => $v;
705
            foreach ($other as $k => $v)
706
                yield $k => $v;
707
        });
708
    }
709
710
    /**
711
     * Determines whether a sequence contains a specified element.
712
     * <p><b>Syntax</b>: contains (value)
713
     * <p>Determines whether a sequence contains a specified element. Enumeration is terminated as soon as a matching element is found.
714
     * @param $value mixed The value to locate in the sequence.
715
     * @return bool true if the source sequence contains an element that has the specified value; otherwise, false.
716
     * @package YaLinqo\Sets
717
     */
718
    public function contains($value)
719
    {
720
        foreach ($this as $v) {
721
            if ($v === $value)
722
                return true;
723
        }
724
        return false;
725
    }
726
727
    /**
728
     * Returns distinct elements from a sequence.
729
     * <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.
730
     * <p><b>Syntax</b>: distinct ()
731
     * <p>Returns distinct elements from a sequence using values as element keys.
732
     * <p><b>Syntax</b>: distinct (keySelector {(v, k) ==> value})
733
     * <p>Returns distinct elements from a sequence using values produced by keySelector as element keys.
734
     * @param callable|null $keySelector {(v, k) ==> value} A function to extract the element key from each element. Default: value.
735
     * @return Enumerable A sequence that contains distinct elements of the input sequence.
736
     * @package YaLinqo\Sets
737
     */
738
    public function distinct($keySelector = null)
739
    {
740
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
741
742
        return new self(function() use ($keySelector) {
743
            $set = [];
744
            foreach ($this as $k => $v) {
745
                $key = $keySelector($v, $k);
746
                if (isset($set[$key]))
747
                    continue;
748
                $set[$key] = true;
749
                yield $k => $v;
750
            }
751
        });
752
    }
753
754
    /**
755
     * 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.
756
     * <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.
757
     * <p><b>Syntax</b>: distinct (other)
758
     * <p>Produces the set difference of two sequences using values as element keys.
759
     * <p><b>Syntax</b>: distinct (other, keySelector {(v, k) ==> value})
760
     * <p>Produces the set difference of two sequences using values produced by keySelector as element keys.
761
     * @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.
762
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the element key from each element. Default: value.
763
     * @return Enumerable A sequence that contains the set difference of the elements of two sequences.
764
     * @package YaLinqo\Sets
765
     */
766
    public function except($other, $keySelector = null)
767
    {
768
        $other = self::from($other);
769
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
770
771
        return new self(function() use ($other, $keySelector) {
772
            $set = [];
773
            foreach ($other as $k => $v) {
774
                $key = $keySelector($v, $k);
775
                $set[$key] = true;
776
            }
777
            foreach ($this as $k => $v) {
778
                $key = $keySelector($v, $k);
779
                if (isset($set[$key]))
780
                    continue;
781
                $set[$key] = true;
782
                yield $k => $v;
783
            }
784
        });
785
    }
786
787
    /**
788
     * 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.
789
     * <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.
790
     * <p><b>Syntax</b>: intersect (other)
791
     * <p>Produces the set intersection of two sequences using values as element keys.
792
     * <p><b>Syntax</b>: intersect (other, keySelector {(v, k) ==> value})
793
     * <p>Produces the set intersection of two sequences using values produced by keySelector as element keys.
794
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other A sequence whose distinct elements that also appear in the first sequence will be returned.
795
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the element key from each element. Default: value.
796
     * @return Enumerable A sequence that contains the elements that form the set intersection of two sequences.
797
     * @package YaLinqo\Sets
798
     */
799
    public function intersect($other, $keySelector = null)
800
    {
801
        $other = self::from($other);
802
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
803
804
        return new self(function() use ($other, $keySelector) {
805
            $set = [];
806
            foreach ($other as $k => $v) {
807
                $key = $keySelector($v, $k);
808
                $set[$key] = true;
809
            }
810
            foreach ($this as $k => $v) {
811
                $key = $keySelector($v, $k);
812
                if (!isset($set[$key]))
813
                    continue;
814
                unset($set[$key]);
815
                yield $k => $v;
816
            }
817
        });
818
    }
819
820
    /**
821
     * Adds a value to the beginning of the sequence.
822
     * <p><b>Syntax</b>: prepend (other, value)
823
     * <p>Adds a value to the beginning of the sequence with an automatic sequental integer key.
824
     * <p><b>Syntax</b>: prepend (other, value, key)
825
     * <p>Adds a value to the beginning of the sequence with the specified key.
826
     * @param mixed $value The value to prepend.
827
     * @param mixed $key The key of the value to prepend.
828
     * @return Enumerable A new sequence that begins with the value.
829
     * @package YaLinqo\Sets
830
     */
831
    public function prepend($value, $key = Utils::UNDEFINED)
832
    {
833
        return new self(function() use ($value, $key) {
834
            if ($key !== Utils::UNDEFINED)
835
                yield $key => $value;
836
            else
837
                yield $value;
838
            // TODO Switch to 'yield from' when support for PHP<7.0 is dropped.
839
            foreach ($this as $k => $v)
840
                yield $k => $v;
841
        });
842
    }
843
844
    /**
845
     * Produces the set union of two sequences.
846
     * <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.
847
     * <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.
848
     * <p><b>Syntax</b>: union (other)
849
     * <p>Produces the set union of two sequences using values as element keys.
850
     * <p><b>Syntax</b>: union (other, keySelector {(v, k) ==> value})
851
     * <p>Produces the set union of two sequences using values produced by keySelector as element keys.
852
     * @param array|\Iterator|\IteratorAggregate|Enumerable $other A sequence whose distinct elements form the second set for the union.
853
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract the element key from each element. Default: value.
854
     * @return Enumerable A sequence that contains the elements from both input sequences, excluding duplicates.
855
     * @package YaLinqo\Sets
856
     */
857
    public function union($other, $keySelector = null)
858
    {
859
        $other = self::from($other);
860
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
861
862
        return new self(function() use ($other, $keySelector) {
863
            $set = [];
864
            foreach ($this as $k => $v) {
865
                $key = $keySelector($v, $k);
866
                if (isset($set[$key]))
867
                    continue;
868
                $set[$key] = true;
869
                yield $k => $v;
870
            }
871
            foreach ($other as $k => $v) {
872
                $key = $keySelector($v, $k);
873
                if (isset($set[$key]))
874
                    continue;
875
                $set[$key] = true;
876
                yield $k => $v;
877
            }
878
        });
879
    }
880
881
    #endregion
882
883
    #region Conversion
884
885
    /**
886
     * Creates an array from a sequence.
887
     * <p><b>Syntax</b>: toArray ()
888
     * <p>The toArray method forces immediate query evaluation and returns an array that contains the query results.
889
     * <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.
890
     * <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.
891
     * @return array An array that contains the elements from the input sequence.
892
     * @package YaLinqo\Conversion
893
     */
894
    public function toArray()
895
    {
896
        /** @var $it \Iterator|\ArrayIterator */
897
        $it = $this->getIterator();
898
        if ($it instanceof \ArrayIterator)
899
            return $it->getArrayCopy();
900
901
        $array = [];
902
        foreach ($it as $k => $v)
903
            $array[$k] = $v;
904
        return $array;
905
    }
906
907
    /**
908
     * Creates an array from a sequence, traversing deeply.
909
     * <p><b>Syntax</b>: toArrayDeep ()
910
     * <p>The toArrayDeep method forces immediate query evaluation and returns an array that contains the query results.
911
     * <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.
912
     * <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.
913
     * @return array An array that contains the elements from the input sequence.
914
     * @package YaLinqo\Conversion
915
     */
916
    public function toArrayDeep()
917
    {
918
        return $this->toArrayDeepProc($this);
919
    }
920
921
    /**
922
     * Proc for {@link toArrayDeep}.
923
     * @param $enum \Traversable Source sequence.
924
     * @return array An array that contains the elements from the input sequence.
925
     * @package YaLinqo\Conversion
926
     */
927
    protected function toArrayDeepProc($enum)
928
    {
929
        $array = [];
930
        foreach ($enum as $k => $v)
931
            $array[$k] = $v instanceof \Traversable || is_array($v) ? $this->toArrayDeepProc($v) : $v;
932
        return $array;
933
    }
934
935
    /**
936
     * Creates an array from a sequence, with sequental integer keys.
937
     * <p><b>Syntax</b>: toList ()
938
     * <p>The toList method forces immediate query evaluation and returns an array that contains the query results.
939
     * <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.
940
     * <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.
941
     * @return array An array that contains the elements from the input sequence.
942
     * @package YaLinqo\Conversion
943
     */
944
    public function toList()
945
    {
946
        /** @var $it \Iterator|\ArrayIterator */
947
        $it = $this->getIterator();
948
        if ($it instanceof \ArrayIterator)
949
            return array_values($it->getArrayCopy());
950
951
        $array = [];
952
        foreach ($it as $v)
953
            $array[] = $v;
954
        return $array;
955
    }
956
957
    /**
958
     * Creates an array from a sequence, with sequental integer keys.
959
     * <p><b>Syntax</b>: toListDeep ()
960
     * <p>The toListDeep method forces immediate query evaluation and returns an array that contains the query results.
961
     * <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.
962
     * <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.
963
     * @return array An array that contains the elements from the input sequence.
964
     * @package YaLinqo\Conversion
965
     */
966
    public function toListDeep()
967
    {
968
        return $this->toListDeepProc($this);
969
    }
970
971
    /**
972
     * Proc for {@link toListDeep}.
973
     * @param $enum \Traversable Source sequence.
974
     * @return array An array that contains the elements from the input sequence.
975
     * @package YaLinqo\Conversion
976
     */
977
    protected function toListDeepProc($enum)
978
    {
979
        $array = [];
980
        foreach ($enum as $v)
981
            $array[] = $v instanceof \Traversable || is_array($v) ? $this->toListDeepProc($v) : $v;
982
        return $array;
983
    }
984
985
    /**
986
     * Creates an array from a sequence according to specified key selector and value selector functions.
987
     * <p><b>Syntax</b>: toDictionary ([keySelector {(v, k) ==> key} [, valueSelector {(v, k) ==> value}]])
988
     * <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.
989
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from each element. Default: key.
990
     * @param callable|null $valueSelector {(v, k) ==> value} A transform function to produce a result value from each element. Default: value.
991
     * @return array An array that contains keys and values selected from the input sequence.
992
     * @package YaLinqo\Conversion
993
     */
994
    public function toDictionary($keySelector = null, $valueSelector = null)
995
    {
996
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$key);
997
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
998
999
        $dic = [];
1000
        foreach ($this as $k => $v)
1001
            $dic[$keySelector($v, $k)] = $valueSelector($v, $k);
1002
        return $dic;
1003
    }
1004
1005
    /**
1006
     * Returns a string containing the JSON representation of sequence (converted to array).
1007
     * <p><b>Syntax</b>: toJSON ([options])
1008
     * <p>This function only works with UTF-8 encoded data.
1009
     * @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.
1010
     * @return string A JSON encoded string on success or false on failure.
1011
     * @see json_encode
1012
     * @package YaLinqo\Conversion
1013
     */
1014
    public function toJSON($options = 0)
1015
    {
1016
        return json_encode($this->toArrayDeep(), $options);
1017
    }
1018
1019
    /**
1020
     * Creates an array from a sequence according to specified key selector and value selector functions.
1021
     * <p><b>Syntax</b>: toLookup ([keySelector {(v, k) ==> key} [, valueSelector {(v, k) ==> value}]])
1022
     * <p>The toLookup method returns an array, a one-to-many dictionary that maps keys to arrays of values.
1023
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from each element. Default: key.
1024
     * @param callable|null $valueSelector {(v, k) ==> value} A transform function to produce a result value from each element. Default: value.
1025
     * @return array An array that contains keys and value arrays selected from the input sequence.
1026
     * @package YaLinqo\Conversion
1027
     */
1028
    public function toLookup($keySelector = null, $valueSelector = null)
1029
    {
1030
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$key);
1031
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
1032
1033
        $lookup = [];
1034
        foreach ($this as $k => $v)
1035
            $lookup[$keySelector($v, $k)][] = $valueSelector($v, $k);
1036
        return $lookup;
1037
    }
1038
1039
    /**
1040
     * Returns a sequence of keys from the source sequence.
1041
     * <p><b>Syntax</b>: toKeys ()
1042
     * @return Enumerable A sequence with keys from the source sequence as values and sequental integers as keys.
1043
     * @see array_keys
1044
     * @package YaLinqo\Conversion
1045
     */
1046
    public function toKeys()
1047
    {
1048
        return $this->select(Functions::$key, Functions::increment());
1049
    }
1050
1051
    /**
1052
     * Returns a sequence of values from the source sequence; keys are discarded.
1053
     * <p><b>Syntax</b>: toValues ()
1054
     * @return Enumerable A sequence with the same values and sequental integers as keys.
1055
     * @see array_values
1056
     * @package YaLinqo\Conversion
1057
     */
1058
    public function toValues()
1059
    {
1060
        return $this->select(Functions::$value, Functions::increment());
1061
    }
1062
1063
    /**
1064
     * Transform the sequence to an object.
1065
     * <p><b>Syntax</b>: toObject ([propertySelector {(v, k) ==> name} [, valueSelector {(v, k) ==> value}]])
1066
     * @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.
1067
     * @param callable|null $valueSelector {(v, k) ==> value} A function to extract a property value from an element. Default: value.
1068
     * @return \stdClass
1069
     * @package YaLinqo\Conversion
1070
     */
1071
    public function toObject($propertySelector = null, $valueSelector = null)
1072
    {
1073
        $propertySelector = Utils::createLambda($propertySelector, 'v,k', Functions::$key);
1074
        $valueSelector = Utils::createLambda($valueSelector, 'v,k', Functions::$value);
1075
1076
        $obj = new \stdClass();
1077
        foreach ($this as $k => $v)
1078
            $obj->{$propertySelector($v, $k)} = $valueSelector($v, $k);
1079
        return $obj;
1080
    }
1081
1082
    /**
1083
     * Returns a string containing a string representation of all the sequence values, with the separator string between each element.
1084
     * <p><b>Syntax</b>: toString ([separator [, selector]])
1085
     * @param string $separator A string separating values in the result string. Default: ''.
1086
     * @param callable|null $valueSelector {(v, k) ==> value} A transform function to apply to each element. Default: value.
1087
     * @return string
1088
     * @see implode
1089
     * @package YaLinqo\Conversion
1090
     */
1091
    public function toString($separator = '', $valueSelector = null)
1092
    {
1093
        $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...
1094
        $array = $valueSelector ? $this->select($valueSelector)->toList() : $this->toList();
1095
        return implode($separator, $array);
1096
    }
1097
1098
    #endregion
1099
1100
    #region Actions
1101
1102
    /**
1103
     * Invokes an action for each element in the sequence.
1104
     * <p><b>Syntax</b>: process (action {(v, k) ==> void})
1105
     * <p>Process method does not start enumeration itself. To force enumeration, you can use {@link each} method.
1106
     * <p>Original LINQ method name: do.
1107
     * @param callable $action {(v, k) ==> void} The action to invoke for each element in the sequence.
1108
     * @return Enumerable The source sequence with the side-effecting behavior applied.
1109
     * @package YaLinqo\Actions
1110
     */
1111
    public function call($action)
1112
    {
1113
        $action = Utils::createLambda($action, 'v,k');
1114
1115
        return new self(function() use ($action) {
1116
            foreach ($this as $k => $v) {
1117
                $action($v, $k);
1118
                yield $k => $v;
1119
            }
1120
        });
1121
    }
1122
1123
    /**
1124
     * Invokes an action for each element in the sequence.
1125
     * <p><b>Syntax</b>: each (action {(v, k) ==> void})
1126
     * <p>Each method forces enumeration. To just add side-effect without enumerating, you can use {@link process} method.
1127
     * <p>Original LINQ method name: foreach.
1128
     * @param callable $action {(v, k) ==> void} The action to invoke for each element in the sequence.
1129
     * @package YaLinqo\Actions
1130
     */
1131
    public function each($action = null)
1132
    {
1133
        $action = Utils::createLambda($action, 'v,k', Functions::$blank);
1134
1135
        foreach ($this as $k => $v)
1136
            $action($v, $k);
1137
    }
1138
1139
    /**
1140
     * Output the result of calling {@link toString} method.
1141
     * <p><b>Syntax</b>: write ([separator [, selector]])
1142
     * @param string $separator A string separating values in the result string. Default: ''.
1143
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
1144
     * @see implode, echo
1145
     * @package YaLinqo\Actions
1146
     */
1147
    public function write($separator = '', $selector = null)
1148
    {
1149
        echo $this->toString($separator, $selector);
1150
    }
1151
1152
    /**
1153
     * Output all the sequence values, with a new line after each element.
1154
     * <p><b>Syntax</b>: writeLine ([selector])
1155
     * @param callable|null $selector {(v, k) ==> value} A transform function to apply to each element. Default: value.
1156
     * @return string
1157
     * @see echo, PHP_EOL
1158
     * @package YaLinqo\Actions
1159
     */
1160
    public function writeLine($selector = null)
1161
    {
1162
        $selector = Utils::createLambda($selector, 'v,k', Functions::$value);
1163
1164
        foreach ($this as $k => $v) {
1165
            echo $selector($v, $k), PHP_EOL;
1166
        }
1167
    }
1168
1169
    #endregion
1170
}