Completed
Push — develop ( 3ee9cc...870d86 )
by Adrien
29:45
created

MathTrig::SUMIF()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 10
nop 3
dl 0
loc 26
rs 8.439
c 0
b 0
f 0
ccs 15
cts 15
cp 1
crap 5
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
/**
6
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 *
22
 * @category    PhpSpreadsheet
23
 *
24
 * @copyright   Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
25
 * @license     http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
26
 */
27
class MathTrig
28
{
29
    //
30
    //    Private method to return an array of the factors of the input value
31
    //
32 35
    private static function factors($value)
33
    {
34 35
        $startVal = floor(sqrt($value));
35
36 35
        $factorArray = [];
37 35
        for ($i = $startVal; $i > 1; --$i) {
38 33
            if (($value % $i) == 0) {
39 23
                $factorArray = array_merge($factorArray, self::factors($value / $i));
40 23
                $factorArray = array_merge($factorArray, self::factors($i));
41 23
                if ($i <= sqrt($value)) {
42 23
                    break;
43
                }
44
            }
45
        }
46 35
        if (!empty($factorArray)) {
47 23
            rsort($factorArray);
48
49 23
            return $factorArray;
50
        }
51
52 35
        return [(int) $value];
53
    }
54
55 5
    private static function romanCut($num, $n)
56
    {
57 5
        return ($num - ($num % $n)) / $n;
58
    }
59
60
    /**
61
     * ATAN2.
62
     *
63
     * This function calculates the arc tangent of the two variables x and y. It is similar to
64
     *        calculating the arc tangent of y ÷ x, except that the signs of both arguments are used
65
     *        to determine the quadrant of the result.
66
     * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a
67
     *        point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between
68
     *        -pi and pi, excluding -pi.
69
     *
70
     * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard
71
     *        PHP atan2() function, so we need to reverse them here before calling the PHP atan() function.
72
     *
73
     * Excel Function:
74
     *        ATAN2(xCoordinate,yCoordinate)
75
     *
76
     * @category Mathematical and Trigonometric Functions
77
     *
78
     * @param float $xCoordinate the x-coordinate of the point
79
     * @param float $yCoordinate the y-coordinate of the point
80
     *
81
     * @return float the inverse tangent of the specified x- and y-coordinates
82
     */
83 16
    public static function ATAN2($xCoordinate = null, $yCoordinate = null)
84
    {
85 16
        $xCoordinate = Functions::flattenSingleValue($xCoordinate);
86 16
        $yCoordinate = Functions::flattenSingleValue($yCoordinate);
87
88 16
        $xCoordinate = ($xCoordinate !== null) ? $xCoordinate : 0.0;
89 16
        $yCoordinate = ($yCoordinate !== null) ? $yCoordinate : 0.0;
90
91 16
        if (((is_numeric($xCoordinate)) || (is_bool($xCoordinate))) &&
92 16
            ((is_numeric($yCoordinate))) || (is_bool($yCoordinate))) {
93 15
            $xCoordinate = (float) $xCoordinate;
94 15
            $yCoordinate = (float) $yCoordinate;
95
96 15
            if (($xCoordinate == 0) && ($yCoordinate == 0)) {
97 1
                return Functions::DIV0();
98
            }
99
100 14
            return atan2($yCoordinate, $xCoordinate);
101
        }
102
103 1
        return Functions::VALUE();
104
    }
105
106
    /**
107
     * CEILING.
108
     *
109
     * Returns number rounded up, away from zero, to the nearest multiple of significance.
110
     *        For example, if you want to avoid using pennies in your prices and your product is
111
     *        priced at $4.42, use the formula =CEILING(4.42,0.05) to round prices up to the
112
     *        nearest nickel.
113
     *
114
     * Excel Function:
115
     *        CEILING(number[,significance])
116
     *
117
     * @category Mathematical and Trigonometric Functions
118
     *
119
     * @param float $number the number you want to round
120
     * @param float $significance the multiple to which you want to round
121
     *
122
     * @return float Rounded Number
123
     */
124 43 View Code Duplication
    public static function CEILING($number, $significance = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
    {
126 43
        $number = Functions::flattenSingleValue($number);
127 43
        $significance = Functions::flattenSingleValue($significance);
128
129 43
        if ((is_null($significance)) &&
130 2
            (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
131
            $significance = $number / abs($number);
132
        }
133
134 43
        if ((is_numeric($number)) && (is_numeric($significance))) {
135 41
            if (($number == 0.0) || ($significance == 0.0)) {
136 3
                return 0.0;
137 38
            } elseif (self::SIGN($number) == self::SIGN($significance)) {
138 35
                return ceil($number / $significance) * $significance;
139
            }
140
141 3
            return Functions::NAN();
142
        }
143
144 2
        return Functions::VALUE();
145
    }
146
147
    /**
148
     * COMBIN.
149
     *
150
     * Returns the number of combinations for a given number of items. Use COMBIN to
151
     *        determine the total possible number of groups for a given number of items.
152
     *
153
     * Excel Function:
154
     *        COMBIN(numObjs,numInSet)
155
     *
156
     * @category Mathematical and Trigonometric Functions
157
     *
158
     * @param int $numObjs Number of different objects
159
     * @param int $numInSet Number of objects in each combination
160
     *
161
     * @return int Number of combinations
162
     */
163 24
    public static function COMBIN($numObjs, $numInSet)
164
    {
165 24
        $numObjs = Functions::flattenSingleValue($numObjs);
166 24
        $numInSet = Functions::flattenSingleValue($numInSet);
167
168 24
        if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
169 23
            if ($numObjs < $numInSet) {
170 3
                return Functions::NAN();
171 20
            } elseif ($numInSet < 0) {
172 2
                return Functions::NAN();
173
            }
174
175 18
            return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
176
        }
177
178 1
        return Functions::VALUE();
179
    }
180
181
    /**
182
     * EVEN.
183
     *
184
     * Returns number rounded up to the nearest even integer.
185
     * You can use this function for processing items that come in twos. For example,
186
     *        a packing crate accepts rows of one or two items. The crate is full when
187
     *        the number of items, rounded up to the nearest two, matches the crate's
188
     *        capacity.
189
     *
190
     * Excel Function:
191
     *        EVEN(number)
192
     *
193
     * @category Mathematical and Trigonometric Functions
194
     *
195
     * @param float $number Number to round
196
     *
197
     * @return int Rounded Number
198
     */
199 25
    public static function EVEN($number)
200
    {
201 25
        $number = Functions::flattenSingleValue($number);
202
203 25
        if (is_null($number)) {
204 1
            return 0;
205 24
        } elseif (is_bool($number)) {
206 2
            $number = (int) $number;
207
        }
208
209 24
        if (is_numeric($number)) {
210 23
            $significance = 2 * self::SIGN($number);
211
212 23
            return (int) self::CEILING($number, $significance);
213
        }
214
215 1
        return Functions::VALUE();
216
    }
217
218
    /**
219
     * FACT.
220
     *
221
     * Returns the factorial of a number.
222
     * The factorial of a number is equal to 1*2*3*...* number.
223
     *
224
     * Excel Function:
225
     *        FACT(factVal)
226
     *
227
     * @category Mathematical and Trigonometric Functions
228
     *
229
     * @param float $factVal Factorial Value
230
     *
231
     * @return int Factorial
232
     */
233 145
    public static function FACT($factVal)
234
    {
235 145
        $factVal = Functions::flattenSingleValue($factVal);
236
237 145
        if (is_numeric($factVal)) {
238 144
            if ($factVal < 0) {
239 1
                return Functions::NAN();
240
            }
241 143
            $factLoop = floor($factVal);
242 143
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
243
                if ($factVal > $factLoop) {
244
                    return Functions::NAN();
245
                }
246
            }
247
248 143
            $factorial = 1;
249 143
            while ($factLoop > 1) {
250 71
                $factorial *= $factLoop--;
251
            }
252
253 143
            return $factorial;
254
        }
255
256 1
        return Functions::VALUE();
257
    }
258
259
    /**
260
     * FACTDOUBLE.
261
     *
262
     * Returns the double factorial of a number.
263
     *
264
     * Excel Function:
265
     *        FACTDOUBLE(factVal)
266
     *
267
     * @category Mathematical and Trigonometric Functions
268
     *
269
     * @param float $factVal Factorial Value
270
     *
271
     * @return int Double Factorial
272
     */
273 8
    public static function FACTDOUBLE($factVal)
274
    {
275 8
        $factLoop = Functions::flattenSingleValue($factVal);
276
277 8
        if (is_numeric($factLoop)) {
278 7
            $factLoop = floor($factLoop);
279 7
            if ($factVal < 0) {
280 1
                return Functions::NAN();
281
            }
282 6
            $factorial = 1;
283 6
            while ($factLoop > 1) {
284 5
                $factorial *= $factLoop--;
285 5
                --$factLoop;
286
            }
287
288 6
            return $factorial;
289
        }
290
291 1
        return Functions::VALUE();
292
    }
293
294
    /**
295
     * FLOOR.
296
     *
297
     * Rounds number down, toward zero, to the nearest multiple of significance.
298
     *
299
     * Excel Function:
300
     *        FLOOR(number[,significance])
301
     *
302
     * @category Mathematical and Trigonometric Functions
303
     *
304
     * @param float $number Number to round
305
     * @param float $significance Significance
306
     *
307
     * @return float Rounded Number
308
     */
309 11 View Code Duplication
    public static function FLOOR($number, $significance = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
    {
311 11
        $number = Functions::flattenSingleValue($number);
312 11
        $significance = Functions::flattenSingleValue($significance);
313
314 11
        if ((is_null($significance)) &&
315 2
            (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
316
            $significance = $number / abs($number);
317
        }
318
319 11
        if ((is_numeric($number)) && (is_numeric($significance))) {
320 9
            if ($significance == 0.0) {
321 1
                return Functions::DIV0();
322 8
            } elseif ($number == 0.0) {
323
                return 0.0;
324 8
            } elseif (self::SIGN($number) == self::SIGN($significance)) {
325 6
                return floor($number / $significance) * $significance;
326
            }
327
328 2
            return Functions::NAN();
329
        }
330
331 2
        return Functions::VALUE();
332
    }
333
334
    /**
335
     * GCD.
336
     *
337
     * Returns the greatest common divisor of a series of numbers.
338
     * The greatest common divisor is the largest integer that divides both
339
     *        number1 and number2 without a remainder.
340
     *
341
     * Excel Function:
342
     *        GCD(number1[,number2[, ...]])
343
     *
344
     * @category Mathematical and Trigonometric Functions
345
     *
346
     * @param mixed $args Data values
347
     *
348
     * @return int Greatest Common Divisor
349
     */
350 24
    public static function GCD(...$args)
351
    {
352 24
        $returnValue = 1;
353 24
        $allValuesFactors = [];
354
        // Loop through arguments
355 24
        foreach (Functions::flattenArray($args) as $value) {
356 24
            if (!is_numeric($value)) {
357 1
                return Functions::VALUE();
358 24
            } elseif ($value == 0) {
359 4
                continue;
360 23
            } elseif ($value < 0) {
361 1
                return Functions::NAN();
362
            }
363 23
            $myFactors = self::factors($value);
364 23
            $myCountedFactors = array_count_values($myFactors);
365 23
            $allValuesFactors[] = $myCountedFactors;
366
        }
367 22
        $allValuesCount = count($allValuesFactors);
368 22
        if ($allValuesCount == 0) {
369 1
            return 0;
370
        }
371
372 21
        $mergedArray = $allValuesFactors[0];
373 21
        for ($i = 1; $i < $allValuesCount; ++$i) {
374 19
            $mergedArray = array_intersect_key($mergedArray, $allValuesFactors[$i]);
375
        }
376 21
        $mergedArrayValues = count($mergedArray);
377 21
        if ($mergedArrayValues == 0) {
378 6
            return $returnValue;
379 15
        } elseif ($mergedArrayValues > 1) {
380 3
            foreach ($mergedArray as $mergedKey => $mergedValue) {
381 3
                foreach ($allValuesFactors as $highestPowerTest) {
382 3
                    foreach ($highestPowerTest as $testKey => $testValue) {
383 3
                        if (($testKey == $mergedKey) && ($testValue < $mergedValue)) {
384 2
                            $mergedArray[$mergedKey] = $testValue;
385 3
                            $mergedValue = $testValue;
386
                        }
387
                    }
388
                }
389
            }
390
391 3
            $returnValue = 1;
392 3
            foreach ($mergedArray as $key => $value) {
393 3
                $returnValue *= pow($key, $value);
394
            }
395
396 3
            return $returnValue;
397
        }
398 12
        $keys = array_keys($mergedArray);
399 12
        $key = $keys[0];
400 12
        $value = $mergedArray[$key];
401 12
        foreach ($allValuesFactors as $testValue) {
402 12
            foreach ($testValue as $mergedKey => $mergedValue) {
403 12
                if (($mergedKey == $key) && ($mergedValue < $value)) {
404 12
                    $value = $mergedValue;
405
                }
406
            }
407
        }
408
409 12
        return pow($key, $value);
410
    }
411
412
    /**
413
     * INT.
414
     *
415
     * Casts a floating point value to an integer
416
     *
417
     * Excel Function:
418
     *        INT(number)
419
     *
420
     * @category Mathematical and Trigonometric Functions
421
     *
422
     * @param float $number Number to cast to an integer
423
     *
424
     * @return int Integer value
425
     */
426 19 View Code Duplication
    public static function INT($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
    {
428 19
        $number = Functions::flattenSingleValue($number);
429
430 19
        if (is_null($number)) {
431 1
            return 0;
432 18
        } elseif (is_bool($number)) {
433 2
            return (int) $number;
434
        }
435 16
        if (is_numeric($number)) {
436 15
            return (int) floor($number);
437
        }
438
439 1
        return Functions::VALUE();
440
    }
441
442
    /**
443
     * LCM.
444
     *
445
     * Returns the lowest common multiplier of a series of numbers
446
     * The least common multiple is the smallest positive integer that is a multiple
447
     * of all integer arguments number1, number2, and so on. Use LCM to add fractions
448
     * with different denominators.
449
     *
450
     * Excel Function:
451
     *        LCM(number1[,number2[, ...]])
452
     *
453
     * @category Mathematical and Trigonometric Functions
454
     *
455
     * @param mixed $args Data values
456
     *
457
     * @return int Lowest Common Multiplier
458
     */
459 12
    public static function LCM(...$args)
460
    {
461 12
        $returnValue = 1;
462 12
        $allPoweredFactors = [];
463
        // Loop through arguments
464 12
        foreach (Functions::flattenArray($args) as $value) {
465 12
            if (!is_numeric($value)) {
466 1
                return Functions::VALUE();
467
            }
468 12
            if ($value == 0) {
469 1
                return 0;
470 12
            } elseif ($value < 0) {
471 1
                return Functions::NAN();
472
            }
473 12
            $myFactors = self::factors(floor($value));
474 12
            $myCountedFactors = array_count_values($myFactors);
475 12
            $myPoweredFactors = [];
476 12
            foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) {
477 12
                $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor, $myCountedPower);
478
            }
479 12
            foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
480 12
                if (isset($allPoweredFactors[$myPoweredValue])) {
481 6
                    if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
482 4
                        $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
483
                    }
484
                } else {
485 12
                    $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
486
                }
487
            }
488
        }
489 9
        foreach ($allPoweredFactors as $allPoweredFactor) {
490 9
            $returnValue *= (int) $allPoweredFactor;
491
        }
492
493 9
        return $returnValue;
494
    }
