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.

OrderedEnumerable::trySortArrayWithMultisort()   B
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
rs 8.5906
cc 5
eloc 15
nc 4
nop 3
1
<?php
2
3
/**
4
 * OrderedEnumerable 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
/**
13
 * Subclass of Enumerable supporting ordering by multiple conditions.
14
 * @package YaLinqo
15
 */
16
class OrderedEnumerable extends Enumerable
17
{
18
    /** Source sequence. @var Enumerable */
19
    private $source;
20
    /** Parent ordered sequence. @var \YaLinqo\OrderedEnumerable */
21
    private $parent;
22
    /** Sort order for array_multisort: SORT_DESC or SORT_ASC. @var int|bool */
23
    private $sortOrder;
24
    /** Sort flags for array_multisort. @var int */
25
    private $sortFlags;
26
    /** Whether comparer result needs to be negated (used in usort). @var bool */
27
    private $isReversed;
28
    /** Key selector. @var callable {(v, k) ==> key} */
29
    private $keySelector;
30
    /** Comprarer function. @var callable {(a, b) ==> diff} */
31
    private $comparer;
32
33
    /**
34
     * @internal
35
     * @param Enumerable $source
36
     * @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).
37
     * @param int $sortFlags Sort flags for array_multisort.
38
     * @param bool $isReversed Whether comparer result needs to be negated (used in usort).
39
     * @param callable $keySelector {(v, k) ==> key} A function to extract a key from an element.
40
     * @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
41
     * @param \YaLinqo\OrderedEnumerable $parent
42
     */
43
    public function __construct($source, $sortOrder, $sortFlags, $isReversed, $keySelector, $comparer, $parent = null)
44
    {
45
        $this->source = $source;
46
        $this->sortOrder = $sortOrder;
47
        $this->sortFlags = $sortFlags;
48
        $this->isReversed = $isReversed;
49
        $this->keySelector = $keySelector;
50
        $this->comparer = $comparer;
51
        $this->parent = $parent;
52
    }
53
54
    private function getSingleComparer()
55
    {
56
        $comparer = $this->comparer;
57
        if ($this->isReversed)
58
            $comparer = function($a, $b) use ($comparer) { return -$comparer($a, $b); };
59
        return $comparer;
60
    }
61
62
    /**
63
     * <p><b>Syntax</b>: thenByDir (false|true [, {{(v, k) ==> key} [, {{(a, b) ==> diff}]])
64
     * <p>Performs a subsequent ordering of elements in a sequence in a particular direction (ascending, descending) according to a key.
65
     * <p>Three methods are defined to extend the type OrderedEnumerable, which is the return type of this method. These three methods, namely {@link thenBy}, {@link thenByDescending} and {@link 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.
66
     * <p>Because OrderedEnumerable inherits from {@link Enumerable}, you can call {@link Enumerable::orderBy orderBy}, {@link Enumerable::orderByDescending orderByDescending} or {@link Enumerable::orderByDir 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.
67
     * <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.
68
     * @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).
69
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from an element. Default: value.
70
     * @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.
71
     * @return \YaLinqo\OrderedEnumerable
72
     */
73
    public function thenByDir($sortOrder, $keySelector = null, $comparer = null): OrderedEnumerable
74
    {
75
        $sortFlags = Utils::lambdaToSortFlagsAndOrder($comparer, $sortOrder);
76
        $keySelector = Utils::createLambda($keySelector, 'v,k', Functions::$value);
77
        $isReversed = $sortOrder == SORT_DESC;
78
        $comparer = Utils::createComparer($comparer, $sortOrder, $isReversed);
0 ignored issues
show
Bug introduced by
It seems like $sortOrder defined by parameter $sortOrder on line 73 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...
79
        return new self($this->source, $sortOrder, $sortFlags, $isReversed, $keySelector, $comparer, $this);
0 ignored issues
show
Bug introduced by
It seems like $sortFlags defined by \YaLinqo\Utils::lambdaTo...($comparer, $sortOrder) on line 75 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 76 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 78 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...
80
    }
81
82
    /**
83
     * <p><b>Syntax</b>: thenBy ([{{(v, k) ==> key} [, {{(a, b) ==> diff}]])
84
     * <p>Performs a subsequent ordering of the elements in a sequence in ascending order according to a key.
85
     * <p>Three methods are defined to extend the type OrderedEnumerable, which is the return type of this method. These three methods, namely {@link thenBy}, {@link thenByDescending} and {@link 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.
86
     * <p>Because OrderedEnumerable inherits from {@link Enumerable}, you can call {@link Enumerable::orderBy orderBy}, {@link Enumerable::orderByDescending orderByDescending} or {@link Enumerable::orderByDir 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.
87
     * <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.
88
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from an element. Default: value.
89
     * @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.
90
     * @return \YaLinqo\OrderedEnumerable
91
     */
92
    public function thenBy($keySelector = null, $comparer = null): OrderedEnumerable
93
    {
94
        return $this->thenByDir(false, $keySelector, $comparer);
95
    }
96
97
    /**
98
     * <p><b>Syntax</b>: thenByDescending ([{{(v, k) ==> key} [, {{(a, b) ==> diff}]])
99
     * <p>Performs a subsequent ordering of the elements in a sequence in descending order according to a key.
100
     * <p>Three methods are defined to extend the type OrderedEnumerable, which is the return type of this method. These three methods, namely {@link thenBy}, {@link thenByDescending} and {@link 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.
101
     * <p>Because OrderedEnumerable inherits from {@link Enumerable}, you can call {@link Enumerable::orderBy orderBy}, {@link Enumerable::orderByDescending orderByDescending} or {@link Enumerable::orderByDir 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.
102
     * <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.
103
     * @param callable|null $keySelector {(v, k) ==> key} A function to extract a key from an element. Default: value.
104
     * @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.
105
     * @return \YaLinqo\OrderedEnumerable
106
     */
107
    public function thenByDescending($keySelector = null, $comparer = null): OrderedEnumerable
108
    {
109
        return $this->thenByDir(true, $keySelector, $comparer);
110
    }
111
112
    /** {@inheritdoc} */
113
    public function getIterator(): \Traversable
114
    {
115
        $canMultisort = $this->sortFlags !== null;
116
        $array = $this->source->tryGetArrayCopy();
117
118
        $it = $this->trySortBySingleField($array, $canMultisort);
119
        if ($it !== null)
120
            return $it;
121
122
        return $this->sortByMultipleFields($array, $canMultisort);
123
    }
124
125
    private function trySortBySingleField($array, bool $canMultisort)
126
    {
127
        if ($this->parent !== null || $array === null) {
128
            return null;
129
        }
130
        elseif ($this->keySelector === Functions::$value) {
131
            if (!$canMultisort)
132
                uasort($array, $this->getSingleComparer());
133
            elseif ($this->sortOrder == SORT_ASC)
134
                asort($array, $this->sortFlags);
135
            else
136
                arsort($array, $this->sortFlags);
137
        }
138
        elseif ($this->keySelector === Functions::$key) {
139
            if ($canMultisort)
140
                uksort($array, $this->getSingleComparer());
141
            elseif ($this->sortOrder == SORT_ASC)
142
                ksort($array, $this->sortFlags);
143
            else
144
                krsort($array, $this->sortFlags);
145
        }
146
        else {
147
            return null;
148
        }
149
        return new \ArrayIterator($array);
150
    }
151
152
    private function sortByMultipleFields($array, bool $canMultisort)
153
    {
154
        $orders = [];
155
        for ($order = $this; $order !== null; $order = $order->parent) {
156
            $orders[] = $order;
157
            if ($order->sortFlags === null)
158
                $canMultisort = false;
159
        }
160
        $orders = array_reverse($orders);
161
162
        $it = $this->trySortArrayWithMultisort($array, $orders, $canMultisort);
163
        if ($it !== null)
164
            return $it;
165
166
        return $this->sortIterator($orders, $canMultisort);
167
    }
168
169
    private function sortIterator(array $orders, bool $canMultisort)
170
    {
171
        $enum = [];
172
        if ($canMultisort)
173
            $this->sortIteratorWithMultisort($enum, $orders);
174
        else
175
            $this->sortIteratorWithUsort($enum, $orders);
176
177
        foreach ($enum as $pair)
178
            yield $pair[0] => $pair[1];
179
    }
180
181
    private function trySortArrayWithMultisort($array, array $orders, bool $canMultisort)
182
    {
183
        /** @var $order OrderedEnumerable */
184
        if ($array === null || !$canMultisort)
185
            return null;
186
187
        $args = [];
188
        foreach ($orders as $order) {
189
            $column = [];
190
            foreach ($array as $k => $v) {
191
                $keySelector = $order->keySelector;
192
                $column[$k] = $keySelector($v, $k);
193
            }
194
            $args[] = $column;
195
            $args[] = $order->sortOrder;
196
            $args[] = $order->sortFlags;
197
        }
198
        $args[] = &$array;
199
200
        call_user_func_array('array_multisort', $args);
201
202
        return new \ArrayIterator($array);
203
    }
204
205
    private function sortIteratorWithMultisort(&$enum, array $orders)
206
    {
207
        /** @var $order OrderedEnumerable */
208
        foreach ($this->source as $k => $v)
209
            $enum[] = [ $k, $v ];
210
211
        $args = [];
212
        foreach ($orders as $order) {
213
            $column = [];
214
            foreach ($enum as $k => $pair) {
215
                $keySelector = $order->keySelector;
216
                $column[$k] = $keySelector($pair[1], $pair[0]);
217
            }
218
            $args[] = $column;
219
            $args[] = $order->sortOrder;
220
            $args[] = $order->sortFlags;
221
        }
222
        $args[] = &$enum;
223
224
        call_user_func_array('array_multisort', $args);
225
    }
226
227
    private function sortIteratorWithUsort(&$enum, array $orders)
228
    {
229
        /** @var $order OrderedEnumerable */
230
        foreach ($this->source as $k => $v) {
231
            $element = [ $k, $v ];
232
            foreach ($orders as $order) {
233
                $keySelector = $order->keySelector;
234
                $element[] = $keySelector($v, $k);
235
            }
236
            $enum[] = $element;
237
        }
238
239
        usort($enum, function($a, $b) use ($orders) {
240
            /** @var $order OrderedEnumerable */
241
            $count = count($orders);
242
            for ($i = 0; $i < $count; $i++) {
243
                $order = $orders[$i];
244
                $comparer = $order->comparer;
245
                $diff = $comparer($a[$i + 2], $b[$i + 2]);
246
                if ($diff != 0)
247
                    return $order->isReversed ? -$diff : $diff;
248
            }
249
            return 0;
250
        });
251
    }
252
}
253