Passed
Push — master ( 10b137...2bffcf )
by Adrien
10:25
created

Conditional   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Test Coverage

Coverage 99.01%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 88
dl 0
loc 291
ccs 100
cts 101
cp 0.9901
rs 10
c 2
b 0
f 0
wmc 28

15 Methods

Rating   Name   Duplication   Size   Complexity  
A AVERAGEIF() 0 6 1
A buildDataSet() 0 13 2
A buildDatabase() 0 5 1
A COUNTIF() 0 14 2
A buildConditionSet() 0 5 1
A buildConditionSetForValueRange() 0 14 2
A buildConditions() 0 12 2
A buildDatabaseWithValueRange() 0 9 1
A COUNTIFS() 0 12 3
A SUMIFS() 0 12 3
A MINIFS() 0 10 2
A MAXIFS() 0 10 2
A AVERAGEIFS() 0 12 3
A SUMIF() 0 6 1
A databaseFromRangeAndValue() 0 16 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage;
6
use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount;
7
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax;
8
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin;
9
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
10
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
11
12
class Conditional
13
{
14
    private const CONDITION_COLUMN_NAME = 'CONDITION';
15
    private const VALUE_COLUMN_NAME = 'VALUE';
16
    private const CONDITIONAL_COLUMN_NAME = 'CONDITIONAL %d';
17
18
    /**
19
     * AVERAGEIF.
20
     *
21
     * Returns the average value from a range of cells that contain numbers within the list of arguments
22
     *
23
     * Excel Function:
24
     *        AVERAGEIF(range,condition[, average_range])
25
     *
26
     * @param mixed[] $range Data values
27
     * @param string $condition the criteria that defines which cells will be checked
28
     * @param mixed[] $averageRange Data values
29
     *
30
     * @return null|float|string
31
     */
32 16
    public static function AVERAGEIF($range, $condition, $averageRange = [])
33
    {
34 16
        $database = self::databaseFromRangeAndValue($range, $averageRange);
35 16
        $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
36
37 16
        return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
38
    }
39
40
    /**
41
     * AVERAGEIFS.
42
     *
43
     * Counts the number of cells that contain numbers within the list of arguments
44
     *
45
     * Excel Function:
46
     *        AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
47
     *
48
     * @param mixed $args Pairs of Ranges and Criteria
49
     *
50
     * @return null|float|string
51
     */
52 6
    public static function AVERAGEIFS(...$args)
53
    {
54 6
        if (empty($args)) {
55 1
            return 0.0;
56 5
        } elseif (count($args) === 3) {
57 1
            return self::AVERAGEIF($args[1], $args[2], $args[0]);
58
        }
59
60 4
        $conditions = self::buildConditionSetForValueRange(...$args);
61 4
        $database = self::buildDatabaseWithValueRange(...$args);
62
63 4
        return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
64
    }
65
66
    /**
67
     * COUNTIF.
68
     *
69
     * Counts the number of cells that contain numbers within the list of arguments
70
     *
71
     * Excel Function:
72
     *        COUNTIF(range,condition)
73
     *
74
     * @param mixed[] $range Data values
75
     * @param string $condition the criteria that defines which cells will be counted
76
     *
77
     * @return int
78
     */
79 17
    public static function COUNTIF($range, $condition)
80
    {
81
        // Filter out any empty values that shouldn't be included in a COUNT
82 17
        $range = array_filter(
83 17
            Functions::flattenArray($range),
84 17
            function ($value) {
85 17
                return $value !== null && $value !== '';
86 17
            }
87
        );
88
89 17
        $range = array_merge([[self::CONDITION_COLUMN_NAME]], array_chunk($range, 1));
90 17
        $condition = array_merge([[self::CONDITION_COLUMN_NAME]], [[$condition]]);
91
92 17
        return DCount::evaluate($range, null, $condition);
93
    }
94
95
    /**
96
     * COUNTIFS.
97
     *
98
     * Counts the number of cells that contain numbers within the list of arguments
99
     *
100
     * Excel Function:
101
     *        COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…)
102
     *
103
     * @param mixed $args Pairs of Ranges and Criteria
104
     *
105
     * @return int
106
     */
107 6
    public static function COUNTIFS(...$args)
108
    {
109 6
        if (empty($args)) {
110 1
            return 0;
111 5
        } elseif (count($args) === 2) {
112 3
            return self::COUNTIF(...$args);
113
        }
114
115 2
        $database = self::buildDatabase(...$args);
116 2
        $conditions = self::buildConditionSet(...$args);
117
118 2
        return DCount::evaluate($database, null, $conditions);
119
    }
120
121
    /**
122
     * MAXIFS.
123
     *
124
     * Returns the maximum value within a range of cells that contain numbers within the list of arguments
125
     *
126
     * Excel Function:
127
     *        MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
128
     *
129
     * @param mixed $args Pairs of Ranges and Criteria
130
     *
131
     * @return null|float|string
132
     */
133 6
    public static function MAXIFS(...$args)
134
    {
135 6
        if (empty($args)) {
136 1
            return 0.0;
137
        }
138
139 5
        $conditions = self::buildConditionSetForValueRange(...$args);
140 5
        $database = self::buildDatabaseWithValueRange(...$args);
141
142 5
        return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
143
    }
144
145
    /**
146
     * MINIFS.
147
     *
148
     * Returns the minimum value within a range of cells that contain numbers within the list of arguments
149
     *
150
     * Excel Function:
151
     *        MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
152
     *
153
     * @param mixed $args Pairs of Ranges and Criteria
154
     *
155
     * @return null|float|string
156
     */
157 6
    public static function MINIFS(...$args)
158
    {
159 6
        if (empty($args)) {
160 1
            return 0.0;
161
        }
162
163 5
        $conditions = self::buildConditionSetForValueRange(...$args);
164 5
        $database = self::buildDatabaseWithValueRange(...$args);
165
166 5
        return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
167
    }
168
169
    /**
170
     * SUMIF.
171
     *
172
     * Totals the values of cells that contain numbers within the list of arguments
173
     *
174
     * Excel Function:
175
     *        SUMIF(range, criteria, [sum_range])
176
     *
177
     * @param mixed $range Data values
178
     * @param mixed $sumRange
179
     * @param mixed $condition
180
     *
181
     * @return float|string
182
     */
183 18
    public static function SUMIF($range, $condition, $sumRange = [])
184
    {
185 18
        $database = self::databaseFromRangeAndValue($range, $sumRange);
186 18
        $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
187
188 18
        return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
189
    }
190
191
    /**
192
     * SUMIFS.
193
     *
194
     * Counts the number of cells that contain numbers within the list of arguments
195
     *
196
     * Excel Function:
197
     *        SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
198
     *
199
     * @param mixed $args Pairs of Ranges and Criteria
200
     *
201
     * @return null|float|string
202
     */
203 6
    public static function SUMIFS(...$args)
204
    {
205 6
        if (empty($args)) {
206 1
            return 0.0;
207 5
        } elseif (count($args) === 3) {
208
            return self::SUMIF($args[1], $args[2], $args[0]);
209
        }
210
211 5
        $conditions = self::buildConditionSetForValueRange(...$args);
212 5
        $database = self::buildDatabaseWithValueRange(...$args);
213
214 5
        return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
215
    }
216
217 2
    private static function buildConditionSet(...$args): array
218
    {
219 2
        $conditions = self::buildConditions(1, ...$args);
220
221 2
        return array_map(null, ...$conditions);
1 ignored issue
show
Bug introduced by
null of type null is incompatible with the type callable expected by parameter $callback of array_map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
        return array_map(/** @scrutinizer ignore-type */ null, ...$conditions);
Loading history...
222
    }
223
224 19
    private static function buildConditionSetForValueRange(...$args): array
225
    {
226 19
        $conditions = self::buildConditions(2, ...$args);
227
228 19
        if (count($conditions) === 1) {
229 4
            return array_map(
230 4
                function ($value) {
231 4
                    return [$value];
232 4
                },
233 4
                $conditions[0]
234
            );
235
        }
236
237 15
        return array_map(null, ...$conditions);
1 ignored issue
show
Bug introduced by
null of type null is incompatible with the type callable expected by parameter $callback of array_map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

237
        return array_map(/** @scrutinizer ignore-type */ null, ...$conditions);
Loading history...
238
    }
239
240 21
    private static function buildConditions(int $startOffset, ...$args): array
241
    {
242 21
        $conditions = [];
243
244 21
        $pairCount = 1;
245 21
        $argumentCount = count($args);
246 21
        for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
247 21
            $conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]);
