Completed
Push — develop ( 77b3c1...097d34 )
by Adrien
31:27
created

MathTrig::POWER()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 9
nc 6
nop 2
dl 0
loc 17
rs 8.2222
c 0
b 0
f 0
ccs 9
cts 9
cp 1
crap 7
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use PhpOffice\PhpSpreadsheet\Calculation;
6
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
7
use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;
8
9
/**
10
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
11
 *
12
 * This library is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU Lesser General Public
14
 * License as published by the Free Software Foundation; either
15
 * version 2.1 of the License, or (at your option) any later version.
16
 *
17
 * This library is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
 * Lesser General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Lesser General Public
23
 * License along with this library; if not, write to the Free Software
24
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25
 *
26
 * @category    PhpSpreadsheet
27
 *
28
 * @copyright   Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
29
 * @license     http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
30
 */
31
class MathTrig
32
{
33
    //
34
    //    Private method to return an array of the factors of the input value
35
    //
36 35
    private static function factors($value)
37
    {
38 35
        $startVal = floor(sqrt($value));
39
40 35
        $factorArray = [];
41 35
        for ($i = $startVal; $i > 1; --$i) {
42 33
            if (($value % $i) == 0) {
43 23
                $factorArray = array_merge($factorArray, self::factors($value / $i));
44 23
                $factorArray = array_merge($factorArray, self::factors($i));
45 23
                if ($i <= sqrt($value)) {
46 23
                    break;
47
                }
48
            }
49
        }
50 35
        if (!empty($factorArray)) {
51 23
            rsort($factorArray);
52
53 23
            return $factorArray;
54
        }
55
56 35
        return [(int) $value];
57
    }
58
59 5
    private static function romanCut($num, $n)
60
    {
61 5
        return ($num - ($num % $n)) / $n;
62
    }
63
64
    /**
65
     * ATAN2.
66
     *
67
     * This function calculates the arc tangent of the two variables x and y. It is similar to
68
     *        calculating the arc tangent of y ÷ x, except that the signs of both arguments are used
69
     *        to determine the quadrant of the result.
70
     * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a
71
     *        point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between
72
     *        -pi and pi, excluding -pi.
73
     *
74
     * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard
75
     *        PHP atan2() function, so we need to reverse them here before calling the PHP atan() function.
76
     *
77
     * Excel Function:
78
     *        ATAN2(xCoordinate,yCoordinate)
79
     *
80
     * @category Mathematical and Trigonometric Functions
81
     *
82
     * @param float $xCoordinate the x-coordinate of the point
83
     * @param float $yCoordinate the y-coordinate of the point
84
     *
85
     * @return float the inverse tangent of the specified x- and y-coordinates
86
     */
87 16
    public static function ATAN2($xCoordinate = null, $yCoordinate = null)
88
    {
89 16
        $xCoordinate = Functions::flattenSingleValue($xCoordinate);
90 16
        $yCoordinate = Functions::flattenSingleValue($yCoordinate);
91
92 16
        $xCoordinate = ($xCoordinate !== null) ? $xCoordinate : 0.0;
93 16
        $yCoordinate = ($yCoordinate !== null) ? $yCoordinate : 0.0;
94
95 16
        if (((is_numeric($xCoordinate)) || (is_bool($xCoordinate))) &&
96 16
            ((is_numeric($yCoordinate))) || (is_bool($yCoordinate))) {
97 15
            $xCoordinate = (float) $xCoordinate;
98 15
            $yCoordinate = (float) $yCoordinate;
99
100 15
            if (($xCoordinate == 0) && ($yCoordinate == 0)) {
101 1
                return Functions::DIV0();
102
            }
103
104 14
            return atan2($yCoordinate, $xCoordinate);
105
        }
106
107 1
        return Functions::VALUE();
108
    }
109
110
    /**
111
     * CEILING.
112
     *
113
     * Returns number rounded up, away from zero, to the nearest multiple of significance.
114
     *        For example, if you want to avoid using pennies in your prices and your product is
115
     *        priced at $4.42, use the formula =CEILING(4.42,0.05) to round prices up to the
116
     *        nearest nickel.
117
     *
118
     * Excel Function:
119
     *        CEILING(number[,significance])
120
     *
121
     * @category Mathematical and Trigonometric Functions
122
     *
123
     * @param float $number the number you want to round
124
     * @param float $significance the multiple to which you want to round
125
     *
126
     * @return float Rounded Number
127
     */
128 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...
129
    {
130 43
        $number = Functions::flattenSingleValue($number);
131 43
        $significance = Functions::flattenSingleValue($significance);
132
133 43
        if ((is_null($significance)) &&
134 43
            (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
135
            $significance = $number / abs($number);
136
        }
137
138 43
        if ((is_numeric($number)) && (is_numeric($significance))) {
139 41
            if (($number == 0.0) || ($significance == 0.0)) {
140 3
                return 0.0;
141 38
            } elseif (self::SIGN($number) == self::SIGN($significance)) {
142 35
                return ceil($number / $significance) * $significance;
143
            }
144
145 3
            return Functions::NAN();
146
        }
147
148 2
        return Functions::VALUE();
149
    }
150
151
    /**
152
     * COMBIN.
153
     *
154
     * Returns the number of combinations for a given number of items. Use COMBIN to
155
     *        determine the total possible number of groups for a given number of items.
156
     *
157
     * Excel Function:
158
     *        COMBIN(numObjs,numInSet)
159
     *
160
     * @category Mathematical and Trigonometric Functions
161
     *
162
     * @param int $numObjs Number of different objects
163
     * @param int $numInSet Number of objects in each combination
164
     *
165
     * @return int Number of combinations
166
     */
167 24
    public static function COMBIN($numObjs, $numInSet)
168
    {
169 24
        $numObjs = Functions::flattenSingleValue($numObjs);
170 24
        $numInSet = Functions::flattenSingleValue($numInSet);
171
172 24
        if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
173 23
            if ($numObjs < $numInSet) {
174 3
                return Functions::NAN();
175 20
            } elseif ($numInSet < 0) {
176 2
                return Functions::NAN();
177
            }
178
179 18
            return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
180
        }
181
182 1
        return Functions::VALUE();
183
    }
184
185
    /**
186
     * EVEN.
187
     *
188
     * Returns number rounded up to the nearest even integer.
189
     * You can use this function for processing items that come in twos. For example,
190
     *        a packing crate accepts rows of one or two items. The crate is full when
191
     *        the number of items, rounded up to the nearest two, matches the crate's
192
     *        capacity.
193
     *
194
     * Excel Function:
195
     *        EVEN(number)
196
     *
197
     * @category Mathematical and Trigonometric Functions
198
     *
199
     * @param float $number Number to round
200
     *
201
     * @return int Rounded Number
202
     */
203 25
    public static function EVEN($number)
204
    {
205 25
        $number = Functions::flattenSingleValue($number);
206
207 25
        if (is_null($number)) {
208 1
            return 0;
209 24
        } elseif (is_bool($number)) {
210 2
            $number = (int) $number;
211
        }
212
213 24
        if (is_numeric($number)) {
214 23
            $significance = 2 * self::SIGN($number);
215
216 23
            return (int) self::CEILING($number, $significance);
217
        }
218
219 1
        return Functions::VALUE();
220
    }
221
222
    /**
223
     * FACT.
224
     *
225
     * Returns the factorial of a number.
226
     * The factorial of a number is equal to 1*2*3*...* number.
227
     *
228
     * Excel Function:
229
     *        FACT(factVal)
230
     *
231
     * @category Mathematical and Trigonometric Functions
232
     *
233
     * @param float $factVal Factorial Value
234
     *
235
     * @return int Factorial
236
     */
237 145
    public static function FACT($factVal)
238
    {
239 145
        $factVal = Functions::flattenSingleValue($factVal);
240
241 145
        if (is_numeric($factVal)) {
242 144
            if ($factVal < 0) {
243 1
                return Functions::NAN();
244
            }
245 143
            $factLoop = floor($factVal);
246 143
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
247
                if ($factVal > $factLoop) {
248
                    return Functions::NAN();
249
                }
250
            }
251
252 143
            $factorial = 1;
253 143
            while ($factLoop > 1) {
254 71
                $factorial *= $factLoop--;
255
            }
256
257 143
            return $factorial;
258
        }
259
260 1
        return Functions::VALUE();
261
    }