495
496
    /**
497
     * LOG_BASE.
498
     *
499
     * Returns the logarithm of a number to a specified base. The default base is 10.
500
     *
501
     * Excel Function:
502
     *        LOG(number[,base])
503
     *
504
     * @category Mathematical and Trigonometric Functions
505
     *
506
     * @param float $number The positive real number for which you want the logarithm
507
     * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10.
508
     *
509
     * @return float
510
     */
511 69
    public static function logBase($number = null, $base = 10)
512
    {
513 69
        $number = Functions::flattenSingleValue($number);
514 69
        $base = (is_null($base)) ? 10 : (float) Functions::flattenSingleValue($base);
515
516 69
        if ((!is_numeric($base)) || (!is_numeric($number))) {
517 2
            return Functions::VALUE();
518
        }
519 67
        if (($base <= 0) || ($number <= 0)) {
520 18
            return Functions::NAN();
521
        }
522
523 49
        return log($number, $base);
524
    }
525
526
    /**
527
     * MDETERM.
528
     *
529
     * Returns the matrix determinant of an array.
530
     *
531
     * Excel Function:
532
     *        MDETERM(array)
533
     *
534
     * @category Mathematical and Trigonometric Functions
535
     *
536
     * @param array $matrixValues A matrix of values
537
     *
538
     * @return float
539
     */