248 21
            ++$pairCount;
249
        }
250
251 21
        return $conditions;
252
    }
253
254 2
    private static function buildDatabase(...$args): array
255
    {
256 2
        $database = [];
257
258 2
        return self::buildDataSet(0, $database, ...$args);
259
    }
260
261 19
    private static function buildDatabaseWithValueRange(...$args): array
262
    {
263 19
        $database = [];
264 19
        $database[] = array_merge(
265 19
            [self::VALUE_COLUMN_NAME],
266 19
            Functions::flattenArray($args[0])
267
        );
268
269 19
        return self::buildDataSet(1, $database, ...$args);
270
    }
271
272 21
    private static function buildDataSet(int $startOffset, array $database, ...$args): array
273
    {
274 21
        $pairCount = 1;
275 21
        $argumentCount = count($args);
276 21
        for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
277 21
            $database[] = array_merge(
278 21
                [sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)],
279 21
                Functions::flattenArray($args[$argument])
280
            );
281 21
            ++$pairCount;
282
        }
283
284 21
        return array_map(null, ...$database);
1 ignored issue
show
Bug introduced by
null of type null is incompatible with the type callable expected by parameter $callback of array_map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
        return array_map(/** @scrutinizer ignore-type */ null, ...$database);
Loading history...
285
    }
286
287 34
    private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array
288
    {
289 34
        $range = Functions::flattenArray($range);
290
291 34
        $valueRange = Functions::flattenArray($valueRange);
292 34
        if (empty($valueRange)) {
293 6
            $valueRange = $range;
294
        }
295
296 34
        $database = array_map(
297 34
            null,
1 ignored issue
show
Bug introduced by
null of type null is incompatible with the type callable expected by parameter $callback of array_map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

297
            /** @scrutinizer ignore-type */ null,
Loading history...
298 34
            array_merge([self::CONDITION_COLUMN_NAME], $range),
299 34
            array_merge([self::VALUE_COLUMN_NAME], $valueRange)
300
        );
301
302 34
        return $database;
303
    }
304
}
305