262
263
    /**
264
     * FACTDOUBLE.
265
     *
266
     * Returns the double factorial of a number.
267
     *
268
     * Excel Function:
269
     *        FACTDOUBLE(factVal)
270
     *
271
     * @category Mathematical and Trigonometric Functions
272
     *
273
     * @param float $factVal Factorial Value
274
     *
275
     * @return int Double Factorial
276
     */
277 8
    public static function FACTDOUBLE($factVal)
278
    {
279 8
        $factLoop = Functions::flattenSingleValue($factVal);
280
281 8
        if (is_numeric($factLoop)) {
282 7
            $factLoop = floor($factLoop);
283 7
            if ($factVal < 0) {
284 1
                return Functions::NAN();
285
            }
286 6
            $factorial = 1;
287 6
            while ($factLoop > 1) {
288 5
                $factorial *= $factLoop--;
289 5
                --$factLoop;
290
            }
291
292 6
            return $factorial;
293
        }
294
295 1
        return Functions::VALUE();
296
    }
297
298
    /**
299
     * FLOOR.
300
     *
301
     * Rounds number down, toward zero, to the nearest multiple of significance.
302
     *
303
     * Excel Function:
304
     *        FLOOR(number[,significance])
305
     *
306
     * @category Mathematical and Trigonometric Functions
307
     *
308
     * @param float $number Number to round
309
     * @param float $significance Significance
310
     *
311
     * @return float Rounded Number
312
     */