540 14 View Code Duplication
    public static function MDETERM($matrixValues)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
541
    {
542 14
        $matrixData = [];
543 14
        if (!is_array($matrixValues)) {
544
            $matrixValues = [[$matrixValues]];
545
        }
546
547 14
        $row = $maxColumn = 0;
548 14
        foreach ($matrixValues as $matrixRow) {
549 14
            if (!is_array($matrixRow)) {
550
                $matrixRow = [$matrixRow];
551
            }
552 14
            $column = 0;
553 14
            foreach ($matrixRow as $matrixCell) {
554 14
                if ((is_string($matrixCell)) || ($matrixCell === null)) {
555 1
                    return Functions::VALUE();
556
                }
557 14
                $matrixData[$column][$row] = $matrixCell;
558 14
                ++$column;
559
            }
560 14
            if ($column > $maxColumn) {
561 14
                $maxColumn = $column;
562
            }
563 14
            ++$row;
564
        }
565 13
        if ($row != $maxColumn) {
566 1
            return Functions::VALUE();
567
        }
568
569
        try {
570 12
            $matrix = new \PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix($matrixData);
571
572 12
            return $matrix->det();
573
        } catch (\PhpOffice\PhpSpreadsheet\Exception $ex) {
574
            return Functions::VALUE();
575
        }
576
    }