313 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...
314
    {
315 11
        $number = Functions::flattenSingleValue($number);
316 11
        $significance = Functions::flattenSingleValue($significance);
317
318 11
        if ((is_null($significance)) &&
319 11
            (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
320
            $significance = $number / abs($number);
321
        }
322
323 11
        if ((is_numeric($number)) && (is_numeric($significance))) {
324 9
            if ($significance == 0.0) {
325 1
                return Functions::DIV0();
326 8
            } elseif ($number == 0.0) {
327
                return 0.0;
328 8
            } elseif (self::SIGN($number) == self::SIGN($significance)) {
329 6
                return floor($number / $significance) * $significance;
330
            }
331
332 2
            return Functions::NAN();
333
        }
334
335 2
        return Functions::VALUE();
336
    }
337
338
    /**
339
     * GCD.
340
     *
341
     * Returns the greatest common divisor of a series of numbers.
342
     * The greatest common divisor is the largest integer that divides both
343
     *        number1 and number2 without a remainder.
344
     *
345
     * Excel Function:
346
     *        GCD(number1[,number2[, ...]])
347
     *
348
     * @category Mathematical and Trigonometric Functions
349
     *
350
     * @param mixed ...$args Data values
351
     *
352
     * @return int Greatest Common Divisor
353
     */
354 24
    public static function GCD(...$args)
355
    {
356 24
        $returnValue = 1;
357 24
        $allValuesFactors = [];
358
        // Loop through arguments
359 24
        foreach (Functions::flattenArray($args) as $value) {
360 24
            if (!is_numeric($value)) {
361 1
                return Functions::VALUE();
362 24
            } elseif ($value == 0) {
363 4
                continue;
364 23
            } elseif ($value < 0) {
365 1
                return Functions::NAN();
366
            }
367 23
            $myFactors = self::factors($value);
368 23
            $myCountedFactors = array_count_values($myFactors);
369 23
            $allValuesFactors[] = $myCountedFactors;
370
        }
371 22
        $allValuesCount = count($allValuesFactors);
372 22
        if ($allValuesCount == 0) {
373 1
            return 0;
374
        }
375
376 21
        $mergedArray = $allValuesFactors[0];
377 21
        for ($i = 1; $i < $allValuesCount; ++$i) {
378 19
            $mergedArray = array_intersect_key($mergedArray, $allValuesFactors[$i]);
379
        }
380 21
        $mergedArrayValues = count($mergedArray);
381 21
        if ($mergedArrayValues == 0) {
382 6
            return $returnValue;
383 15
        } elseif ($mergedArrayValues > 1) {
384 3
            foreach ($mergedArray as $mergedKey => $mergedValue) {
385 3
                foreach ($allValuesFactors as $highestPowerTest) {
386 3
                    foreach ($highestPowerTest as $testKey => $testValue) {
387 3
                        if (($testKey == $mergedKey) && ($testValue < $mergedValue)) {
388 2
                            $mergedArray[$mergedKey] = $testValue;
389 3
                            $mergedValue = $testValue;
390
                        }
391
                    }
392
                }
393
            }
394
395 3
            $returnValue = 1;
396 3
            foreach ($mergedArray as $key => $value) {
397 3
                $returnValue *= pow($key, $value);
398
            }
399
400 3
            return $returnValue;
401
        }
402 12
        $keys = array_keys($mergedArray);
403 12
        $key = $keys[0];
404 12
        $value = $mergedArray[$key];
405 12
        foreach ($allValuesFactors as $testValue) {
406 12
            foreach ($testValue as $mergedKey => $mergedValue) {
407 12
                if (($mergedKey == $key) && ($mergedValue < $value)) {
408 12
                    $value = $mergedValue;
409
                }
410
            }
411
        }
412
413 12
        return pow($key, $value);
414
    }
415
416
    /**
417
     * INT.
418
     *
419
     * Casts a floating point value to an integer
420
     *
421
     * Excel Function:
422
     *        INT(number)
423
     *
424
     * @category Mathematical and Trigonometric Functions
425
     *
426
     * @param float $number Number to cast to an integer
427
     *
428
     * @return int Integer value
429
     */
430 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...
431
    {
432 19
        $number = Functions::flattenSingleValue($number);
433
434 19
        if (is_null($number)) {
435 1
            return 0;
436 18
        } elseif (is_bool($number)) {
437 2
            return (int) $number;
438
        }
439 16
        if (is_numeric($number)) {
440 15
            return (int) floor($number);
441
        }
442
443 1
        return Functions::VALUE();
444
    }
445
446
    /**
447
     * LCM.
448
     *
449
     * Returns the lowest common multiplier of a series of numbers
450
     * The least common multiple is the smallest positive integer that is a multiple
451
     * of all integer arguments number1, number2, and so on. Use LCM to add fractions
452
     * with different denominators.
453
     *
454
     * Excel Function:
455
     *        LCM(number1[,number2[, ...]])
456
     *
457
     * @category Mathematical and Trigonometric Functions
458
     *
459
     * @param mixed ...$args Data values
460
     *
461
     * @return int Lowest Common Multiplier
462
     */
463 12
    public static function LCM(...$args)
464
    {
465 12
        $returnValue = 1;
466 12
        $allPoweredFactors = [];
467
        // Loop through arguments
468 12
        foreach (Functions::flattenArray($args) as $value) {
469 12
            if (!is_numeric($value)) {
470 1
                return Functions::VALUE();
471
            }
472 12
            if ($value == 0) {
473 1
                return 0;
474 12
            } elseif ($value < 0) {
475 1
                return Functions::NAN();
476
            }
477 12
            $myFactors = self::factors(floor($value));
478 12
            $myCountedFactors = array_count_values($myFactors);
479 12
            $myPoweredFactors = [];
480 12
            foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) {
481 12
                $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor, $myCountedPower);
482
            }
483 12
            foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
484 12
                if (isset($allPoweredFactors[$myPoweredValue])) {
485 6
                    if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
486 6
                        $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
487
                    }
488
                } else {
489 12
                    $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
490
                }
491
            }
492
        }
493 9
        foreach ($allPoweredFactors as $allPoweredFactor) {
494 9
            $returnValue *= (int) $allPoweredFactor;
495
        }
496
497 9
        return $returnValue;
498
    }
499
500
    /**
501
     * LOG_BASE.
502
     *
503
     * Returns the logarithm of a number to a specified base. The default base is 10.
504
     *
505
     * Excel Function:
506
     *        LOG(number[,base])
507
     *
508
     * @category Mathematical and Trigonometric Functions
509
     *
510
     * @param float $number The positive real number for which you want the logarithm
511
     * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10.
512
     *
513
     * @return float
514
     */
515 69
    public static function logBase($number = null, $base = 10)
516
    {
517 69
        $number = Functions::flattenSingleValue($number);
518 69
        $base = (is_null($base)) ? 10 : (float) Functions::flattenSingleValue($base);
519
520 69
        if ((!is_numeric($base)) || (!is_numeric($number))) {
521 2
            return Functions::VALUE();
522
        }
523 67
        if (($base <= 0) || ($number <= 0)) {
524 18
            return Functions::NAN();
525
        }
526
527 49
        return log($number, $base);
528
    }
529
530
    /**
531
     * MDETERM.
532
     *
533
     * Returns the matrix determinant of an array.
534
     *
535
     * Excel Function:
536
     *        MDETERM(array)
537
     *
538
     * @category Mathematical and Trigonometric Functions
539
     *
540
     * @param array $matrixValues A matrix of values
541
     *
542
     * @return float
543
     */
544 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...
545
    {
546 14
        $matrixData = [];
547 14
        if (!is_array($matrixValues)) {
548
            $matrixValues = [[$matrixValues]];
549
        }
550
551 14
        $row = $maxColumn = 0;
552 14
        foreach ($matrixValues as $matrixRow) {
553 14
            if (!is_array($matrixRow)) {
554
                $matrixRow = [$matrixRow];
555
            }
556 14
            $column = 0;
557 14
            foreach ($matrixRow as $matrixCell) {
558 14
                if ((is_string($matrixCell)) || ($matrixCell === null)) {
559 1
                    return Functions::VALUE();
560
                }
561 14
                $matrixData[$column][$row] = $matrixCell;
562 14
                ++$column;
563
            }
564 14
            if ($column > $maxColumn) {
565 14
                $maxColumn = $column;
566
            }
567 14
            ++$row;
568
        }
569 13
        if ($row != $maxColumn) {
570 1
            return Functions::VALUE();
571
        }
572
573
        try {
574 12
            $matrix = new Matrix($matrixData);
575
576 12
            return $matrix->det();
577
        } catch (PhpSpreadsheetException $ex) {
578
            return Functions::VALUE();
579
        }
580
    }
581
582
    /**
583
     * MINVERSE.
584
     *
585
     * Returns the inverse matrix for the matrix stored in an array.
586
     *
587
     * Excel Function:
588
     *        MINVERSE(array)
589
     *
590
     * @category Mathematical and Trigonometric Functions
591
     *
592
     * @param array $matrixValues A matrix of values
593
     *
594
     * @return array
595
     */
596 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...
597
    {
598
        $matrixData = [];
599
        if (!is_array($matrixValues)) {
600
            $matrixValues = [[$matrixValues]];
601
        }
602
603
        $row = $maxColumn = 0;
604
        foreach ($matrixValues as $matrixRow) {
605
            if (!is_array($matrixRow)) {
606
                $matrixRow = [$matrixRow];
607
            }
608
            $column = 0;
609
            foreach ($matrixRow as $matrixCell) {
610
                if ((is_string($matrixCell)) || ($matrixCell === null)) {
611
                    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...
612
                }
613
                $matrixData[$column][$row] = $matrixCell;
614
                ++$column;
615
            }
616
            if ($column > $maxColumn) {
617
                $maxColumn = $column;
618
            }
619
            ++$row;
620
        }
621
        foreach ($matrixValues as $matrixRow) {
622
            if (count($matrixRow) != $maxColumn) {
623
                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...
624
            }
625
        }
626
627
        try {
628
            $matrix = new Matrix($matrixData);
629
630
            return $matrix->inverse()->getArray();
631
        } catch (PhpSpreadsheetException $ex) {
632
            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...
633
        }
634
    }
635
636
    /**
637
     * MMULT.
638
     *
639
     * @param array $matrixData1 A matrix of values
640
     * @param array $matrixData2 A matrix of values
641
     *
642
     * @return array
643
     */
644
    public static function MMULT($matrixData1, $matrixData2)
645
    {
646
        $matrixAData = $matrixBData = [];
647
        if (!is_array($matrixData1)) {
648
            $matrixData1 = [[$matrixData1]];
649
        }
650
        if (!is_array($matrixData2)) {
651
            $matrixData2 = [[$matrixData2]];
652
        }
653
654
        try {
655
            $rowA = 0;
656 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...
657
                if (!is_array($matrixRow)) {
658
                    $matrixRow = [$matrixRow];
659
                }
660
                $columnA = 0;
661
                foreach ($matrixRow as $matrixCell) {
662
                    if ((!is_numeric($matrixCell)) || ($matrixCell === null)) {
663
                        return Functions::VALUE();
664
                    }
665
                    $matrixAData[$rowA][$columnA] = $matrixCell;
666
                    ++$columnA;
667
                }
668
                ++$rowA;
669
            }
670
            $matrixA = new Matrix($matrixAData);
671
            $rowB = 0;
672 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...
673
                if (!is_array($matrixRow)) {
674
                    $matrixRow = [$matrixRow];
675
                }
676
                $columnB = 0;
677
                foreach ($matrixRow as $matrixCell) {
678
                    if ((!is_numeric($matrixCell)) || ($matrixCell === null)) {
679
                        return Functions::VALUE();
680
                    }
681
                    $matrixBData[$rowB][$columnB] = $matrixCell;
682
                    ++$columnB;
683
                }
684
                ++$rowB;
685
            }
686
            $matrixB = new Matrix($matrixBData);
687
688
            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...
689
                return Functions::VALUE();
690
            }
691
692
            return $matrixA->times($matrixB)->getArray();
693
        } catch (PhpSpreadsheetException $ex) {
694
            return Functions::VALUE();
695
        }
696
    }
697
698
    /**
699
     * MOD.
700
     *
701
     * @param int $a Dividend
702
     * @param int $b Divisor
703
     *
704
     * @return int Remainder
705
     */
706 10
    public static function MOD($a = 1, $b = 1)
707
    {
708 10
        $a = (float) Functions::flattenSingleValue($a);
709 10
        $b = (float) Functions::flattenSingleValue($b);
710
711 10
        if ($b == 0.0) {
712 1
            return Functions::DIV0();
713 9
        } elseif (($a < 0.0) && ($b > 0.0)) {
714 1
            return $b - fmod(abs($a), $b);
715 8
        } elseif (($a > 0.0) && ($b < 0.0)) {
716 2
            return $b + fmod($a, abs($b));
717
        }
718
719 6
        return fmod($a, $b);
720
    }
721
722
    /**
723
     * MROUND.
724
     *
725
     * Rounds a number to the nearest multiple of a specified value
726
     *
727
     * @param float $number Number to round
728
     * @param int $multiple Multiple to which you want to round $number
729
     *
730
     * @return float Rounded Number
731
     */