577
578
    /**
579
     * MINVERSE.
580
     *
581
     * Returns the inverse matrix for the matrix stored in an array.
582
     *
583
     * Excel Function:
584
     *        MINVERSE(array)
585
     *
586
     * @category Mathematical and Trigonometric Functions
587
     *
588
     * @param array $matrixValues A matrix of values
589
     *
590
     * @return array
591
     */
592 View Code Duplication
    public static function MINVERSE($matrixValues)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
593
    {
594
        $matrixData = [];
595
        if (!is_array($matrixValues)) {
596
            $matrixValues = [[$matrixValues]];
597
        }
598
599
        $row = $maxColumn = 0;
600
        foreach ($matrixValues as $matrixRow) {
601
            if (!is_array($matrixRow)) {
602
                $matrixRow = [$matrixRow];
603
            }
604
            $column = 0;
605
            foreach ($matrixRow as $matrixCell) {
606
                if ((is_string($matrixCell)) || ($matrixCell === null)) {
607
                    return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \PhpOffice\PhpSpr...ion\Functions::VALUE(); (string) is incompatible with the return type documented by PhpOffice\PhpSpreadsheet...tion\MathTrig::MINVERSE of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
608
                }
609
                $matrixData[$column][$row] = $matrixCell;
610
                ++$column;
611
            }
612
            if ($column > $maxColumn) {
613
                $maxColumn = $column;
614
            }
615
            ++$row;
616
        }
617
        foreach ($matrixValues as $matrixRow) {
618
            if (count($matrixRow) != $maxColumn) {
619
                return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \PhpOffice\PhpSpr...ion\Functions::VALUE(); (string) is incompatible with the return type documented by PhpOffice\PhpSpreadsheet...tion\MathTrig::MINVERSE of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
620
            }
621
        }
622
623
        try {
624
            $matrix = new \PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix($matrixData);
625
626
            return $matrix->inverse()->getArray();
627
        } catch (\PhpOffice\PhpSpreadsheet\Exception $ex) {
628
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \PhpOffice\PhpSpr...ion\Functions::VALUE(); (string) is incompatible with the return type documented by PhpOffice\PhpSpreadsheet...tion\MathTrig::MINVERSE of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
629
        }
630
    }
631
632
    /**
633
     * MMULT.
634
     *
635
     * @param array $matrixData1 A matrix of values
636
     * @param array $matrixData2 A matrix of values
637
     *
638
     * @return array
639
     */
640
    public static function MMULT($matrixData1, $matrixData2)
641
    {
642
        $matrixAData = $matrixBData = [];
643
        if (!is_array($matrixData1)) {
644
            $matrixData1 = [[$matrixData1]];
645
        }
646
        if (!is_array($matrixData2)) {
647
            $matrixData2 = [[$matrixData2]];
648
        }
649
650
        try {
651
            $rowA = 0;
652 View Code Duplication
            foreach ($matrixData1 as $matrixRow) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
653
                if (!is_array($matrixRow)) {
654
                    $matrixRow = [$matrixRow];
655
                }
656
                $columnA = 0;
657
                foreach ($matrixRow as $matrixCell) {
658
                    if ((!is_numeric($matrixCell)) || ($matrixCell === null)) {
659
                        return Functions::VALUE();
660
                    }
661
                    $matrixAData[$rowA][$columnA] = $matrixCell;
662
                    ++$columnA;
663
                }
664
                ++$rowA;
665
            }
666
            $matrixA = new \PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix($matrixAData);
667
            $rowB = 0;
668 View Code Duplication
            foreach ($matrixData2 as $matrixRow) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
669
                if (!is_array($matrixRow)) {
670
                    $matrixRow = [$matrixRow];
671
                }
672
                $columnB = 0;
673
                foreach ($matrixRow as $matrixCell) {
674
                    if ((!is_numeric($matrixCell)) || ($matrixCell === null)) {
675
                        return Functions::VALUE();
676
                    }
677
                    $matrixBData[$rowB][$columnB] = $matrixCell;
678
                    ++$columnB;
679
                }
680
                ++$rowB;
681
            }
682
            $matrixB = new \PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix($matrixBData);
683
684
            if ($columnA != $rowB) {
0 ignored issues
show
Bug introduced by
The variable $columnA does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
685
                return Functions::VALUE();
686
            }
687
688
            return $matrixA->times($matrixB)->getArray();
689
        } catch (\PhpOffice\PhpSpreadsheet\Exception $ex) {
690
            return Functions::VALUE();
691
        }
692
    }
693
694
    /**
695
     * MOD.
696
     *
697
     * @param int $a Dividend
698
     * @param int $b Divisor
699
     *
700
     * @return int Remainder
701
     */
702 10
    public static function MOD($a = 1, $b = 1)
703
    {
704 10
        $a = (float) Functions::flattenSingleValue($a);
705 10
        $b = (float) Functions::flattenSingleValue($b);
706
707 10
        if ($b == 0.0) {
708 1
            return Functions::DIV0();
709 9
        } elseif (($a < 0.0) && ($b > 0.0)) {
710 1
            return $b - fmod(abs($a), $b);
711 8
        } elseif (($a > 0.0) && ($b < 0.0)) {
712 2
            return $b + fmod($a, abs($b));
713
        }
714
715 6
        return fmod($a, $b);
716
    }
717
718
    /**
719
     * MROUND.
720
     *
721
     * Rounds a number to the nearest multiple of a specified value
722
     *
723
     * @param float $number Number to round
724
     * @param int $multiple Multiple to which you want to round $number
725
     *
726
     * @return float Rounded Number
727
     */
728 13
    public static function MROUND($number, $multiple)
729
    {
730 13
        $number = Functions::flattenSingleValue($number);
731 13
        $multiple = Functions::flattenSingleValue($multiple);
732
733 13
        if ((is_numeric($number)) && (is_numeric($multiple))) {
734 11
            if ($multiple == 0) {
735 1
                return 0;
736
            }
737 10
            if ((self::SIGN($number)) == (self::SIGN($multiple))) {
738 9
                $multiplier = 1 / $multiple;
739
740 9
                return round($number * $multiplier) / $multiplier;
741
            }
742
743 1
            return Functions::NAN();
744
        }
745
746 2
        return Functions::VALUE();
747
    }
748
749
    /**
750
     * MULTINOMIAL.
751
     *
752
     * Returns the ratio of the factorial of a sum of values to the product of factorials.
753
     *
754
     * @param array of mixed Data Series
755
     *
756
     * @return float
757
     */
758 2
    public static function MULTINOMIAL(...$args)
759
    {
760 2
        $summer = 0;
761 2
        $divisor = 1;
762
        // Loop through arguments
763 2
        foreach (Functions::flattenArray($args) as $arg) {
764
            // Is it a numeric value?
765 2
            if (is_numeric($arg)) {
766 2
                if ($arg < 1) {
767
                    return Functions::NAN();
768
                }
769 2
                $summer += floor($arg);
770 2
                $divisor *= self::FACT($arg);
771
            } else {
772
                return Functions::VALUE();
773
            }
774
        }
775
776
        // Return
777 2
        if ($summer > 0) {
778 2
            $summer = self::FACT($summer);
779
780 2
            return $summer / $divisor;
781
        }
782
783
        return 0;
784
    }
785
786
    /**
787
     * ODD.
788
     *
789
     * Returns number rounded up to the nearest odd integer.
790
     *
791
     * @param float $number Number to round
792
     *
793
     * @return int Rounded Number
794
     */
795 13
    public static function ODD($number)
796
    {
797 13
        $number = Functions::flattenSingleValue($number);
798
799 13
        if (is_null($number)) {
800 1
            return 1;
801 12
        } elseif (is_bool($number)) {
802 2
            return 1;
803 10
        } elseif (is_numeric($number)) {
804 9
            $significance = self::SIGN($number);
805 9
            if ($significance == 0) {
806 1
                return 1;
807
            }
808
809 8
            $result = self::CEILING($number, $significance);
810 8
            if ($result == self::EVEN($result)) {
811 5
                $result += $significance;
812
            }
813
814 8
            return (int) $result;
815
        }
816
817 1
        return Functions::VALUE();
818
    }
819
820
    /**
821
     * POWER.
822
     *
823
     * Computes x raised to the power y.
824
     *
825
     * @param float $x
826
     * @param float $y
827
     *
828
     * @return float
829
     */
830 81
    public static function POWER($x = 0, $y = 2)
831
    {
832 81
        $x = Functions::flattenSingleValue($x);
833 81
        $y = Functions::flattenSingleValue($y);
834
835
        // Validate parameters
836 81
        if ($x == 0.0 && $y == 0.0) {
837 1
            return Functions::NAN();
838 80
        } elseif ($x == 0.0 && $y < 0.0) {
839 2
            return Functions::DIV0();
840
        }
841
842
        // Return
843 78
        $result = pow($x, $y);
844
845 78
        return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN();
846
    }
847
848
    /**
849
     * PRODUCT.
850
     *
851
     * PRODUCT returns the product of all the values and cells referenced in the argument list.
852
     *
853
     * Excel Function:
854
     *        PRODUCT(value1[,value2[, ...]])
855
     *
856
     * @category Mathematical and Trigonometric Functions
857
     *
858
     * @param mixed $args Data values
859
     *
860
     * @return float
861
     */
862 7 View Code Duplication
    public static function PRODUCT(...$args)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
863
    {
864
        // Return value
865 7
        $returnValue = null;
866
867
        // Loop through arguments
868 7
        foreach (Functions::flattenArray($args) as $arg) {
869
            // Is it a numeric value?
870 7
            if ((is_numeric($arg)) && (!is_string($arg))) {
871 7
                if (is_null($returnValue)) {
872 7
                    $returnValue = $arg;
873
                } else {
874 7
                    $returnValue *= $arg;
875
                }
876
            }
877
        }
878
879
        // Return
880 7
        if (is_null($returnValue)) {
881
            return 0;
882
        }
883
884 7
        return $returnValue;
885
    }
886
887
    /**
888
     * QUOTIENT.
889
     *
890
     * QUOTIENT function returns the integer portion of a division. Numerator is the divided number
891
     *        and denominator is the divisor.
892
     *
893
     * Excel Function:
894
     *        QUOTIENT(value1[,value2[, ...]])
895
     *
896
     * @category Mathematical and Trigonometric Functions
897
     *
898
     * @param mixed $args Data values
899
     *
900
     * @return float
901
     */
902 6
    public static function QUOTIENT(...$args)
903
    {
904
        // Return value
905 6
        $returnValue = null;
906
907
        // Loop through arguments
908 6
        foreach (Functions::flattenArray($args) as $arg) {
909
            // Is it a numeric value?
910 6
            if ((is_numeric($arg)) && (!is_string($arg))) {
911 6
                if (is_null($returnValue)) {
912 6
                    $returnValue = ($arg == 0) ? 0 : $arg;
913
                } else {
914 6
                    if (($returnValue == 0) || ($arg == 0)) {
915
                        $returnValue = 0;
916
                    } else {
917 6
                        $returnValue /= $arg;
918
                    }
919
                }
920
            }
921
        }
922
923
        // Return
924 6
        return (int) $returnValue;
925
    }
926
927
    /**
928
     * RAND.
929
     *
930
     * @param int $min Minimal value
931
     * @param int $max Maximal value
932
     *
933
     * @return int Random number
934
     */
935 1
    public static function RAND($min = 0, $max = 0)