732 13
    public static function MROUND($number, $multiple)
733
    {
734 13
        $number = Functions::flattenSingleValue($number);
735 13
        $multiple = Functions::flattenSingleValue($multiple);
736
737 13
        if ((is_numeric($number)) && (is_numeric($multiple))) {
738 11
            if ($multiple == 0) {
739 1
                return 0;
740
            }
741 10
            if ((self::SIGN($number)) == (self::SIGN($multiple))) {
742 9
                $multiplier = 1 / $multiple;
743
744 9
                return round($number * $multiplier) / $multiplier;
745
            }
746
747 1
            return Functions::NAN();
748
        }
749
750 2
        return Functions::VALUE();
751
    }
752
753
    /**
754
     * MULTINOMIAL.
755
     *
756
     * Returns the ratio of the factorial of a sum of values to the product of factorials.
757
     *
758
     * @param array of mixed Data Series
759
     *
760
     * @return float
761
     */
762 2
    public static function MULTINOMIAL(...$args)
763
    {
764 2
        $summer = 0;
765 2
        $divisor = 1;
766
        // Loop through arguments
767 2
        foreach (Functions::flattenArray($args) as $arg) {
768
            // Is it a numeric value?
769 2
            if (is_numeric($arg)) {
770 2
                if ($arg < 1) {
771
                    return Functions::NAN();
772
                }
773 2
                $summer += floor($arg);
774 2
                $divisor *= self::FACT($arg);
775
            } else {
776 2
                return Functions::VALUE();
777
            }
778
        }
779
780
        // Return
781 2
        if ($summer > 0) {
782 2
            $summer = self::FACT($summer);
783
784 2
            return $summer / $divisor;
785
        }
786
787
        return 0;
788
    }
789
790
    /**
791
     * ODD.
792
     *
793
     * Returns number rounded up to the nearest odd integer.
794
     *
795
     * @param float $number Number to round
796
     *
797
     * @return int Rounded Number
798
     */
799 13
    public static function ODD($number)
800
    {
801 13
        $number = Functions::flattenSingleValue($number);
802
803 13
        if (is_null($number)) {
804 1
            return 1;
805 12
        } elseif (is_bool($number)) {
806 2
            return 1;
807 10
        } elseif (is_numeric($number)) {
808 9
            $significance = self::SIGN($number);
809 9
            if ($significance == 0) {
810 1
                return 1;
811
            }
812
813 8
            $result = self::CEILING($number, $significance);
814 8
            if ($result == self::EVEN($result)) {
815 5
                $result += $significance;
816
            }
817
818 8
            return (int) $result;
819
        }
820
821 1
        return Functions::VALUE();
822
    }
823
824
    /**
825
     * POWER.
826
     *
827
     * Computes x raised to the power y.
828
     *
829
     * @param float $x
830
     * @param float $y
831
     *
832
     * @return float
833
     */
834 81
    public static function POWER($x = 0, $y = 2)
835
    {
836 81
        $x = Functions::flattenSingleValue($x);
837 81
        $y = Functions::flattenSingleValue($y);
838
839
        // Validate parameters
840 81
        if ($x == 0.0 && $y == 0.0) {
841 1
            return Functions::NAN();
842 80
        } elseif ($x == 0.0 && $y < 0.0) {
843 2
            return Functions::DIV0();
844
        }
845
846
        // Return
847 78
        $result = pow($x, $y);
848
849 78
        return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN();
850
    }
851
852
    /**
853
     * PRODUCT.
854
     *
855
     * PRODUCT returns the product of all the values and cells referenced in the argument list.
856
     *
857
     * Excel Function:
858
     *        PRODUCT(value1[,value2[, ...]])
859
     *
860
     * @category Mathematical and Trigonometric Functions
861
     *
862
     * @param mixed ...$args Data values
863
     *
864
     * @return float
865
     */
866 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...
867
    {
868
        // Return value
869 7
        $returnValue = null;
870
871
        // Loop through arguments
872 7
        foreach (Functions::flattenArray($args) as $arg) {
873
            // Is it a numeric value?
874 7
            if ((is_numeric($arg)) && (!is_string($arg))) {
875 7
                if (is_null($returnValue)) {
876 7
                    $returnValue = $arg;
877
                } else {
878 7
                    $returnValue *= $arg;
879
                }
880
            }
881
        }
882
883
        // Return
884 7
        if (is_null($returnValue)) {
885
            return 0;
886
        }
887
888 7
        return $returnValue;
889
    }
890
891
    /**
892
     * QUOTIENT.
893
     *
894
     * QUOTIENT function returns the integer portion of a division. Numerator is the divided number
895
     *        and denominator is the divisor.
896
     *
897
     * Excel Function:
898
     *        QUOTIENT(value1[,value2[, ...]])
899
     *
900
     * @category Mathematical and Trigonometric Functions
901
     *
902
     * @param mixed ...$args Data values
903
     *
904
     * @return float
905
     */
906 6
    public static function QUOTIENT(...$args)
907
    {
908
        // Return value
909 6
        $returnValue = null;
910
911
        // Loop through arguments
912 6
        foreach (Functions::flattenArray($args) as $arg) {
913
            // Is it a numeric value?
914 6
            if ((is_numeric($arg)) && (!is_string($arg))) {
915 6
                if (is_null($returnValue)) {
916 6
                    $returnValue = ($arg == 0) ? 0 : $arg;
917
                } else {
918 6
                    if (($returnValue == 0) || ($arg == 0)) {
919
                        $returnValue = 0;
920
                    } else {
921 6
                        $returnValue /= $arg;
922
                    }
923
                }
924
            }
925
        }
926
927
        // Return
928 6
        return (int) $returnValue;
929
    }
930
931
    /**
932
     * RAND.
933
     *
934
     * @param int $min Minimal value
935
     * @param int $max Maximal value
936
     *
937
     * @return int Random number
938
     */