936
    {
937 1
        $min = Functions::flattenSingleValue($min);
938 1
        $max = Functions::flattenSingleValue($max);
939
940 1
        if ($min == 0 && $max == 0) {
941
            return (mt_rand(0, 10000000)) / 10000000;
942
        }
943
944 1
        return mt_rand($min, $max);
945
    }
946
947 5
    public static function ROMAN($aValue, $style = 0)
948
    {
949 5
        $aValue = Functions::flattenSingleValue($aValue);
950 5
        $style = (is_null($style)) ? 0 : (int) Functions::flattenSingleValue($style);
0 ignored issues
show
Unused Code introduced by
$style is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
951 5 View Code Duplication
        if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
952
            return Functions::VALUE();
953
        }
954 5
        $aValue = (int) $aValue;
955 5
        if ($aValue == 0) {
956
            return '';
957
        }
958
959 5
        $mill = ['', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM'];
960 5
        $cent = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'];
961 5
        $tens = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'];
962 5
        $ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'];
963
964 5
        $roman = '';
965 5
        while ($aValue > 5999) {
966
            $roman .= 'M';
967
            $aValue -= 1000;
968
        }
969 5
        $m = self::romanCut($aValue, 1000);
970 5
        $aValue %= 1000;
971 5
        $c = self::romanCut($aValue, 100);
972 5
        $aValue %= 100;
973 5
        $t = self::romanCut($aValue, 10);
974 5
        $aValue %= 10;
975
976 5
        return $roman . $mill[$m] . $cent[$c] . $tens[$t] . $ones[$aValue];
977
    }
978
979
    /**
980
     * ROUNDUP.
981
     *
982
     * Rounds a number up to a specified number of decimal places
983
     *
984
     * @param float $number Number to round
985
     * @param int $digits Number of digits to which you want to round $number
986
     *
987
     * @return float Rounded Number
988
     */
989 14 View Code Duplication
    public static function ROUNDUP($number, $digits)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
990
    {
991 14
        $number = Functions::flattenSingleValue($number);
992 14
        $digits = Functions::flattenSingleValue($digits);
993
994 14
        if ((is_numeric($number)) && (is_numeric($digits))) {
995 12
            $significance = pow(10, (int) $digits);
996 12
            if ($number < 0.0) {
997 2
                return floor($number * $significance) / $significance;
998
            }
999
1000 10
            return ceil($number * $significance) / $significance;
1001
        }
1002
1003 2
        return Functions::VALUE();
1004
    }
1005
1006
    /**
1007
     * ROUNDDOWN.
1008
     *
1009
     * Rounds a number down to a specified number of decimal places
1010
     *
1011
     * @param float $number Number to round
1012
     * @param int $digits Number of digits to which you want to round $number
1013
     *
1014
     * @return float Rounded Number
1015
     */
1016 14 View Code Duplication
    public static function ROUNDDOWN($number, $digits)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1017
    {
1018 14
        $number = Functions::flattenSingleValue($number);
1019 14
        $digits = Functions::flattenSingleValue($digits);
1020
1021 14
        if ((is_numeric($number)) && (is_numeric($digits))) {
1022 12
            $significance = pow(10, (int) $digits);
1023 12
            if ($number < 0.0) {
1024 2
                return ceil($number * $significance) / $significance;
1025
            }
1026
1027 10
            return floor($number * $significance) / $significance;
1028
        }
1029
1030 2
        return Functions::VALUE();
1031
    }
1032
1033
    /**
1034
     * SERIESSUM.
1035
     *
1036
     * Returns the sum of a power series
1037
     *
1038
     * @param float $x Input value to the power series
0 ignored issues
show
Bug introduced by
There is no parameter named $x. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1039
     * @param float $n Initial power to which you want to raise $x
0 ignored issues
show
Bug introduced by
There is no parameter named $n. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1040
     * @param float $m Step by which to increase $n for each term in the series
0 ignored issues
show
Bug introduced by
There is no parameter named $m. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1041
     * @param array of mixed Data Series
1042
     *
1043
     * @return float
1044
     */
1045 2
    public static function SERIESSUM(...$args)
1046
    {
1047 2
        $returnValue = 0;
1048
1049
        // Loop through arguments
1050 2
        $aArgs = Functions::flattenArray($args);
1051
1052 2
        $x = array_shift($aArgs);
1053 2
        $n = array_shift($aArgs);
1054 2
        $m = array_shift($aArgs);
1055
1056 2
        if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) {
1057
            // Calculate
1058 2
            $i = 0;
1059 2
            foreach ($aArgs as $arg) {
1060
                // Is it a numeric value?
1061 2
                if ((is_numeric($arg)) && (!is_string($arg))) {
1062 2
                    $returnValue += $arg * pow($x, $n + ($m * $i++));
1063
                } else {
1064
                    return Functions::VALUE();
1065
                }
1066
            }
1067
1068 2
            return $returnValue;
1069
        }
1070
1071
        return Functions::VALUE();
1072
    }
1073
1074
    /**
1075
     * SIGN.
1076
     *
1077
     * Determines the sign of a number. Returns 1 if the number is positive, zero (0)
1078
     *        if the number is 0, and -1 if the number is negative.
1079
     *
1080
     * @param float $number Number to round
1081
     *
1082
     * @return int sign value
1083
     */