939 2
    public static function RAND($min = 0, $max = 0)
940
    {
941 2
        $min = Functions::flattenSingleValue($min);
942 2
        $max = Functions::flattenSingleValue($max);
943
944 2
        if ($min == 0 && $max == 0) {
945 1
            return (mt_rand(0, 10000000)) / 10000000;
946
        }
947
948 2
        return mt_rand($min, $max);
949
    }
950
951 5
    public static function ROMAN($aValue, $style = 0)
952
    {
953 5
        $aValue = Functions::flattenSingleValue($aValue);
954 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...
955 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...
956
            return Functions::VALUE();
957
        }
958 5
        $aValue = (int) $aValue;
959 5
        if ($aValue == 0) {
960
            return '';
961
        }
962
963 5
        $mill = ['', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM'];
964 5
        $cent = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'];
965 5
        $tens = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'];
966 5
        $ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'];
967
968 5
        $roman = '';
969 5
        while ($aValue > 5999) {
970
            $roman .= 'M';
971
            $aValue -= 1000;
972
        }
973 5
        $m = self::romanCut($aValue, 1000);
974 5
        $aValue %= 1000;
975 5
        $c = self::romanCut($aValue, 100);
976 5
        $aValue %= 100;
977 5
        $t = self::romanCut($aValue, 10);
978 5
        $aValue %= 10;
979
980 5
        return $roman . $mill[$m] . $cent[$c] . $tens[$t] . $ones[$aValue];
981
    }
982
983
    /**
984
     * ROUNDUP.
985
     *
986
     * Rounds a number up to a specified number of decimal places
987
     *
988
     * @param float $number Number to round
989
     * @param int $digits Number of digits to which you want to round $number
990
     *
991
     * @return float Rounded Number
992
     */
993 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...
994
    {
995 14
        $number = Functions::flattenSingleValue($number);
996 14
        $digits = Functions::flattenSingleValue($digits);
997
998 14
        if ((is_numeric($number)) && (is_numeric($digits))) {
999 12
            $significance = pow(10, (int) $digits);
1000 12
            if ($number < 0.0) {
1001 2
                return floor($number * $significance) / $significance;
1002
            }
1003
1004 10
            return ceil($number * $significance) / $significance;
1005
        }
1006
1007 2
        return Functions::VALUE();
1008
    }
1009
1010
    /**
1011
     * ROUNDDOWN.
1012
     *
1013
     * Rounds a number down to a specified number of decimal places
1014
     *
1015
     * @param float $number Number to round
1016
     * @param int $digits Number of digits to which you want to round $number
1017
     *
1018
     * @return float Rounded Number
1019
     */
1020 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...
1021
    {
1022 14
        $number = Functions::flattenSingleValue($number);
1023 14
        $digits = Functions::flattenSingleValue($digits);
1024
1025 14
        if ((is_numeric($number)) && (is_numeric($digits))) {
1026 12
            $significance = pow(10, (int) $digits);
1027 12
            if ($number < 0.0) {
1028 2
                return ceil($number * $significance) / $significance;
1029
            }
1030
1031 10
            return floor($number * $significance) / $significance;
1032
        }
1033
1034 2
        return Functions::VALUE();
1035
    }
1036
1037
    /**
1038
     * SERIESSUM.
1039
     *
1040
     * Returns the sum of a power series
1041
     *
1042
     * @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...
1043
     * @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...
1044
     * @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...
1045
     * @param array of mixed Data Series
1046
     *
1047
     * @return float
1048
     */
1049 2
    public static function SERIESSUM(...$args)
1050
    {
1051 2
        $returnValue = 0;
1052
1053
        // Loop through arguments
1054 2
        $aArgs = Functions::flattenArray($args);
1055
1056 2
        $x = array_shift($aArgs);
1057 2
        $n = array_shift($aArgs);
1058 2
        $m = array_shift($aArgs);
1059
1060 2
        if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) {
1061
            // Calculate
1062 2
            $i = 0;
1063 2
            foreach ($aArgs as $arg) {
1064
                // Is it a numeric value?
1065 2
                if ((is_numeric($arg)) && (!is_string($arg))) {
1066 2
                    $returnValue += $arg * pow($x, $n + ($m * $i++));
1067
                } else {
1068 2
                    return Functions::VALUE();
1069
                }
1070
            }
1071
1072 2
            return $returnValue;
1073
        }
1074
1075
        return Functions::VALUE();
1076
    }
1077
1078
    /**
1079
     * SIGN.
1080
     *
1081
     * Determines the sign of a number. Returns 1 if the number is positive, zero (0)
1082
     *        if the number is 0, and -1 if the number is negative.
1083
     *
1084
     * @param float $number Number to round
1085
     *
1086
     * @return int sign value
1087
     */
1088 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...
1089
    {
1090 72
        $number = Functions::flattenSingleValue($number);
1091
1092 72
        if (is_bool($number)) {
1093 2
            return (int) $number;
1094
        }
1095 70
        if (is_numeric($number)) {
1096 69
            if ($number == 0.0) {
1097 4
                return 0;
1098
            }
1099
1100 65
            return $number / abs($number);
1101
        }
1102
1103 1
        return Functions::VALUE();
1104
    }
1105
1106
    /**
1107
     * SQRTPI.
1108
     *
1109
     * Returns the square root of (number * pi).
1110
     *
1111
     * @param float $number Number
1112
     *
1113
     * @return float Square Root of Number * Pi
1114
     */
1115 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...
1116
    {
1117 15
        $number = Functions::flattenSingleValue($number);
1118
1119 15
        if (is_numeric($number)) {
1120 14
            if ($number < 0) {
1121 3
                return Functions::NAN();
1122
            }
1123
1124 11
            return sqrt($number * M_PI);
1125
        }
1126
1127 1
        return Functions::VALUE();
1128
    }