1084 72 View Code Duplication
    public static function SIGN($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1085
    {
1086 72
        $number = Functions::flattenSingleValue($number);
1087
1088 72
        if (is_bool($number)) {
1089 2
            return (int) $number;
1090
        }
1091 70
        if (is_numeric($number)) {
1092 69
            if ($number == 0.0) {
1093 4
                return 0;
1094
            }
1095
1096 65
            return $number / abs($number);
1097
        }
1098
1099 1
        return Functions::VALUE();
1100
    }
1101
1102
    /**
1103
     * SQRTPI.
1104
     *
1105
     * Returns the square root of (number * pi).
1106
     *
1107
     * @param float $number Number
1108
     *
1109
     * @return float Square Root of Number * Pi
1110
     */
1111 15 View Code Duplication
    public static function SQRTPI($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1112
    {
1113 15
        $number = Functions::flattenSingleValue($number);
1114
1115 15
        if (is_numeric($number)) {
1116 14
            if ($number < 0) {
1117 3
                return Functions::NAN();
1118
            }
1119
1120 11
            return sqrt($number * M_PI);
1121
        }
1122
1123 1
        return Functions::VALUE();
1124
    }
1125
1126
    /**
1127
     * SUBTOTAL.
1128
     *
1129
     * Returns a subtotal in a list or database.
1130
     *
1131
     * @param int the number 1 to 11 that specifies which function to
1132
     *                    use in calculating subtotals within a list
1133
     * @param array of mixed Data Series
1134
     *
1135
     * @return float
1136
     */
1137
    public static function SUBTOTAL(...$args)
1138
    {
1139
        $aArgs = Functions::flattenArray($args);
1140
1141
        // Calculate
1142
        $subtotal = array_shift($aArgs);
1143
1144
        if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
1145
            switch ($subtotal) {
1146
                case 1:
1147
                    return Statistical::AVERAGE($aArgs);
1148
                case 2:
1149
                    return Statistical::COUNT($aArgs);
1150
                case 3:
1151
                    return Statistical::COUNTA($aArgs);
1152
                case 4:
1153
                    return Statistical::MAX($aArgs);
1154
                case 5:
1155
                    return Statistical::MIN($aArgs);
1156
                case 6:
1157
                    return self::PRODUCT($aArgs);
1158
                case 7:
1159
                    return Statistical::STDEV($aArgs);
1160
                case 8:
1161
                    return Statistical::STDEVP($aArgs);
1162
                case 9:
1163
                    return self::SUM($aArgs);
1164
                case 10:
1165
                    return Statistical::VARFunc($aArgs);
1166
                case 11:
1167
                    return Statistical::VARP($aArgs);
1168
            }
1169
        }
1170
1171
        return Functions::VALUE();
1172
    }
1173
1174
    /**
1175
     * SUM.
1176
     *
1177
     * SUM computes the sum of all the values and cells referenced in the argument list.
1178
     *
1179
     * Excel Function:
1180
     *        SUM(value1[,value2[, ...]])
1181
     *
1182
     * @category Mathematical and Trigonometric Functions
1183
     *
1184
     * @param mixed $args Data values
1185
     *
1186
     * @return float
1187
     */
1188 19
    public static function SUM(...$args)
1189
    {
1190 19
        $returnValue = 0;
1191
1192
        // Loop through the arguments
1193 19
        foreach (Functions::flattenArray($args) as $arg) {
1194
            // Is it a numeric value?
1195 19
            if ((is_numeric($arg)) && (!is_string($arg))) {
1196 19
                $returnValue += $arg;
1197
            }
1198
        }
1199
1200 19
        return $returnValue;
1201
    }
1202
1203
    /**
1204
     * SUMIF.
1205
     *
1206
     * Counts the number of cells that contain numbers within the list of arguments
1207
     *
1208
     * Excel Function:
1209
     *        SUMIF(value1[,value2[, ...]],condition)
1210
     *
1211
     * @category Mathematical and Trigonometric Functions
1212
     *
1213
     * @param mixed $aArgs Data values
1214
     * @param string $condition the criteria that defines which cells will be summed
1215
     * @param mixed $aArgs
1216
     * @param mixed $sumArgs
1217
     *
1218
     * @return float
1219
     */
1220 5
    public static function SUMIF($aArgs, $condition, $sumArgs = [])
1221
    {
1222 5
        $returnValue = 0;
1223
1224 5
        $aArgs = Functions::flattenArray($aArgs);
1225 5
        $sumArgs = Functions::flattenArray($sumArgs);
1226 5
        if (empty($sumArgs)) {
1227 1
            $sumArgs = $aArgs;
1228
        }
1229 5
        $condition = Functions::ifCondition($condition);
1230
        // Loop through arguments
1231 5
        foreach ($aArgs as $key => $arg) {
1232 5
            if (!is_numeric($arg)) {
1233 4
                $arg = str_replace('"', '""', $arg);
1234 4
                $arg = \PhpOffice\PhpSpreadsheet\Calculation::wrapResult(strtoupper($arg));
1235
            }
1236
1237 5
            $testCondition = '=' . $arg . $condition;
1238 5
            if (\PhpOffice\PhpSpreadsheet\Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
1239
                // Is it a value within our criteria
1240 5
                $returnValue += $sumArgs[$key];
1241
            }
1242
        }
1243
1244 5
        return $returnValue;
1245
    }
1246
1247
    /**
1248
     * SUMIFS.
1249
     *
1250
     *    Counts the number of cells that contain numbers within the list of arguments
1251
     *
1252
     *    Excel Function:
1253
     *        SUMIFS(value1[,value2[, ...]],condition)
1254
     *
1255
     *    @category Mathematical and Trigonometric Functions
1256
     *
1257
     * @param mixed $args Data values
1258
     * @param string $condition the criteria that defines which cells will be summed
0 ignored issues
show
Bug introduced by
There is no parameter named $condition. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1259
     *
1260
     * @return float
1261
     */
1262
    public static function SUMIFS(...$args)
1263
    {
1264
        $arrayList = $args;
1265
1266
        // Return value
1267
        $returnValue = 0;
1268
1269
        $sumArgs = Functions::flattenArray(array_shift($arrayList));
1270
1271
        while (count($arrayList) > 0) {
1272
            $aArgsArray[] = Functions::flattenArray(array_shift($arrayList));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$aArgsArray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $aArgsArray = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1273
            $conditions[] = Functions::ifCondition(array_shift($arrayList));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$conditions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $conditions = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1274
        }
1275
1276
        // Loop through each set of arguments and conditions
1277
        foreach ($conditions as $index => $condition) {
0 ignored issues
show
Bug introduced by
The variable $conditions does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1278
            $aArgs = $aArgsArray[$index];
0 ignored issues
show
Bug introduced by
The variable $aArgsArray does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1279
1280
            // Loop through arguments
1281
            foreach ($aArgs as $key => $arg) {
1282
                if (!is_numeric($arg)) {
1283
                    $arg = \PhpOffice\PhpSpreadsheet\Calculation::wrapResult(strtoupper($arg));
1284
                }
1285
                $testCondition = '=' . $arg . $condition;
1286
                if (\PhpOffice\PhpSpreadsheet\Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
1287
                    // Is it a value within our criteria
1288
                    $returnValue += $sumArgs[$key];
1289
                }
1290
            }
1291
        }
1292
1293
        // Return
1294
        return $returnValue;
1295
    }
1296
1297
    /**
1298
     * SUMPRODUCT.
1299
     *
1300
     * Excel Function:
1301
     *        SUMPRODUCT(value1[,value2[, ...]])
1302
     *
1303
     * @category Mathematical and Trigonometric Functions
1304
     *
1305
     * @param mixed $args Data values
1306
     *
1307
     * @return float
1308
     */
1309
    public static function SUMPRODUCT(...$args)
1310
    {
1311
        $arrayList = $args;
1312
1313
        $wrkArray = Functions::flattenArray(array_shift($arrayList));
1314
        $wrkCellCount = count($wrkArray);
1315
1316
        for ($i = 0; $i < $wrkCellCount; ++$i) {
1317
            if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) {
1318
                $wrkArray[$i] = 0;
1319
            }
1320
        }
1321
1322
        foreach ($arrayList as $matrixData) {
1323
            $array2 = Functions::flattenArray($matrixData);
1324
            $count = count($array2);
1325
            if ($wrkCellCount != $count) {
1326
                return Functions::VALUE();
1327
            }
1328
1329
            foreach ($array2 as $i => $val) {
1330
                if ((!is_numeric($val)) || (is_string($val))) {
1331
                    $val = 0;
1332
                }
1333
                $wrkArray[$i] *= $val;
1334
            }
1335
        }
1336
1337
        return array_sum($wrkArray);
1338
    }
1339
1340
    /**
1341
     * SUMSQ.
1342
     *
1343
     * SUMSQ returns the sum of the squares of the arguments
1344
     *
1345
     * Excel Function:
1346
     *        SUMSQ(value1[,value2[, ...]])
1347
     *
1348
     * @category Mathematical and Trigonometric Functions
1349
     *
1350
     * @param mixed $args Data values
1351
     *
1352
     * @return float
1353
     */
1354 7 View Code Duplication
    public static function SUMSQ(...$args)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1355
    {
1356 7
        $returnValue = 0;
1357
1358
        // Loop through arguments
1359 7
        foreach (Functions::flattenArray($args) as $arg) {
1360
            // Is it a numeric value?
1361 7
            if ((is_numeric($arg)) && (!is_string($arg))) {
1362 7
                $returnValue += ($arg * $arg);
1363
            }
1364
        }
1365
1366 7
        return $returnValue;
1367
    }
1368
1369
    /**
1370
     * SUMX2MY2.
1371
     *
1372
     * @param mixed[] $matrixData1 Matrix #1
1373
     * @param mixed[] $matrixData2 Matrix #2
1374
     *
1375
     * @return float
1376
     */
1377 View Code Duplication
    public static function SUMX2MY2($matrixData1, $matrixData2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1378
    {
1379
        $array1 = Functions::flattenArray($matrixData1);
1380
        $array2 = Functions::flattenArray($matrixData2);
1381
        $count = min(count($array1), count($array2));
1382
1383
        $result = 0;
1384
        for ($i = 0; $i < $count; ++$i) {
1385
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1386
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1387
                $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]);
1388
            }
1389
        }
1390
1391
        return $result;
1392
    }
1393
1394
    /**
1395
     * SUMX2PY2.
1396
     *
1397
     * @param mixed[] $matrixData1 Matrix #1
1398
     * @param mixed[] $matrixData2 Matrix #2
1399
     *
1400
     * @return float
1401
     */
1402 View Code Duplication
    public static function SUMX2PY2($matrixData1, $matrixData2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1403
    {
1404
        $array1 = Functions::flattenArray($matrixData1);
1405
        $array2 = Functions::flattenArray($matrixData2);
1406
        $count = min(count($array1), count($array2));
1407
1408
        $result = 0;
1409
        for ($i = 0; $i < $count; ++$i) {
1410
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1411
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1412
                $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]);
1413
            }
1414
        }
1415
1416
        return $result;
1417
    }
1418
1419
    /**
1420
     * SUMXMY2.
1421
     *
1422
     * @param mixed[] $matrixData1 Matrix #1
1423
     * @param mixed[] $matrixData2 Matrix #2
1424
     *
1425
     * @return float
1426
     */
1427 View Code Duplication
    public static function SUMXMY2($matrixData1, $matrixData2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1428
    {
1429
        $array1 = Functions::flattenArray($matrixData1);
1430
        $array2 = Functions::flattenArray($matrixData2);
1431
        $count = min(count($array1), count($array2));
1432
1433
        $result = 0;
1434
        for ($i = 0; $i < $count; ++$i) {
1435
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1436
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1437
                $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]);
1438
            }
1439
        }
1440
1441
        return $result;
1442
    }
1443
1444
    /**
1445
     * TRUNC.
1446
     *
1447
     * Truncates value to the number of fractional digits by number_digits.
1448
     *
1449
     * @param float $value
1450
     * @param int $digits
1451
     *
1452
     * @return float Truncated value
1453
     */
1454 19
    public static function TRUNC($value = 0, $digits = 0)
1455
    {
1456 19
        $value = Functions::flattenSingleValue($value);
1457 19
        $digits = Functions::flattenSingleValue($digits);
1458
1459
        // Validate parameters
1460 19
        if ((!is_numeric($value)) || (!is_numeric($digits))) {
1461 2
            return Functions::VALUE();
1462
        }
1463 17
        $digits = floor($digits);
1464
1465
        // Truncate
1466 17
        $adjust = pow(10, $digits);
1467
1468 17
        if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) {
1469 2
            return $value;
1470
        }
1471
1472 15
        return ((int) ($value * $adjust)) / $adjust;
1473
    }
1474
}
1475