1129
1130
    protected static function filterHiddenArgs($cellReference, $args)
1131
    {
1132
        return array_filter(
1133
            $args,
1134
            function ($index) use ($cellReference) {
1135
                list(, $row, $column) = explode('.', $index);
1136
1137
                return $cellReference->getWorksheet()->getRowDimension($row)->getVisible() &&
1138
                    $cellReference->getWorksheet()->getColumnDimension($column)->getVisible();
1139
            },
1140
            ARRAY_FILTER_USE_KEY
1141
        );
1142
    }
1143
1144
    /**
1145
     * SUBTOTAL.
1146
     *
1147
     * Returns a subtotal in a list or database.
1148
     *
1149
     * @param int the number 1 to 11 that specifies which function to
1150
     *                    use in calculating subtotals within a list
1151
     * @param array of mixed Data Series
1152
     *
1153
     * @return float
1154
     */
1155
    public static function SUBTOTAL(...$args)
1156
    {
1157
        $aArgs = Functions::flattenArrayIndexed($args);
1158
        $cellReference = array_pop($aArgs);
1159
        $subtotal = array_shift($aArgs);
1160
1161
        // Calculate
1162
        if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
1163
            if ($subtotal > 100) {
1164
                $aArgs = self::filterHiddenArgs($cellReference, $aArgs);
1165
                $subtotal = $subtotal - 100;
1166
            }
1167
1168
            switch ($subtotal) {
1169
                case 1:
1170
                    return Statistical::AVERAGE($aArgs);
1171
                case 2:
1172
                    return Statistical::COUNT($aArgs);
1173
                case 3:
1174
                    return Statistical::COUNTA($aArgs);
1175
                case 4:
1176
                    return Statistical::MAX($aArgs);
1177
                case 5:
1178
                    return Statistical::MIN($aArgs);
1179
                case 6:
1180
                    return self::PRODUCT($aArgs);
1181
                case 7:
1182
                    return Statistical::STDEV($aArgs);
1183
                case 8:
1184
                    return Statistical::STDEVP($aArgs);
1185
                case 9:
1186
                    return self::SUM($aArgs);
1187
                case 10:
1188
                    return Statistical::VARFunc($aArgs);
1189
                case 11:
1190
                    return Statistical::VARP($aArgs);
1191
            }
1192
        }
1193
1194
        return Functions::VALUE();
1195
    }
1196
1197
    /**
1198
     * SUM.
1199
     *
1200
     * SUM computes the sum of all the values and cells referenced in the argument list.
1201
     *
1202
     * Excel Function:
1203
     *        SUM(value1[,value2[, ...]])
1204
     *
1205
     * @category Mathematical and Trigonometric Functions
1206
     *
1207
     * @param mixed ...$args Data values
1208
     *
1209
     * @return float
1210
     */
1211 21
    public static function SUM(...$args)
1212
    {
1213 21
        $returnValue = 0;
1214
1215
        // Loop through the arguments
1216 21
        foreach (Functions::flattenArray($args) as $arg) {
1217
            // Is it a numeric value?
1218 21
            if ((is_numeric($arg)) && (!is_string($arg))) {
1219 21
                $returnValue += $arg;
1220
            }
1221
        }
1222
1223 21
        return $returnValue;
1224
    }
1225
1226
    /**
1227
     * SUMIF.
1228
     *
1229
     * Counts the number of cells that contain numbers within the list of arguments
1230
     *
1231
     * Excel Function:
1232
     *        SUMIF(value1[,value2[, ...]],condition)
1233
     *
1234
     * @category Mathematical and Trigonometric Functions
1235
     *
1236
     * @param mixed $aArgs Data values
1237
     * @param string $condition the criteria that defines which cells will be summed
1238
     * @param mixed $aArgs
1239
     * @param mixed $sumArgs
1240
     *
1241
     * @return float
1242
     */
1243 5
    public static function SUMIF($aArgs, $condition, $sumArgs = [])
1244
    {
1245 5
        $returnValue = 0;
1246
1247 5
        $aArgs = Functions::flattenArray($aArgs);
1248 5
        $sumArgs = Functions::flattenArray($sumArgs);
1249 5
        if (empty($sumArgs)) {
1250 1
            $sumArgs = $aArgs;
1251
        }
1252 5
        $condition = Functions::ifCondition($condition);
1253
        // Loop through arguments
1254 5
        foreach ($aArgs as $key => $arg) {
1255 5
            if (!is_numeric($arg)) {
1256 4
                $arg = str_replace('"', '""', $arg);
1257 4
                $arg = Calculation::wrapResult(strtoupper($arg));
1258
            }
1259
1260 5
            $testCondition = '=' . $arg . $condition;
1261 5
            if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
1262
                // Is it a value within our criteria
1263 5
                $returnValue += $sumArgs[$key];
1264
            }
1265
        }
1266
1267 5
        return $returnValue;
1268
    }
1269
1270
    /**
1271
     * SUMIFS.
1272
     *
1273
     *    Counts the number of cells that contain numbers within the list of arguments
1274
     *
1275
     *    Excel Function:
1276
     *        SUMIFS(value1[,value2[, ...]],condition)
1277
     *
1278
     *    @category Mathematical and Trigonometric Functions
1279
     *
1280
     * @param mixed $args Data values
1281
     * @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...
1282
     *
1283
     * @return float
1284
     */
1285
    public static function SUMIFS(...$args)
1286
    {
1287
        $arrayList = $args;
1288
1289
        // Return value
1290
        $returnValue = 0;
1291
1292
        $sumArgs = Functions::flattenArray(array_shift($arrayList));
1293
1294
        while (count($arrayList) > 0) {
1295
            $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...
1296
            $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...
1297
        }
1298
1299
        // Loop through each set of arguments and conditions
1300
        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...
1301
            $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...
1302
1303
            // Loop through arguments
1304
            foreach ($aArgs as $key => $arg) {
1305
                if (!is_numeric($arg)) {
1306
                    $arg = Calculation::wrapResult(strtoupper($arg));
1307
                }
1308
                $testCondition = '=' . $arg . $condition;
1309
                if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
1310
                    // Is it a value within our criteria
1311
                    $returnValue += $sumArgs[$key];
1312
                }
1313
            }
1314
        }
1315
1316
        // Return
1317
        return $returnValue;
1318
    }
1319
1320
    /**
1321
     * SUMPRODUCT.
1322
     *
1323
     * Excel Function:
1324
     *        SUMPRODUCT(value1[,value2[, ...]])
1325
     *
1326
     * @category Mathematical and Trigonometric Functions
1327
     *
1328
     * @param mixed ...$args Data values
1329
     *
1330
     * @return float
1331
     */
1332
    public static function SUMPRODUCT(...$args)
1333
    {
1334
        $arrayList = $args;
1335
1336
        $wrkArray = Functions::flattenArray(array_shift($arrayList));
1337
        $wrkCellCount = count($wrkArray);
1338
1339
        for ($i = 0; $i < $wrkCellCount; ++$i) {
1340
            if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) {
1341
                $wrkArray[$i] = 0;
1342
            }
1343
        }
1344
1345
        foreach ($arrayList as $matrixData) {
1346
            $array2 = Functions::flattenArray($matrixData);
1347
            $count = count($array2);
1348
            if ($wrkCellCount != $count) {
1349
                return Functions::VALUE();
1350
            }
1351
1352
            foreach ($array2 as $i => $val) {
1353
                if ((!is_numeric($val)) || (is_string($val))) {
1354
                    $val = 0;
1355
                }
1356
                $wrkArray[$i] *= $val;
1357
            }
1358
        }
1359
1360
        return array_sum($wrkArray);
1361
    }
1362
1363
    /**
1364
     * SUMSQ.
1365
     *
1366
     * SUMSQ returns the sum of the squares of the arguments
1367
     *
1368
     * Excel Function:
1369
     *        SUMSQ(value1[,value2[, ...]])
1370
     *
1371
     * @category Mathematical and Trigonometric Functions
1372
     *
1373
     * @param mixed ...$args Data values
1374
     *
1375
     * @return float
1376
     */
1377 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...
1378
    {
1379 7
        $returnValue = 0;
1380
1381
        // Loop through arguments
1382 7
        foreach (Functions::flattenArray($args) as $arg) {
1383
            // Is it a numeric value?
1384 7
            if ((is_numeric($arg)) && (!is_string($arg))) {
1385 7
                $returnValue += ($arg * $arg);
1386
            }
1387
        }
1388
1389 7
        return $returnValue;
1390
    }
1391
1392
    /**
1393
     * SUMX2MY2.
1394
     *
1395
     * @param mixed[] $matrixData1 Matrix #1
1396
     * @param mixed[] $matrixData2 Matrix #2
1397
     *
1398
     * @return float
1399
     */
1400 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...
1401
    {
1402
        $array1 = Functions::flattenArray($matrixData1);
1403
        $array2 = Functions::flattenArray($matrixData2);
1404
        $count = min(count($array1), count($array2));
1405
1406
        $result = 0;
1407
        for ($i = 0; $i < $count; ++$i) {
1408
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1409
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1410
                $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]);
1411
            }
1412
        }
1413
1414
        return $result;
1415
    }
1416
1417
    /**
1418
     * SUMX2PY2.
1419
     *
1420
     * @param mixed[] $matrixData1 Matrix #1
1421
     * @param mixed[] $matrixData2 Matrix #2
1422
     *
1423
     * @return float
1424
     */
1425 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...
1426
    {
1427
        $array1 = Functions::flattenArray($matrixData1);
1428
        $array2 = Functions::flattenArray($matrixData2);
1429
        $count = min(count($array1), count($array2));
1430
1431
        $result = 0;
1432
        for ($i = 0; $i < $count; ++$i) {
1433
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1434
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1435
                $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]);
1436
            }
1437
        }
1438
1439
        return $result;
1440
    }
1441
1442
    /**
1443
     * SUMXMY2.
1444
     *
1445
     * @param mixed[] $matrixData1 Matrix #1
1446
     * @param mixed[] $matrixData2 Matrix #2
1447
     *
1448
     * @return float
1449
     */
1450 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...
1451
    {
1452
        $array1 = Functions::flattenArray($matrixData1);
1453
        $array2 = Functions::flattenArray($matrixData2);
1454
        $count = min(count($array1), count($array2));
1455
1456
        $result = 0;
1457
        for ($i = 0; $i < $count; ++$i) {
1458
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1459
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1460
                $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]);
1461
            }
1462
        }
1463
1464
        return $result;
1465
    }
1466
1467
    /**
1468
     * TRUNC.
1469
     *
1470
     * Truncates value to the number of fractional digits by number_digits.
1471
     *
1472
     * @param float $value
1473
     * @param int $digits
1474
     *
1475
     * @return float Truncated value
1476
     */
1477 19
    public static function TRUNC($value = 0, $digits = 0)
1478
    {
1479 19
        $value = Functions::flattenSingleValue($value);
1480 19
        $digits = Functions::flattenSingleValue($digits);
1481
1482
        // Validate parameters
1483 19
        if ((!is_numeric($value)) || (!is_numeric($digits))) {
1484 2
            return Functions::VALUE();
1485
        }
1486 17
        $digits = floor($digits);
1487
1488
        // Truncate
1489 17
        $adjust = pow(10, $digits);
1490
1491 17
        if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) {
1492 2
            return $value;
1493
        }
1494
1495 15
        return ((int) ($value * $adjust)) / $adjust;
1496
    }
1497
}
1498