Completed
Push — develop ( 782b4e...557e80 )
by Adrien
43:38
created

Calculation::dataTestReference()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 3
nop 1
dl 0
loc 15
ccs 9
cts 10
cp 0.9
crap 4.016
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack;
6
use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger;
7
use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack;
8
use PhpOffice\PhpSpreadsheet\Cell\Cell;
9
use PhpOffice\PhpSpreadsheet\NamedRange;
10
use PhpOffice\PhpSpreadsheet\Shared;
11
use PhpOffice\PhpSpreadsheet\Spreadsheet;
12
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
13
14
class Calculation
15
{
16
    /** Constants                */
17
    /** Regular Expressions        */
18
    //    Numeric operand
19
    const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?';
20
    //    String operand
21
    const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"';
22
    //    Opening bracket
23
    const CALCULATION_REGEXP_OPENBRACE = '\(';
24
    //    Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
25
    const CALCULATION_REGEXP_FUNCTION = '@?([A-Z][A-Z0-9\.]*)[\s]*\(';
26
    //    Cell reference (cell or range of cells, with or without a sheet reference)
27
    const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})';
28
    //    Named Range of cells
29
    const CALCULATION_REGEXP_NAMEDRANGE = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)';
30
    //    Error
31
    const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
32
33
    /** constants */
34
    const RETURN_ARRAY_AS_ERROR = 'error';
35
    const RETURN_ARRAY_AS_VALUE = 'value';
36
    const RETURN_ARRAY_AS_ARRAY = 'array';
37
38
    private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
39
40
    /**
41
     * Instance of this class.
42
     *
43
     * @var \PhpOffice\PhpSpreadsheet\Calculation
44
     */
45
    private static $instance;
46
47
    /**
48
     * Instance of the spreadsheet this Calculation Engine is using.
49
     *
50
     * @var PhpSpreadsheet
51
     */
52
    private $spreadsheet;
53
54
    /**
55
     * Calculation cache.
56
     *
57
     * @var array
58
     */
59
    private $calculationCache = [];
60
61
    /**
62
     * Calculation cache enabled.
63
     *
64
     * @var bool
65
     */
66
    private $calculationCacheEnabled = true;
67
68
    /**
69
     * List of operators that can be used within formulae
70
     * The true/false value indicates whether it is a binary operator or a unary operator.
71
     *
72
     * @var array
73
     */
74
    private static $operators = [
75
        '+' => true, '-' => true, '*' => true, '/' => true,
76
        '^' => true, '&' => true, '%' => false, '~' => false,
77
        '>' => true, '<' => true, '=' => true, '>=' => true,
78
        '<=' => true, '<>' => true, '|' => true, ':' => true,
79
    ];
80
81
    /**
82
     * List of binary operators (those that expect two operands).
83
     *
84
     * @var array
85
     */
86
    private static $binaryOperators = [
87
        '+' => true, '-' => true, '*' => true, '/' => true,
88
        '^' => true, '&' => true, '>' => true, '<' => true,
89
        '=' => true, '>=' => true, '<=' => true, '<>' => true,
90
        '|' => true, ':' => true,
91
    ];
92
93
    /**
94
     * The debug log generated by the calculation engine.
95
     *
96
     * @var Logger
97
     */
98
    private $debugLog;
99
100
    /**
101
     * Flag to determine how formula errors should be handled
102
     *        If true, then a user error will be triggered
103
     *        If false, then an exception will be thrown.
104
     *
105
     * @var bool
106
     */
107
    public $suppressFormulaErrors = false;
108
109
    /**
110
     * Error message for any error that was raised/thrown by the calculation engine.
111
     *
112
     * @var string
113
     */
114
    public $formulaError;
115
116
    /**
117
     * An array of the nested cell references accessed by the calculation engine, used for the debug log.
118
     *
119
     * @var array of string
120
     */
121
    private $cyclicReferenceStack;
122
123
    private $cellStack = [];
124
125
    /**
126
     * Current iteration counter for cyclic formulae
127
     * If the value is 0 (or less) then cyclic formulae will throw an exception,
128
     * otherwise they will iterate to the limit defined here before returning a result.
129
     *
130
     * @var int
131
     */
132
    private $cyclicFormulaCounter = 1;
133
134
    private $cyclicFormulaCell = '';
135
136
    /**
137
     * Number of iterations for cyclic formulae.
138
     *
139
     * @var int
140
     */
141
    public $cyclicFormulaCount = 1;
142
143
    /**
144
     * Epsilon Precision used for comparisons in calculations.
145
     *
146
     * @var float
147
     */
148
    private $delta = 0.1e-12;
149
150
    /**
151
     * The current locale setting.
152
     *
153
     * @var string
154
     */
155
    private static $localeLanguage = 'en_us'; //    US English    (default locale)
156
157
    /**
158
     * List of available locale settings
159
     * Note that this is read for the locale subdirectory only when requested.
160
     *
161
     * @var string[]
162
     */
163
    private static $validLocaleLanguages = [
164
        'en', //    English        (default language)
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
165
    ];
166
167
    /**
168
     * Locale-specific argument separator for function arguments.
169
     *
170
     * @var string
171
     */
172
    private static $localeArgumentSeparator = ',';
173
    private static $localeFunctions = [];
174
175
    /**
176
     * Locale-specific translations for Excel constants (True, False and Null).
177
     *
178
     * @var string[]
179
     */
180
    public static $localeBoolean = [
181
        'TRUE' => 'TRUE',
182
        'FALSE' => 'FALSE',
183
        'NULL' => 'NULL',
184
    ];
185
186
    /**
187
     * Excel constant string translations to their PHP equivalents
188
     * Constant conversion from text name/value to actual (datatyped) value.
189
     *
190
     * @var string[]
191
     */
192
    private static $excelConstants = [
193
        'TRUE' => true,
194
        'FALSE' => false,
195
        'NULL' => null,
196
    ];
197
198
    // PhpSpreadsheet functions
199
    private static $phpSpreadsheetFunctions = [
200
        'ABS' => [
201
            'category' => Category::CATEGORY_MATH_AND_TRIG,
202
            'functionCall' => 'abs',
203
            'argumentCount' => '1',
204
        ],
205
        'ACCRINT' => [
206
            'category' => Category::CATEGORY_FINANCIAL,
207
            'functionCall' => [Financial::class, 'ACCRINT'],
208
            'argumentCount' => '4-7',
209
        ],
210
        'ACCRINTM' => [
211
            'category' => Category::CATEGORY_FINANCIAL,
212
            'functionCall' => [Financial::class, 'ACCRINTM'],
213
            'argumentCount' => '3-5',
214
        ],
215
        'ACOS' => [
216
            'category' => Category::CATEGORY_MATH_AND_TRIG,
217
            'functionCall' => 'acos',
218
            'argumentCount' => '1',
219
        ],
220
        'ACOSH' => [
221
            'category' => Category::CATEGORY_MATH_AND_TRIG,
222
            'functionCall' => 'acosh',
223
            'argumentCount' => '1',
224
        ],
225
        'ADDRESS' => [
226
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
227
            'functionCall' => [LookupRef::class, 'cellAddress'],
228
            'argumentCount' => '2-5',
229
        ],
230
        'AMORDEGRC' => [
231
            'category' => Category::CATEGORY_FINANCIAL,
232
            'functionCall' => [Financial::class, 'AMORDEGRC'],
233
            'argumentCount' => '6,7',
234
        ],
235
        'AMORLINC' => [
236
            'category' => Category::CATEGORY_FINANCIAL,
237
            'functionCall' => [Financial::class, 'AMORLINC'],
238
            'argumentCount' => '6,7',
239
        ],
240
        'AND' => [
241
            'category' => Category::CATEGORY_LOGICAL,
242
            'functionCall' => [Logical::class, 'logicalAnd'],
243
            'argumentCount' => '1+',
244
        ],
245
        'AREAS' => [
246
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
247
            'functionCall' => [Functions::class, 'DUMMY'],
248
            'argumentCount' => '1',
249
        ],
250
        'ASC' => [
251
            'category' => Category::CATEGORY_TEXT_AND_DATA,
252
            'functionCall' => [Functions::class, 'DUMMY'],
253
            'argumentCount' => '1',
254
        ],
255
        'ASIN' => [
256
            'category' => Category::CATEGORY_MATH_AND_TRIG,
257
            'functionCall' => 'asin',
258
            'argumentCount' => '1',
259
        ],
260
        'ASINH' => [
261
            'category' => Category::CATEGORY_MATH_AND_TRIG,
262
            'functionCall' => 'asinh',
263
            'argumentCount' => '1',
264
        ],
265
        'ATAN' => [
266
            'category' => Category::CATEGORY_MATH_AND_TRIG,
267
            'functionCall' => 'atan',
268
            'argumentCount' => '1',
269
        ],
270
        'ATAN2' => [
271
            'category' => Category::CATEGORY_MATH_AND_TRIG,
272
            'functionCall' => [MathTrig::class, 'ATAN2'],
273
            'argumentCount' => '2',
274
        ],
275
        'ATANH' => [
276
            'category' => Category::CATEGORY_MATH_AND_TRIG,
277
            'functionCall' => 'atanh',
278
            'argumentCount' => '1',
279
        ],
280
        'AVEDEV' => [
281
            'category' => Category::CATEGORY_STATISTICAL,
282
            'functionCall' => [Statistical::class, 'AVEDEV'],
283
            'argumentCount' => '1+',
284
        ],
285
        'AVERAGE' => [
286
            'category' => Category::CATEGORY_STATISTICAL,
287
            'functionCall' => [Statistical::class, 'AVERAGE'],
288
            'argumentCount' => '1+',
289
        ],
290
        'AVERAGEA' => [
291
            'category' => Category::CATEGORY_STATISTICAL,
292
            'functionCall' => [Statistical::class, 'AVERAGEA'],
293
            'argumentCount' => '1+',
294
        ],
295
        'AVERAGEIF' => [
296
            'category' => Category::CATEGORY_STATISTICAL,
297
            'functionCall' => [Statistical::class, 'AVERAGEIF'],
298
            'argumentCount' => '2,3',
299
        ],
300
        'AVERAGEIFS' => [
301
            'category' => Category::CATEGORY_STATISTICAL,
302
            'functionCall' => [Functions::class, 'DUMMY'],
303
            'argumentCount' => '3+',
304
        ],
305
        'BAHTTEXT' => [
306
            'category' => Category::CATEGORY_TEXT_AND_DATA,
307
            'functionCall' => [Functions::class, 'DUMMY'],
308
            'argumentCount' => '1',
309
        ],
310
        'BESSELI' => [
311
            'category' => Category::CATEGORY_ENGINEERING,
312
            'functionCall' => [Engineering::class, 'BESSELI'],
313
            'argumentCount' => '2',
314
        ],
315
        'BESSELJ' => [
316
            'category' => Category::CATEGORY_ENGINEERING,
317
            'functionCall' => [Engineering::class, 'BESSELJ'],
318
            'argumentCount' => '2',
319
        ],
320
        'BESSELK' => [
321
            'category' => Category::CATEGORY_ENGINEERING,
322
            'functionCall' => [Engineering::class, 'BESSELK'],
323
            'argumentCount' => '2',
324
        ],
325
        'BESSELY' => [
326
            'category' => Category::CATEGORY_ENGINEERING,
327
            'functionCall' => [Engineering::class, 'BESSELY'],
328
            'argumentCount' => '2',
329
        ],
330
        'BETADIST' => [
331
            'category' => Category::CATEGORY_STATISTICAL,
332
            'functionCall' => [Statistical::class, 'BETADIST'],
333
            'argumentCount' => '3-5',
334
        ],
335
        'BETAINV' => [
336
            'category' => Category::CATEGORY_STATISTICAL,
337
            'functionCall' => [Statistical::class, 'BETAINV'],
338
            'argumentCount' => '3-5',
339
        ],
340
        'BIN2DEC' => [
341
            'category' => Category::CATEGORY_ENGINEERING,
342
            'functionCall' => [Engineering::class, 'BINTODEC'],
343
            'argumentCount' => '1',
344
        ],
345
        'BIN2HEX' => [
346
            'category' => Category::CATEGORY_ENGINEERING,
347
            'functionCall' => [Engineering::class, 'BINTOHEX'],
348
            'argumentCount' => '1,2',
349
        ],
350
        'BIN2OCT' => [
351
            'category' => Category::CATEGORY_ENGINEERING,
352
            'functionCall' => [Engineering::class, 'BINTOOCT'],
353
            'argumentCount' => '1,2',
354
        ],
355
        'BINOMDIST' => [
356
            'category' => Category::CATEGORY_STATISTICAL,
357
            'functionCall' => [Statistical::class, 'BINOMDIST'],
358
            'argumentCount' => '4',
359
        ],
360
        'CEILING' => [
361
            'category' => Category::CATEGORY_MATH_AND_TRIG,
362
            'functionCall' => [MathTrig::class, 'CEILING'],
363
            'argumentCount' => '2',
364
        ],
365
        'CELL' => [
366
            'category' => Category::CATEGORY_INFORMATION,
367
            'functionCall' => [Functions::class, 'DUMMY'],
368
            'argumentCount' => '1,2',
369
        ],
370
        'CHAR' => [
371
            'category' => Category::CATEGORY_TEXT_AND_DATA,
372
            'functionCall' => [TextData::class, 'CHARACTER'],
373
            'argumentCount' => '1',
374
        ],
375
        'CHIDIST' => [
376
            'category' => Category::CATEGORY_STATISTICAL,
377
            'functionCall' => [Statistical::class, 'CHIDIST'],
378
            'argumentCount' => '2',
379
        ],
380
        'CHIINV' => [
381
            'category' => Category::CATEGORY_STATISTICAL,
382
            'functionCall' => [Statistical::class, 'CHIINV'],
383
            'argumentCount' => '2',
384
        ],
385
        'CHITEST' => [
386
            'category' => Category::CATEGORY_STATISTICAL,
387
            'functionCall' => [Functions::class, 'DUMMY'],
388
            'argumentCount' => '2',
389
        ],
390
        'CHOOSE' => [
391
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
392
            'functionCall' => [LookupRef::class, 'CHOOSE'],
393
            'argumentCount' => '2+',
394
        ],
395
        'CLEAN' => [
396
            'category' => Category::CATEGORY_TEXT_AND_DATA,
397
            'functionCall' => [TextData::class, 'TRIMNONPRINTABLE'],
398
            'argumentCount' => '1',
399
        ],
400
        'CODE' => [
401
            'category' => Category::CATEGORY_TEXT_AND_DATA,
402
            'functionCall' => [TextData::class, 'ASCIICODE'],
403
            'argumentCount' => '1',
404
        ],
405
        'COLUMN' => [
406
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
407
            'functionCall' => [LookupRef::class, 'COLUMN'],
408
            'argumentCount' => '-1',
409
            'passByReference' => [true],
410
        ],
411
        'COLUMNS' => [
412
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
413
            'functionCall' => [LookupRef::class, 'COLUMNS'],
414
            'argumentCount' => '1',
415
        ],
416
        'COMBIN' => [
417
            'category' => Category::CATEGORY_MATH_AND_TRIG,
418
            'functionCall' => [MathTrig::class, 'COMBIN'],
419
            'argumentCount' => '2',
420
        ],
421
        'COMPLEX' => [
422
            'category' => Category::CATEGORY_ENGINEERING,
423
            'functionCall' => [Engineering::class, 'COMPLEX'],
424
            'argumentCount' => '2,3',
425
        ],
426
        'CONCATENATE' => [
427
            'category' => Category::CATEGORY_TEXT_AND_DATA,
428
            'functionCall' => [TextData::class, 'CONCATENATE'],
429
            'argumentCount' => '1+',
430
        ],
431
        'CONFIDENCE' => [
432
            'category' => Category::CATEGORY_STATISTICAL,
433
            'functionCall' => [Statistical::class, 'CONFIDENCE'],
434
            'argumentCount' => '3',
435
        ],
436
        'CONVERT' => [
437
            'category' => Category::CATEGORY_ENGINEERING,
438
            'functionCall' => [Engineering::class, 'CONVERTUOM'],
439
            'argumentCount' => '3',
440
        ],
441
        'CORREL' => [
442
            'category' => Category::CATEGORY_STATISTICAL,
443
            'functionCall' => [Statistical::class, 'CORREL'],
444
            'argumentCount' => '2',
445
        ],
446
        'COS' => [
447
            'category' => Category::CATEGORY_MATH_AND_TRIG,
448
            'functionCall' => 'cos',
449
            'argumentCount' => '1',
450
        ],
451
        'COSH' => [
452
            'category' => Category::CATEGORY_MATH_AND_TRIG,
453
            'functionCall' => 'cosh',
454
            'argumentCount' => '1',
455
        ],
456
        'COUNT' => [
457
            'category' => Category::CATEGORY_STATISTICAL,
458
            'functionCall' => [Statistical::class, 'COUNT'],
459
            'argumentCount' => '1+',
460
        ],
461
        'COUNTA' => [
462
            'category' => Category::CATEGORY_STATISTICAL,
463
            'functionCall' => [Statistical::class, 'COUNTA'],
464
            'argumentCount' => '1+',
465
        ],
466
        'COUNTBLANK' => [
467
            'category' => Category::CATEGORY_STATISTICAL,
468
            'functionCall' => [Statistical::class, 'COUNTBLANK'],
469
            'argumentCount' => '1',
470
        ],
471
        'COUNTIF' => [
472
            'category' => Category::CATEGORY_STATISTICAL,
473
            'functionCall' => [Statistical::class, 'COUNTIF'],
474
            'argumentCount' => '2',
475
        ],
476
        'COUNTIFS' => [
477
            'category' => Category::CATEGORY_STATISTICAL,
478
            'functionCall' => [Functions::class, 'DUMMY'],
479
            'argumentCount' => '2',
480
        ],
481
        'COUPDAYBS' => [
482
            'category' => Category::CATEGORY_FINANCIAL,
483
            'functionCall' => [Financial::class, 'COUPDAYBS'],
484
            'argumentCount' => '3,4',
485
        ],
486
        'COUPDAYS' => [
487
            'category' => Category::CATEGORY_FINANCIAL,
488
            'functionCall' => [Financial::class, 'COUPDAYS'],
489
            'argumentCount' => '3,4',
490
        ],
491
        'COUPDAYSNC' => [
492
            'category' => Category::CATEGORY_FINANCIAL,
493
            'functionCall' => [Financial::class, 'COUPDAYSNC'],
494
            'argumentCount' => '3,4',
495
        ],
496
        'COUPNCD' => [
497
            'category' => Category::CATEGORY_FINANCIAL,
498
            'functionCall' => [Financial::class, 'COUPNCD'],
499
            'argumentCount' => '3,4',
500
        ],
501
        'COUPNUM' => [
502
            'category' => Category::CATEGORY_FINANCIAL,
503
            'functionCall' => [Financial::class, 'COUPNUM'],
504
            'argumentCount' => '3,4',
505
        ],
506
        'COUPPCD' => [
507
            'category' => Category::CATEGORY_FINANCIAL,
508
            'functionCall' => [Financial::class, 'COUPPCD'],
509
            'argumentCount' => '3,4',
510
        ],
511
        'COVAR' => [
512
            'category' => Category::CATEGORY_STATISTICAL,
513
            'functionCall' => [Statistical::class, 'COVAR'],
514
            'argumentCount' => '2',
515
        ],
516
        'CRITBINOM' => [
517
            'category' => Category::CATEGORY_STATISTICAL,
518
            'functionCall' => [Statistical::class, 'CRITBINOM'],
519
            'argumentCount' => '3',
520
        ],
521
        'CUBEKPIMEMBER' => [
522
            'category' => Category::CATEGORY_CUBE,
523
            'functionCall' => [Functions::class, 'DUMMY'],
524
            'argumentCount' => '?',
525
        ],
526
        'CUBEMEMBER' => [
527
            'category' => Category::CATEGORY_CUBE,
528
            'functionCall' => [Functions::class, 'DUMMY'],
529
            'argumentCount' => '?',
530
        ],
531
        'CUBEMEMBERPROPERTY' => [
532
            'category' => Category::CATEGORY_CUBE,
533
            'functionCall' => [Functions::class, 'DUMMY'],
534
            'argumentCount' => '?',
535
        ],
536
        'CUBERANKEDMEMBER' => [
537
            'category' => Category::CATEGORY_CUBE,
538
            'functionCall' => [Functions::class, 'DUMMY'],
539
            'argumentCount' => '?',
540
        ],
541
        'CUBESET' => [
542
            'category' => Category::CATEGORY_CUBE,
543
            'functionCall' => [Functions::class, 'DUMMY'],
544
            'argumentCount' => '?',
545
        ],
546
        'CUBESETCOUNT' => [
547
            'category' => Category::CATEGORY_CUBE,
548
            'functionCall' => [Functions::class, 'DUMMY'],
549
            'argumentCount' => '?',
550
        ],
551
        'CUBEVALUE' => [
552
            'category' => Category::CATEGORY_CUBE,
553
            'functionCall' => [Functions::class, 'DUMMY'],
554
            'argumentCount' => '?',
555
        ],
556
        'CUMIPMT' => [
557
            'category' => Category::CATEGORY_FINANCIAL,
558
            'functionCall' => [Financial::class, 'CUMIPMT'],
559
            'argumentCount' => '6',
560
        ],
561
        'CUMPRINC' => [
562
            'category' => Category::CATEGORY_FINANCIAL,
563
            'functionCall' => [Financial::class, 'CUMPRINC'],
564
            'argumentCount' => '6',
565
        ],
566
        'DATE' => [
567
            'category' => Category::CATEGORY_DATE_AND_TIME,
568
            'functionCall' => [DateTime::class, 'DATE'],
569
            'argumentCount' => '3',
570
        ],
571
        'DATEDIF' => [
572
            'category' => Category::CATEGORY_DATE_AND_TIME,
573
            'functionCall' => [DateTime::class, 'DATEDIF'],
574
            'argumentCount' => '2,3',
575
        ],
576
        'DATEVALUE' => [
577
            'category' => Category::CATEGORY_DATE_AND_TIME,
578
            'functionCall' => [DateTime::class, 'DATEVALUE'],
579
            'argumentCount' => '1',
580
        ],
581
        'DAVERAGE' => [
582
            'category' => Category::CATEGORY_DATABASE,
583
            'functionCall' => [Database::class, 'DAVERAGE'],
584
            'argumentCount' => '3',
585
        ],
586
        'DAY' => [
587
            'category' => Category::CATEGORY_DATE_AND_TIME,
588
            'functionCall' => [DateTime::class, 'DAYOFMONTH'],
589
            'argumentCount' => '1',
590
        ],
591
        'DAYS360' => [
592
            'category' => Category::CATEGORY_DATE_AND_TIME,
593
            'functionCall' => [DateTime::class, 'DAYS360'],
594
            'argumentCount' => '2,3',
595
        ],
596
        'DB' => [
597
            'category' => Category::CATEGORY_FINANCIAL,
598
            'functionCall' => [Financial::class, 'DB'],
599
            'argumentCount' => '4,5',
600
        ],
601
        'DCOUNT' => [
602
            'category' => Category::CATEGORY_DATABASE,
603
            'functionCall' => [Database::class, 'DCOUNT'],
604
            'argumentCount' => '3',
605
        ],
606
        'DCOUNTA' => [
607
            'category' => Category::CATEGORY_DATABASE,
608
            'functionCall' => [Database::class, 'DCOUNTA'],
609
            'argumentCount' => '3',
610
        ],
611
        'DDB' => [
612
            'category' => Category::CATEGORY_FINANCIAL,
613
            'functionCall' => [Financial::class, 'DDB'],
614
            'argumentCount' => '4,5',
615
        ],
616
        'DEC2BIN' => [
617
            'category' => Category::CATEGORY_ENGINEERING,
618
            'functionCall' => [Engineering::class, 'DECTOBIN'],
619
            'argumentCount' => '1,2',
620
        ],
621
        'DEC2HEX' => [
622
            'category' => Category::CATEGORY_ENGINEERING,
623
            'functionCall' => [Engineering::class, 'DECTOHEX'],
624
            'argumentCount' => '1,2',
625
        ],
626
        'DEC2OCT' => [
627
            'category' => Category::CATEGORY_ENGINEERING,
628
            'functionCall' => [Engineering::class, 'DECTOOCT'],
629
            'argumentCount' => '1,2',
630
        ],
631
        'DEGREES' => [
632
            'category' => Category::CATEGORY_MATH_AND_TRIG,
633
            'functionCall' => 'rad2deg',
634
            'argumentCount' => '1',
635
        ],
636
        'DELTA' => [
637
            'category' => Category::CATEGORY_ENGINEERING,
638
            'functionCall' => [Engineering::class, 'DELTA'],
639
            'argumentCount' => '1,2',
640
        ],
641
        'DEVSQ' => [
642
            'category' => Category::CATEGORY_STATISTICAL,
643
            'functionCall' => [Statistical::class, 'DEVSQ'],
644
            'argumentCount' => '1+',
645
        ],
646
        'DGET' => [
647
            'category' => Category::CATEGORY_DATABASE,
648
            'functionCall' => [Database::class, 'DGET'],
649
            'argumentCount' => '3',
650
        ],
651
        'DISC' => [
652
            'category' => Category::CATEGORY_FINANCIAL,
653
            'functionCall' => [Financial::class, 'DISC'],
654
            'argumentCount' => '4,5',
655
        ],
656
        'DMAX' => [
657
            'category' => Category::CATEGORY_DATABASE,
658
            'functionCall' => [Database::class, 'DMAX'],
659
            'argumentCount' => '3',
660
        ],
661
        'DMIN' => [
662
            'category' => Category::CATEGORY_DATABASE,
663
            'functionCall' => [Database::class, 'DMIN'],
664
            'argumentCount' => '3',
665
        ],
666
        'DOLLAR' => [
667
            'category' => Category::CATEGORY_TEXT_AND_DATA,
668
            'functionCall' => [TextData::class, 'DOLLAR'],
669
            'argumentCount' => '1,2',
670
        ],
671
        'DOLLARDE' => [
672
            'category' => Category::CATEGORY_FINANCIAL,
673
            'functionCall' => [Financial::class, 'DOLLARDE'],
674
            'argumentCount' => '2',
675
        ],
676
        'DOLLARFR' => [
677
            'category' => Category::CATEGORY_FINANCIAL,
678
            'functionCall' => [Financial::class, 'DOLLARFR'],
679
            'argumentCount' => '2',
680
        ],
681
        'DPRODUCT' => [
682
            'category' => Category::CATEGORY_DATABASE,
683
            'functionCall' => [Database::class, 'DPRODUCT'],
684
            'argumentCount' => '3',
685
        ],
686
        'DSTDEV' => [
687
            'category' => Category::CATEGORY_DATABASE,
688
            'functionCall' => [Database::class, 'DSTDEV'],
689
            'argumentCount' => '3',
690
        ],
691
        'DSTDEVP' => [
692
            'category' => Category::CATEGORY_DATABASE,
693
            'functionCall' => [Database::class, 'DSTDEVP'],
694
            'argumentCount' => '3',
695
        ],
696
        'DSUM' => [
697
            'category' => Category::CATEGORY_DATABASE,
698
            'functionCall' => [Database::class, 'DSUM'],
699
            'argumentCount' => '3',
700
        ],
701
        'DURATION' => [
702
            'category' => Category::CATEGORY_FINANCIAL,
703
            'functionCall' => [Functions::class, 'DUMMY'],
704
            'argumentCount' => '5,6',
705
        ],
706
        'DVAR' => [
707
            'category' => Category::CATEGORY_DATABASE,
708
            'functionCall' => [Database::class, 'DVAR'],
709
            'argumentCount' => '3',
710
        ],
711
        'DVARP' => [
712
            'category' => Category::CATEGORY_DATABASE,
713
            'functionCall' => [Database::class, 'DVARP'],
714
            'argumentCount' => '3',
715
        ],
716
        'EDATE' => [
717
            'category' => Category::CATEGORY_DATE_AND_TIME,
718
            'functionCall' => [DateTime::class, 'EDATE'],
719
            'argumentCount' => '2',
720
        ],
721
        'EFFECT' => [
722
            'category' => Category::CATEGORY_FINANCIAL,
723
            'functionCall' => [Financial::class, 'EFFECT'],
724
            'argumentCount' => '2',
725
        ],
726
        'EOMONTH' => [
727
            'category' => Category::CATEGORY_DATE_AND_TIME,
728
            'functionCall' => [DateTime::class, 'EOMONTH'],
729
            'argumentCount' => '2',
730
        ],
731
        'ERF' => [
732
            'category' => Category::CATEGORY_ENGINEERING,
733
            'functionCall' => [Engineering::class, 'ERF'],
734
            'argumentCount' => '1,2',
735
        ],
736
        'ERFC' => [
737
            'category' => Category::CATEGORY_ENGINEERING,
738
            'functionCall' => [Engineering::class, 'ERFC'],
739
            'argumentCount' => '1',
740
        ],
741
        'ERROR.TYPE' => [
742
            'category' => Category::CATEGORY_INFORMATION,
743
            'functionCall' => [Functions::class, 'errorType'],
744
            'argumentCount' => '1',
745
        ],
746
        'EVEN' => [
747
            'category' => Category::CATEGORY_MATH_AND_TRIG,
748
            'functionCall' => [MathTrig::class, 'EVEN'],
749
            'argumentCount' => '1',
750
        ],
751
        'EXACT' => [
752
            'category' => Category::CATEGORY_TEXT_AND_DATA,
753
            'functionCall' => [Functions::class, 'DUMMY'],
754
            'argumentCount' => '2',
755
        ],
756
        'EXP' => [
757
            'category' => Category::CATEGORY_MATH_AND_TRIG,
758
            'functionCall' => 'exp',
759
            'argumentCount' => '1',
760
        ],
761
        'EXPONDIST' => [
762
            'category' => Category::CATEGORY_STATISTICAL,
763
            'functionCall' => [Statistical::class, 'EXPONDIST'],
764
            'argumentCount' => '3',
765
        ],
766
        'FACT' => [
767
            'category' => Category::CATEGORY_MATH_AND_TRIG,
768
            'functionCall' => [MathTrig::class, 'FACT'],
769
            'argumentCount' => '1',
770
        ],
771
        'FACTDOUBLE' => [
772
            'category' => Category::CATEGORY_MATH_AND_TRIG,
773
            'functionCall' => [MathTrig::class, 'FACTDOUBLE'],
774
            'argumentCount' => '1',
775
        ],
776
        'FALSE' => [
777
            'category' => Category::CATEGORY_LOGICAL,
778
            'functionCall' => [Logical::class, 'FALSE'],
779
            'argumentCount' => '0',
780
        ],
781
        'FDIST' => [
782
            'category' => Category::CATEGORY_STATISTICAL,
783
            'functionCall' => [Functions::class, 'DUMMY'],
784
            'argumentCount' => '3',
785
        ],
786
        'FIND' => [
787
            'category' => Category::CATEGORY_TEXT_AND_DATA,
788
            'functionCall' => [TextData::class, 'SEARCHSENSITIVE'],
789
            'argumentCount' => '2,3',
790
        ],
791
        'FINDB' => [
792
            'category' => Category::CATEGORY_TEXT_AND_DATA,
793
            'functionCall' => [TextData::class, 'SEARCHSENSITIVE'],
794
            'argumentCount' => '2,3',
795
        ],
796
        'FINV' => [
797
            'category' => Category::CATEGORY_STATISTICAL,
798
            'functionCall' => [Functions::class, 'DUMMY'],
799
            'argumentCount' => '3',
800
        ],
801
        'FISHER' => [
802
            'category' => Category::CATEGORY_STATISTICAL,
803
            'functionCall' => [Statistical::class, 'FISHER'],
804
            'argumentCount' => '1',
805
        ],
806
        'FISHERINV' => [
807
            'category' => Category::CATEGORY_STATISTICAL,
808
            'functionCall' => [Statistical::class, 'FISHERINV'],
809
            'argumentCount' => '1',
810
        ],
811
        'FIXED' => [
812
            'category' => Category::CATEGORY_TEXT_AND_DATA,
813
            'functionCall' => [TextData::class, 'FIXEDFORMAT'],
814
            'argumentCount' => '1-3',
815
        ],
816
        'FLOOR' => [
817
            'category' => Category::CATEGORY_MATH_AND_TRIG,
818
            'functionCall' => [MathTrig::class, 'FLOOR'],
819
            'argumentCount' => '2',
820
        ],
821
        'FORECAST' => [
822
            'category' => Category::CATEGORY_STATISTICAL,
823
            'functionCall' => [Statistical::class, 'FORECAST'],
824
            'argumentCount' => '3',
825
        ],
826
        'FREQUENCY' => [
827
            'category' => Category::CATEGORY_STATISTICAL,
828
            'functionCall' => [Functions::class, 'DUMMY'],
829
            'argumentCount' => '2',
830
        ],
831
        'FTEST' => [
832
            'category' => Category::CATEGORY_STATISTICAL,
833
            'functionCall' => [Functions::class, 'DUMMY'],
834
            'argumentCount' => '2',
835
        ],
836
        'FV' => [
837
            'category' => Category::CATEGORY_FINANCIAL,
838
            'functionCall' => [Financial::class, 'FV'],
839
            'argumentCount' => '3-5',
840
        ],
841
        'FVSCHEDULE' => [
842
            'category' => Category::CATEGORY_FINANCIAL,
843
            'functionCall' => [Financial::class, 'FVSCHEDULE'],
844
            'argumentCount' => '2',
845
        ],
846
        'GAMMADIST' => [
847
            'category' => Category::CATEGORY_STATISTICAL,
848
            'functionCall' => [Statistical::class, 'GAMMADIST'],
849
            'argumentCount' => '4',
850
        ],
851
        'GAMMAINV' => [
852
            'category' => Category::CATEGORY_STATISTICAL,
853
            'functionCall' => [Statistical::class, 'GAMMAINV'],
854
            'argumentCount' => '3',
855
        ],
856
        'GAMMALN' => [
857
            'category' => Category::CATEGORY_STATISTICAL,
858
            'functionCall' => [Statistical::class, 'GAMMALN'],
859
            'argumentCount' => '1',
860
        ],
861
        'GCD' => [
862
            'category' => Category::CATEGORY_MATH_AND_TRIG,
863
            'functionCall' => [MathTrig::class, 'GCD'],
864
            'argumentCount' => '1+',
865
        ],
866
        'GEOMEAN' => [
867
            'category' => Category::CATEGORY_STATISTICAL,
868
            'functionCall' => [Statistical::class, 'GEOMEAN'],
869
            'argumentCount' => '1+',
870
        ],
871
        'GESTEP' => [
872
            'category' => Category::CATEGORY_ENGINEERING,
873
            'functionCall' => [Engineering::class, 'GESTEP'],
874
            'argumentCount' => '1,2',
875
        ],
876
        'GETPIVOTDATA' => [
877
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
878
            'functionCall' => [Functions::class, 'DUMMY'],
879
            'argumentCount' => '2+',
880
        ],
881
        'GROWTH' => [
882
            'category' => Category::CATEGORY_STATISTICAL,
883
            'functionCall' => [Statistical::class, 'GROWTH'],
884
            'argumentCount' => '1-4',
885
        ],
886
        'HARMEAN' => [
887
            'category' => Category::CATEGORY_STATISTICAL,
888
            'functionCall' => [Statistical::class, 'HARMEAN'],
889
            'argumentCount' => '1+',
890
        ],
891
        'HEX2BIN' => [
892
            'category' => Category::CATEGORY_ENGINEERING,
893
            'functionCall' => [Engineering::class, 'HEXTOBIN'],
894
            'argumentCount' => '1,2',
895
        ],
896
        'HEX2DEC' => [
897
            'category' => Category::CATEGORY_ENGINEERING,
898
            'functionCall' => [Engineering::class, 'HEXTODEC'],
899
            'argumentCount' => '1',
900
        ],
901
        'HEX2OCT' => [
902
            'category' => Category::CATEGORY_ENGINEERING,
903
            'functionCall' => [Engineering::class, 'HEXTOOCT'],
904
            'argumentCount' => '1,2',
905
        ],
906
        'HLOOKUP' => [
907
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
908
            'functionCall' => [LookupRef::class, 'HLOOKUP'],
909
            'argumentCount' => '3,4',
910
        ],
911
        'HOUR' => [
912
            'category' => Category::CATEGORY_DATE_AND_TIME,
913
            'functionCall' => [DateTime::class, 'HOUROFDAY'],
914
            'argumentCount' => '1',
915
        ],
916
        'HYPERLINK' => [
917
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
918
            'functionCall' => [LookupRef::class, 'HYPERLINK'],
919
            'argumentCount' => '1,2',
920
            'passCellReference' => true,
921
        ],
922
        'HYPGEOMDIST' => [
923
            'category' => Category::CATEGORY_STATISTICAL,
924
            'functionCall' => [Statistical::class, 'HYPGEOMDIST'],
925
            'argumentCount' => '4',
926
        ],
927
        'IF' => [
928
            'category' => Category::CATEGORY_LOGICAL,
929
            'functionCall' => [Logical::class, 'statementIf'],
930
            'argumentCount' => '1-3',
931
        ],
932
        'IFERROR' => [
933
            'category' => Category::CATEGORY_LOGICAL,
934
            'functionCall' => [Logical::class, 'IFERROR'],
935
            'argumentCount' => '2',
936
        ],
937
        'IMABS' => [
938
            'category' => Category::CATEGORY_ENGINEERING,
939
            'functionCall' => [Engineering::class, 'IMABS'],
940
            'argumentCount' => '1',
941
        ],
942
        'IMAGINARY' => [
943
            'category' => Category::CATEGORY_ENGINEERING,
944
            'functionCall' => [Engineering::class, 'IMAGINARY'],
945
            'argumentCount' => '1',
946
        ],
947
        'IMARGUMENT' => [
948
            'category' => Category::CATEGORY_ENGINEERING,
949
            'functionCall' => [Engineering::class, 'IMARGUMENT'],
950
            'argumentCount' => '1',
951
        ],
952
        'IMCONJUGATE' => [
953
            'category' => Category::CATEGORY_ENGINEERING,
954
            'functionCall' => [Engineering::class, 'IMCONJUGATE'],
955
            'argumentCount' => '1',
956
        ],
957
        'IMCOS' => [
958
            'category' => Category::CATEGORY_ENGINEERING,
959
            'functionCall' => [Engineering::class, 'IMCOS'],
960
            'argumentCount' => '1',
961
        ],
962
        'IMDIV' => [
963
            'category' => Category::CATEGORY_ENGINEERING,
964
            'functionCall' => [Engineering::class, 'IMDIV'],
965
            'argumentCount' => '2',
966
        ],
967
        'IMEXP' => [
968
            'category' => Category::CATEGORY_ENGINEERING,
969
            'functionCall' => [Engineering::class, 'IMEXP'],
970
            'argumentCount' => '1',
971
        ],
972
        'IMLN' => [
973
            'category' => Category::CATEGORY_ENGINEERING,
974
            'functionCall' => [Engineering::class, 'IMLN'],
975
            'argumentCount' => '1',
976
        ],
977
        'IMLOG10' => [
978
            'category' => Category::CATEGORY_ENGINEERING,
979
            'functionCall' => [Engineering::class, 'IMLOG10'],
980
            'argumentCount' => '1',
981
        ],
982
        'IMLOG2' => [
983
            'category' => Category::CATEGORY_ENGINEERING,
984
            'functionCall' => [Engineering::class, 'IMLOG2'],
985
            'argumentCount' => '1',
986
        ],
987
        'IMPOWER' => [
988
            'category' => Category::CATEGORY_ENGINEERING,
989
            'functionCall' => [Engineering::class, 'IMPOWER'],
990
            'argumentCount' => '2',
991
        ],
992
        'IMPRODUCT' => [
993
            'category' => Category::CATEGORY_ENGINEERING,
994
            'functionCall' => [Engineering::class, 'IMPRODUCT'],
995
            'argumentCount' => '1+',
996
        ],
997
        'IMREAL' => [
998
            'category' => Category::CATEGORY_ENGINEERING,
999
            'functionCall' => [Engineering::class, 'IMREAL'],
1000
            'argumentCount' => '1',
1001
        ],
1002
        'IMSIN' => [
1003
            'category' => Category::CATEGORY_ENGINEERING,
1004
            'functionCall' => [Engineering::class, 'IMSIN'],
1005
            'argumentCount' => '1',
1006
        ],
1007
        'IMSQRT' => [
1008
            'category' => Category::CATEGORY_ENGINEERING,
1009
            'functionCall' => [Engineering::class, 'IMSQRT'],
1010
            'argumentCount' => '1',
1011
        ],
1012
        'IMSUB' => [
1013
            'category' => Category::CATEGORY_ENGINEERING,
1014
            'functionCall' => [Engineering::class, 'IMSUB'],
1015
            'argumentCount' => '2',
1016
        ],
1017
        'IMSUM' => [
1018
            'category' => Category::CATEGORY_ENGINEERING,
1019
            'functionCall' => [Engineering::class, 'IMSUM'],
1020
            'argumentCount' => '1+',
1021
        ],
1022
        'INDEX' => [
1023
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1024
            'functionCall' => [LookupRef::class, 'INDEX'],
1025
            'argumentCount' => '1-4',
1026
        ],
1027
        'INDIRECT' => [
1028
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1029
            'functionCall' => [LookupRef::class, 'INDIRECT'],
1030
            'argumentCount' => '1,2',
1031
            'passCellReference' => true,
1032
        ],
1033
        'INFO' => [
1034
            'category' => Category::CATEGORY_INFORMATION,
1035
            'functionCall' => [Functions::class, 'DUMMY'],
1036
            'argumentCount' => '1',
1037
        ],
1038
        'INT' => [
1039
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1040
            'functionCall' => [MathTrig::class, 'INT'],
1041
            'argumentCount' => '1',
1042
        ],
1043
        'INTERCEPT' => [
1044
            'category' => Category::CATEGORY_STATISTICAL,
1045
            'functionCall' => [Statistical::class, 'INTERCEPT'],
1046
            'argumentCount' => '2',
1047
        ],
1048
        'INTRATE' => [
1049
            'category' => Category::CATEGORY_FINANCIAL,
1050
            'functionCall' => [Financial::class, 'INTRATE'],
1051
            'argumentCount' => '4,5',
1052
        ],
1053
        'IPMT' => [
1054
            'category' => Category::CATEGORY_FINANCIAL,
1055
            'functionCall' => [Financial::class, 'IPMT'],
1056
            'argumentCount' => '4-6',
1057
        ],
1058
        'IRR' => [
1059
            'category' => Category::CATEGORY_FINANCIAL,
1060
            'functionCall' => [Financial::class, 'IRR'],
1061
            'argumentCount' => '1,2',
1062
        ],
1063
        'ISBLANK' => [
1064
            'category' => Category::CATEGORY_INFORMATION,
1065
            'functionCall' => [Functions::class, 'isBlank'],
1066
            'argumentCount' => '1',
1067
        ],
1068
        'ISERR' => [
1069
            'category' => Category::CATEGORY_INFORMATION,
1070
            'functionCall' => [Functions::class, 'isErr'],
1071
            'argumentCount' => '1',
1072
        ],
1073
        'ISERROR' => [
1074
            'category' => Category::CATEGORY_INFORMATION,
1075
            'functionCall' => [Functions::class, 'isError'],
1076
            'argumentCount' => '1',
1077
        ],
1078
        'ISEVEN' => [
1079
            'category' => Category::CATEGORY_INFORMATION,
1080
            'functionCall' => [Functions::class, 'isEven'],
1081
            'argumentCount' => '1',
1082
        ],
1083
        'ISLOGICAL' => [
1084
            'category' => Category::CATEGORY_INFORMATION,
1085
            'functionCall' => [Functions::class, 'isLogical'],
1086
            'argumentCount' => '1',
1087
        ],
1088
        'ISNA' => [
1089
            'category' => Category::CATEGORY_INFORMATION,
1090
            'functionCall' => [Functions::class, 'isNa'],
1091
            'argumentCount' => '1',
1092
        ],
1093
        'ISNONTEXT' => [
1094
            'category' => Category::CATEGORY_INFORMATION,
1095
            'functionCall' => [Functions::class, 'isNonText'],
1096
            'argumentCount' => '1',
1097
        ],
1098
        'ISNUMBER' => [
1099
            'category' => Category::CATEGORY_INFORMATION,
1100
            'functionCall' => [Functions::class, 'isNumber'],
1101
            'argumentCount' => '1',
1102
        ],
1103
        'ISODD' => [
1104
            'category' => Category::CATEGORY_INFORMATION,
1105
            'functionCall' => [Functions::class, 'isOdd'],
1106
            'argumentCount' => '1',
1107
        ],
1108
        'ISPMT' => [
1109
            'category' => Category::CATEGORY_FINANCIAL,
1110
            'functionCall' => [Financial::class, 'ISPMT'],
1111
            'argumentCount' => '4',
1112
        ],
1113
        'ISREF' => [
1114
            'category' => Category::CATEGORY_INFORMATION,
1115
            'functionCall' => [Functions::class, 'DUMMY'],
1116
            'argumentCount' => '1',
1117
        ],
1118
        'ISTEXT' => [
1119
            'category' => Category::CATEGORY_INFORMATION,
1120
            'functionCall' => [Functions::class, 'isText'],
1121
            'argumentCount' => '1',
1122
        ],
1123
        'JIS' => [
1124
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1125
            'functionCall' => [Functions::class, 'DUMMY'],
1126
            'argumentCount' => '1',
1127
        ],
1128
        'KURT' => [
1129
            'category' => Category::CATEGORY_STATISTICAL,
1130
            'functionCall' => [Statistical::class, 'KURT'],
1131
            'argumentCount' => '1+',
1132
        ],
1133
        'LARGE' => [
1134
            'category' => Category::CATEGORY_STATISTICAL,
1135
            'functionCall' => [Statistical::class, 'LARGE'],
1136
            'argumentCount' => '2',
1137
        ],
1138
        'LCM' => [
1139
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1140
            'functionCall' => [MathTrig::class, 'LCM'],
1141
            'argumentCount' => '1+',
1142
        ],
1143
        'LEFT' => [
1144
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1145
            'functionCall' => [TextData::class, 'LEFT'],
1146
            'argumentCount' => '1,2',
1147
        ],
1148
        'LEFTB' => [
1149
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1150
            'functionCall' => [TextData::class, 'LEFT'],
1151
            'argumentCount' => '1,2',
1152
        ],
1153
        'LEN' => [
1154
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1155
            'functionCall' => [TextData::class, 'STRINGLENGTH'],
1156
            'argumentCount' => '1',
1157
        ],
1158
        'LENB' => [
1159
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1160
            'functionCall' => [TextData::class, 'STRINGLENGTH'],
1161
            'argumentCount' => '1',
1162
        ],
1163
        'LINEST' => [
1164
            'category' => Category::CATEGORY_STATISTICAL,
1165
            'functionCall' => [Statistical::class, 'LINEST'],
1166
            'argumentCount' => '1-4',
1167
        ],
1168
        'LN' => [
1169
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1170
            'functionCall' => 'log',
1171
            'argumentCount' => '1',
1172
        ],
1173
        'LOG' => [
1174
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1175
            'functionCall' => [MathTrig::class, 'logBase'],
1176
            'argumentCount' => '1,2',
1177
        ],
1178
        'LOG10' => [
1179
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1180
            'functionCall' => 'log10',
1181
            'argumentCount' => '1',
1182
        ],
1183
        'LOGEST' => [
1184
            'category' => Category::CATEGORY_STATISTICAL,
1185
            'functionCall' => [Statistical::class, 'LOGEST'],
1186
            'argumentCount' => '1-4',
1187
        ],
1188
        'LOGINV' => [
1189
            'category' => Category::CATEGORY_STATISTICAL,
1190
            'functionCall' => [Statistical::class, 'LOGINV'],
1191
            'argumentCount' => '3',
1192
        ],
1193
        'LOGNORMDIST' => [
1194
            'category' => Category::CATEGORY_STATISTICAL,
1195
            'functionCall' => [Statistical::class, 'LOGNORMDIST'],
1196
            'argumentCount' => '3',
1197
        ],
1198
        'LOOKUP' => [
1199
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1200
            'functionCall' => [LookupRef::class, 'LOOKUP'],
1201
            'argumentCount' => '2,3',
1202
        ],
1203
        'LOWER' => [
1204
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1205
            'functionCall' => [TextData::class, 'LOWERCASE'],
1206
            'argumentCount' => '1',
1207
        ],
1208
        'MATCH' => [
1209
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1210
            'functionCall' => [LookupRef::class, 'MATCH'],
1211
            'argumentCount' => '2,3',
1212
        ],
1213
        'MAX' => [
1214
            'category' => Category::CATEGORY_STATISTICAL,
1215
            'functionCall' => [Statistical::class, 'MAX'],
1216
            'argumentCount' => '1+',
1217
        ],
1218
        'MAXA' => [
1219
            'category' => Category::CATEGORY_STATISTICAL,
1220
            'functionCall' => [Statistical::class, 'MAXA'],
1221
            'argumentCount' => '1+',
1222
        ],
1223
        'MAXIF' => [
1224
            'category' => Category::CATEGORY_STATISTICAL,
1225
            'functionCall' => [Statistical::class, 'MAXIF'],
1226
            'argumentCount' => '2+',
1227
        ],
1228
        'MDETERM' => [
1229
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1230
            'functionCall' => [MathTrig::class, 'MDETERM'],
1231
            'argumentCount' => '1',
1232
        ],
1233
        'MDURATION' => [
1234
            'category' => Category::CATEGORY_FINANCIAL,
1235
            'functionCall' => [Functions::class, 'DUMMY'],
1236
            'argumentCount' => '5,6',
1237
        ],
1238
        'MEDIAN' => [
1239
            'category' => Category::CATEGORY_STATISTICAL,
1240
            'functionCall' => [Statistical::class, 'MEDIAN'],
1241
            'argumentCount' => '1+',
1242
        ],
1243
        'MEDIANIF' => [
1244
            'category' => Category::CATEGORY_STATISTICAL,
1245
            'functionCall' => [Functions::class, 'DUMMY'],
1246
            'argumentCount' => '2+',
1247
        ],
1248
        'MID' => [
1249
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1250
            'functionCall' => [TextData::class, 'MID'],
1251
            'argumentCount' => '3',
1252
        ],
1253
        'MIDB' => [
1254
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1255
            'functionCall' => [TextData::class, 'MID'],
1256
            'argumentCount' => '3',
1257
        ],
1258
        'MIN' => [
1259
            'category' => Category::CATEGORY_STATISTICAL,
1260
            'functionCall' => [Statistical::class, 'MIN'],
1261
            'argumentCount' => '1+',
1262
        ],
1263
        'MINA' => [
1264
            'category' => Category::CATEGORY_STATISTICAL,
1265
            'functionCall' => [Statistical::class, 'MINA'],
1266
            'argumentCount' => '1+',
1267
        ],
1268
        'MINIF' => [
1269
            'category' => Category::CATEGORY_STATISTICAL,
1270
            'functionCall' => [Statistical::class, 'MINIF'],
1271
            'argumentCount' => '2+',
1272
        ],
1273
        'MINUTE' => [
1274
            'category' => Category::CATEGORY_DATE_AND_TIME,
1275
            'functionCall' => [DateTime::class, 'MINUTE'],
1276
            'argumentCount' => '1',
1277
        ],
1278
        'MINVERSE' => [
1279
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1280
            'functionCall' => [MathTrig::class, 'MINVERSE'],
1281
            'argumentCount' => '1',
1282
        ],
1283
        'MIRR' => [
1284
            'category' => Category::CATEGORY_FINANCIAL,
1285
            'functionCall' => [Financial::class, 'MIRR'],
1286
            'argumentCount' => '3',
1287
        ],
1288
        'MMULT' => [
1289
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1290
            'functionCall' => [MathTrig::class, 'MMULT'],
1291
            'argumentCount' => '2',
1292
        ],
1293
        'MOD' => [
1294
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1295
            'functionCall' => [MathTrig::class, 'MOD'],
1296
            'argumentCount' => '2',
1297
        ],
1298
        'MODE' => [
1299
            'category' => Category::CATEGORY_STATISTICAL,
1300
            'functionCall' => [Statistical::class, 'MODE'],
1301
            'argumentCount' => '1+',
1302
        ],
1303
        'MONTH' => [
1304
            'category' => Category::CATEGORY_DATE_AND_TIME,
1305
            'functionCall' => [DateTime::class, 'MONTHOFYEAR'],
1306
            'argumentCount' => '1',
1307
        ],
1308
        'MROUND' => [
1309
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1310
            'functionCall' => [MathTrig::class, 'MROUND'],
1311
            'argumentCount' => '2',
1312
        ],
1313
        'MULTINOMIAL' => [
1314
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1315
            'functionCall' => [MathTrig::class, 'MULTINOMIAL'],
1316
            'argumentCount' => '1+',
1317
        ],
1318
        'N' => [
1319
            'category' => Category::CATEGORY_INFORMATION,
1320
            'functionCall' => [Functions::class, 'n'],
1321
            'argumentCount' => '1',
1322
        ],
1323
        'NA' => [
1324
            'category' => Category::CATEGORY_INFORMATION,
1325
            'functionCall' => [Functions::class, 'NA'],
1326
            'argumentCount' => '0',
1327
        ],
1328
        'NEGBINOMDIST' => [
1329
            'category' => Category::CATEGORY_STATISTICAL,
1330
            'functionCall' => [Statistical::class, 'NEGBINOMDIST'],
1331
            'argumentCount' => '3',
1332
        ],
1333
        'NETWORKDAYS' => [
1334
            'category' => Category::CATEGORY_DATE_AND_TIME,
1335
            'functionCall' => [DateTime::class, 'NETWORKDAYS'],
1336
            'argumentCount' => '2+',
1337
        ],
1338
        'NOMINAL' => [
1339
            'category' => Category::CATEGORY_FINANCIAL,
1340
            'functionCall' => [Financial::class, 'NOMINAL'],
1341
            'argumentCount' => '2',
1342
        ],
1343
        'NORMDIST' => [
1344
            'category' => Category::CATEGORY_STATISTICAL,
1345
            'functionCall' => [Statistical::class, 'NORMDIST'],
1346
            'argumentCount' => '4',
1347
        ],
1348
        'NORMINV' => [
1349
            'category' => Category::CATEGORY_STATISTICAL,
1350
            'functionCall' => [Statistical::class, 'NORMINV'],
1351
            'argumentCount' => '3',
1352
        ],
1353
        'NORMSDIST' => [
1354
            'category' => Category::CATEGORY_STATISTICAL,
1355
            'functionCall' => [Statistical::class, 'NORMSDIST'],
1356
            'argumentCount' => '1',
1357
        ],
1358
        'NORMSINV' => [
1359
            'category' => Category::CATEGORY_STATISTICAL,
1360
            'functionCall' => [Statistical::class, 'NORMSINV'],
1361
            'argumentCount' => '1',
1362
        ],
1363
        'NOT' => [
1364
            'category' => Category::CATEGORY_LOGICAL,
1365
            'functionCall' => [Logical::class, 'NOT'],
1366
            'argumentCount' => '1',
1367
        ],
1368
        'NOW' => [
1369
            'category' => Category::CATEGORY_DATE_AND_TIME,
1370
            'functionCall' => [DateTime::class, 'DATETIMENOW'],
1371
            'argumentCount' => '0',
1372
        ],
1373
        'NPER' => [
1374
            'category' => Category::CATEGORY_FINANCIAL,
1375
            'functionCall' => [Financial::class, 'NPER'],
1376
            'argumentCount' => '3-5',
1377
        ],
1378
        'NPV' => [
1379
            'category' => Category::CATEGORY_FINANCIAL,
1380
            'functionCall' => [Financial::class, 'NPV'],
1381
            'argumentCount' => '2+',
1382
        ],
1383
        'OCT2BIN' => [
1384
            'category' => Category::CATEGORY_ENGINEERING,
1385
            'functionCall' => [Engineering::class, 'OCTTOBIN'],
1386
            'argumentCount' => '1,2',
1387
        ],
1388
        'OCT2DEC' => [
1389
            'category' => Category::CATEGORY_ENGINEERING,
1390
            'functionCall' => [Engineering::class, 'OCTTODEC'],
1391
            'argumentCount' => '1',
1392
        ],
1393
        'OCT2HEX' => [
1394
            'category' => Category::CATEGORY_ENGINEERING,
1395
            'functionCall' => [Engineering::class, 'OCTTOHEX'],
1396
            'argumentCount' => '1,2',
1397
        ],
1398
        'ODD' => [
1399
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1400
            'functionCall' => [MathTrig::class, 'ODD'],
1401
            'argumentCount' => '1',
1402
        ],
1403
        'ODDFPRICE' => [
1404
            'category' => Category::CATEGORY_FINANCIAL,
1405
            'functionCall' => [Functions::class, 'DUMMY'],
1406
            'argumentCount' => '8,9',
1407
        ],
1408
        'ODDFYIELD' => [
1409
            'category' => Category::CATEGORY_FINANCIAL,
1410
            'functionCall' => [Functions::class, 'DUMMY'],
1411
            'argumentCount' => '8,9',
1412
        ],
1413
        'ODDLPRICE' => [
1414
            'category' => Category::CATEGORY_FINANCIAL,
1415
            'functionCall' => [Functions::class, 'DUMMY'],
1416
            'argumentCount' => '7,8',
1417
        ],
1418
        'ODDLYIELD' => [
1419
            'category' => Category::CATEGORY_FINANCIAL,
1420
            'functionCall' => [Functions::class, 'DUMMY'],
1421
            'argumentCount' => '7,8',
1422
        ],
1423
        'OFFSET' => [
1424
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1425
            'functionCall' => [LookupRef::class, 'OFFSET'],
1426
            'argumentCount' => '3-5',
1427
            'passCellReference' => true,
1428
            'passByReference' => [true],
1429
        ],
1430
        'OR' => [
1431
            'category' => Category::CATEGORY_LOGICAL,
1432
            'functionCall' => [Logical::class, 'logicalOr'],
1433
            'argumentCount' => '1+',
1434
        ],
1435
        'PEARSON' => [
1436
            'category' => Category::CATEGORY_STATISTICAL,
1437
            'functionCall' => [Statistical::class, 'CORREL'],
1438
            'argumentCount' => '2',
1439
        ],
1440
        'PERCENTILE' => [
1441
            'category' => Category::CATEGORY_STATISTICAL,
1442
            'functionCall' => [Statistical::class, 'PERCENTILE'],
1443
            'argumentCount' => '2',
1444
        ],
1445
        'PERCENTRANK' => [
1446
            'category' => Category::CATEGORY_STATISTICAL,
1447
            'functionCall' => [Statistical::class, 'PERCENTRANK'],
1448
            'argumentCount' => '2,3',
1449
        ],
1450
        'PERMUT' => [
1451
            'category' => Category::CATEGORY_STATISTICAL,
1452
            'functionCall' => [Statistical::class, 'PERMUT'],
1453
            'argumentCount' => '2',
1454
        ],
1455
        'PHONETIC' => [
1456
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1457
            'functionCall' => [Functions::class, 'DUMMY'],
1458
            'argumentCount' => '1',
1459
        ],
1460
        'PI' => [
1461
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1462
            'functionCall' => 'pi',
1463
            'argumentCount' => '0',
1464
        ],
1465
        'PMT' => [
1466
            'category' => Category::CATEGORY_FINANCIAL,
1467
            'functionCall' => [Financial::class, 'PMT'],
1468
            'argumentCount' => '3-5',
1469
        ],
1470
        'POISSON' => [
1471
            'category' => Category::CATEGORY_STATISTICAL,
1472
            'functionCall' => [Statistical::class, 'POISSON'],
1473
            'argumentCount' => '3',
1474
        ],
1475
        'POWER' => [
1476
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1477
            'functionCall' => [MathTrig::class, 'POWER'],
1478
            'argumentCount' => '2',
1479
        ],
1480
        'PPMT' => [
1481
            'category' => Category::CATEGORY_FINANCIAL,
1482
            'functionCall' => [Financial::class, 'PPMT'],
1483
            'argumentCount' => '4-6',
1484
        ],
1485
        'PRICE' => [
1486
            'category' => Category::CATEGORY_FINANCIAL,
1487
            'functionCall' => [Financial::class, 'PRICE'],
1488
            'argumentCount' => '6,7',
1489
        ],
1490
        'PRICEDISC' => [
1491
            'category' => Category::CATEGORY_FINANCIAL,
1492
            'functionCall' => [Financial::class, 'PRICEDISC'],
1493
            'argumentCount' => '4,5',
1494
        ],
1495
        'PRICEMAT' => [
1496
            'category' => Category::CATEGORY_FINANCIAL,
1497
            'functionCall' => [Financial::class, 'PRICEMAT'],
1498
            'argumentCount' => '5,6',
1499
        ],
1500
        'PROB' => [
1501
            'category' => Category::CATEGORY_STATISTICAL,
1502
            'functionCall' => [Functions::class, 'DUMMY'],
1503
            'argumentCount' => '3,4',
1504
        ],
1505
        'PRODUCT' => [
1506
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1507
            'functionCall' => [MathTrig::class, 'PRODUCT'],
1508
            'argumentCount' => '1+',
1509
        ],
1510
        'PROPER' => [
1511
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1512
            'functionCall' => [TextData::class, 'PROPERCASE'],
1513
            'argumentCount' => '1',
1514
        ],
1515
        'PV' => [
1516
            'category' => Category::CATEGORY_FINANCIAL,
1517
            'functionCall' => [Financial::class, 'PV'],
1518
            'argumentCount' => '3-5',
1519
        ],
1520
        'QUARTILE' => [
1521
            'category' => Category::CATEGORY_STATISTICAL,
1522
            'functionCall' => [Statistical::class, 'QUARTILE'],
1523
            'argumentCount' => '2',
1524
        ],
1525
        'QUOTIENT' => [
1526
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1527
            'functionCall' => [MathTrig::class, 'QUOTIENT'],
1528
            'argumentCount' => '2',
1529
        ],
1530
        'RADIANS' => [
1531
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1532
            'functionCall' => 'deg2rad',
1533
            'argumentCount' => '1',
1534
        ],
1535
        'RAND' => [
1536
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1537
            'functionCall' => [MathTrig::class, 'RAND'],
1538
            'argumentCount' => '0',
1539
        ],
1540
        'RANDBETWEEN' => [
1541
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1542
            'functionCall' => [MathTrig::class, 'RAND'],
1543
            'argumentCount' => '2',
1544
        ],
1545
        'RANK' => [
1546
            'category' => Category::CATEGORY_STATISTICAL,
1547
            'functionCall' => [Statistical::class, 'RANK'],
1548
            'argumentCount' => '2,3',
1549
        ],
1550
        'RATE' => [
1551
            'category' => Category::CATEGORY_FINANCIAL,
1552
            'functionCall' => [Financial::class, 'RATE'],
1553
            'argumentCount' => '3-6',
1554
        ],
1555
        'RECEIVED' => [
1556
            'category' => Category::CATEGORY_FINANCIAL,
1557
            'functionCall' => [Financial::class, 'RECEIVED'],
1558
            'argumentCount' => '4-5',
1559
        ],
1560
        'REPLACE' => [
1561
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1562
            'functionCall' => [TextData::class, 'REPLACE'],
1563
            'argumentCount' => '4',
1564
        ],
1565
        'REPLACEB' => [
1566
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1567
            'functionCall' => [TextData::class, 'REPLACE'],
1568
            'argumentCount' => '4',
1569
        ],
1570
        'REPT' => [
1571
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1572
            'functionCall' => 'str_repeat',
1573
            'argumentCount' => '2',
1574
        ],
1575
        'RIGHT' => [
1576
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1577
            'functionCall' => [TextData::class, 'RIGHT'],
1578
            'argumentCount' => '1,2',
1579
        ],
1580
        'RIGHTB' => [
1581
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1582
            'functionCall' => [TextData::class, 'RIGHT'],
1583
            'argumentCount' => '1,2',
1584
        ],
1585
        'ROMAN' => [
1586
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1587
            'functionCall' => [MathTrig::class, 'ROMAN'],
1588
            'argumentCount' => '1,2',
1589
        ],
1590
        'ROUND' => [
1591
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1592
            'functionCall' => 'round',
1593
            'argumentCount' => '2',
1594
        ],
1595
        'ROUNDDOWN' => [
1596
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1597
            'functionCall' => [MathTrig::class, 'ROUNDDOWN'],
1598
            'argumentCount' => '2',
1599
        ],
1600
        'ROUNDUP' => [
1601
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1602
            'functionCall' => [MathTrig::class, 'ROUNDUP'],
1603
            'argumentCount' => '2',
1604
        ],
1605
        'ROW' => [
1606
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1607
            'functionCall' => [LookupRef::class, 'ROW'],
1608
            'argumentCount' => '-1',
1609
            'passByReference' => [true],
1610
        ],
1611
        'ROWS' => [
1612
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1613
            'functionCall' => [LookupRef::class, 'ROWS'],
1614
            'argumentCount' => '1',
1615
        ],
1616
        'RSQ' => [
1617
            'category' => Category::CATEGORY_STATISTICAL,
1618
            'functionCall' => [Statistical::class, 'RSQ'],
1619
            'argumentCount' => '2',
1620
        ],
1621
        'RTD' => [
1622
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1623
            'functionCall' => [Functions::class, 'DUMMY'],
1624
            'argumentCount' => '1+',
1625
        ],
1626
        'SEARCH' => [
1627
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1628
            'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
1629
            'argumentCount' => '2,3',
1630
        ],
1631
        'SEARCHB' => [
1632
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1633
            'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
1634
            'argumentCount' => '2,3',
1635
        ],
1636
        'SECOND' => [
1637
            'category' => Category::CATEGORY_DATE_AND_TIME,
1638
            'functionCall' => [DateTime::class, 'SECOND'],
1639
            'argumentCount' => '1',
1640
        ],
1641
        'SERIESSUM' => [
1642
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1643
            'functionCall' => [MathTrig::class, 'SERIESSUM'],
1644
            'argumentCount' => '4',
1645
        ],
1646
        'SIGN' => [
1647
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1648
            'functionCall' => [MathTrig::class, 'SIGN'],
1649
            'argumentCount' => '1',
1650
        ],
1651
        'SIN' => [
1652
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1653
            'functionCall' => 'sin',
1654
            'argumentCount' => '1',
1655
        ],
1656
        'SINH' => [
1657
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1658
            'functionCall' => 'sinh',
1659
            'argumentCount' => '1',
1660
        ],
1661
        'SKEW' => [
1662
            'category' => Category::CATEGORY_STATISTICAL,
1663
            'functionCall' => [Statistical::class, 'SKEW'],
1664
            'argumentCount' => '1+',
1665
        ],
1666
        'SLN' => [
1667
            'category' => Category::CATEGORY_FINANCIAL,
1668
            'functionCall' => [Financial::class, 'SLN'],
1669
            'argumentCount' => '3',
1670
        ],
1671
        'SLOPE' => [
1672
            'category' => Category::CATEGORY_STATISTICAL,
1673
            'functionCall' => [Statistical::class, 'SLOPE'],
1674
            'argumentCount' => '2',
1675
        ],
1676
        'SMALL' => [
1677
            'category' => Category::CATEGORY_STATISTICAL,
1678
            'functionCall' => [Statistical::class, 'SMALL'],
1679
            'argumentCount' => '2',
1680
        ],
1681
        'SQRT' => [
1682
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1683
            'functionCall' => 'sqrt',
1684
            'argumentCount' => '1',
1685
        ],
1686
        'SQRTPI' => [
1687
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1688
            'functionCall' => [MathTrig::class, 'SQRTPI'],
1689
            'argumentCount' => '1',
1690
        ],
1691
        'STANDARDIZE' => [
1692
            'category' => Category::CATEGORY_STATISTICAL,
1693
            'functionCall' => [Statistical::class, 'STANDARDIZE'],
1694
            'argumentCount' => '3',
1695
        ],
1696
        'STDEV' => [
1697
            'category' => Category::CATEGORY_STATISTICAL,
1698
            'functionCall' => [Statistical::class, 'STDEV'],
1699
            'argumentCount' => '1+',
1700
        ],
1701
        'STDEVA' => [
1702
            'category' => Category::CATEGORY_STATISTICAL,
1703
            'functionCall' => [Statistical::class, 'STDEVA'],
1704
            'argumentCount' => '1+',
1705
        ],
1706
        'STDEVP' => [
1707
            'category' => Category::CATEGORY_STATISTICAL,
1708
            'functionCall' => [Statistical::class, 'STDEVP'],
1709
            'argumentCount' => '1+',
1710
        ],
1711
        'STDEVPA' => [
1712
            'category' => Category::CATEGORY_STATISTICAL,
1713
            'functionCall' => [Statistical::class, 'STDEVPA'],
1714
            'argumentCount' => '1+',
1715
        ],
1716
        'STEYX' => [
1717
            'category' => Category::CATEGORY_STATISTICAL,
1718
            'functionCall' => [Statistical::class, 'STEYX'],
1719
            'argumentCount' => '2',
1720
        ],
1721
        'SUBSTITUTE' => [
1722
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1723
            'functionCall' => [TextData::class, 'SUBSTITUTE'],
1724
            'argumentCount' => '3,4',
1725
        ],
1726
        'SUBTOTAL' => [
1727
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1728
            'functionCall' => [MathTrig::class, 'SUBTOTAL'],
1729
            'argumentCount' => '2+',
1730
            'passCellReference' => true,
1731
        ],
1732
        'SUM' => [
1733
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1734
            'functionCall' => [MathTrig::class, 'SUM'],
1735
            'argumentCount' => '1+',
1736
        ],
1737
        'SUMIF' => [
1738
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1739
            'functionCall' => [MathTrig::class, 'SUMIF'],
1740
            'argumentCount' => '2,3',
1741
        ],
1742
        'SUMIFS' => [
1743
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1744
            'functionCall' => [MathTrig::class, 'SUMIFS'],
1745
            'argumentCount' => '3+',
1746
        ],
1747
        'SUMPRODUCT' => [
1748
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1749
            'functionCall' => [MathTrig::class, 'SUMPRODUCT'],
1750
            'argumentCount' => '1+',
1751
        ],
1752
        'SUMSQ' => [
1753
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1754
            'functionCall' => [MathTrig::class, 'SUMSQ'],
1755
            'argumentCount' => '1+',
1756
        ],
1757
        'SUMX2MY2' => [
1758
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1759
            'functionCall' => [MathTrig::class, 'SUMX2MY2'],
1760
            'argumentCount' => '2',
1761
        ],
1762
        'SUMX2PY2' => [
1763
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1764
            'functionCall' => [MathTrig::class, 'SUMX2PY2'],
1765
            'argumentCount' => '2',
1766
        ],
1767
        'SUMXMY2' => [
1768
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1769
            'functionCall' => [MathTrig::class, 'SUMXMY2'],
1770
            'argumentCount' => '2',
1771
        ],
1772
        'SYD' => [
1773
            'category' => Category::CATEGORY_FINANCIAL,
1774
            'functionCall' => [Financial::class, 'SYD'],
1775
            'argumentCount' => '4',
1776
        ],
1777
        'T' => [
1778
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1779
            'functionCall' => [TextData::class, 'RETURNSTRING'],
1780
            'argumentCount' => '1',
1781
        ],
1782
        'TAN' => [
1783
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1784
            'functionCall' => 'tan',
1785
            'argumentCount' => '1',
1786
        ],
1787
        'TANH' => [
1788
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1789
            'functionCall' => 'tanh',
1790
            'argumentCount' => '1',
1791
        ],
1792
        'TBILLEQ' => [
1793
            'category' => Category::CATEGORY_FINANCIAL,
1794
            'functionCall' => [Financial::class, 'TBILLEQ'],
1795
            'argumentCount' => '3',
1796
        ],
1797
        'TBILLPRICE' => [
1798
            'category' => Category::CATEGORY_FINANCIAL,
1799
            'functionCall' => [Financial::class, 'TBILLPRICE'],
1800
            'argumentCount' => '3',
1801
        ],
1802
        'TBILLYIELD' => [
1803
            'category' => Category::CATEGORY_FINANCIAL,
1804
            'functionCall' => [Financial::class, 'TBILLYIELD'],
1805
            'argumentCount' => '3',
1806
        ],
1807
        'TDIST' => [
1808
            'category' => Category::CATEGORY_STATISTICAL,
1809
            'functionCall' => [Statistical::class, 'TDIST'],
1810
            'argumentCount' => '3',
1811
        ],
1812
        'TEXT' => [
1813
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1814
            'functionCall' => [TextData::class, 'TEXTFORMAT'],
1815
            'argumentCount' => '2',
1816
        ],
1817
        'TIME' => [
1818
            'category' => Category::CATEGORY_DATE_AND_TIME,
1819
            'functionCall' => [DateTime::class, 'TIME'],
1820
            'argumentCount' => '3',
1821
        ],
1822
        'TIMEVALUE' => [
1823
            'category' => Category::CATEGORY_DATE_AND_TIME,
1824
            'functionCall' => [DateTime::class, 'TIMEVALUE'],
1825
            'argumentCount' => '1',
1826
        ],
1827
        'TINV' => [
1828
            'category' => Category::CATEGORY_STATISTICAL,
1829
            'functionCall' => [Statistical::class, 'TINV'],
1830
            'argumentCount' => '2',
1831
        ],
1832
        'TODAY' => [
1833
            'category' => Category::CATEGORY_DATE_AND_TIME,
1834
            'functionCall' => [DateTime::class, 'DATENOW'],
1835
            'argumentCount' => '0',
1836
        ],
1837
        'TRANSPOSE' => [
1838
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1839
            'functionCall' => [LookupRef::class, 'TRANSPOSE'],
1840
            'argumentCount' => '1',
1841
        ],
1842
        'TREND' => [
1843
            'category' => Category::CATEGORY_STATISTICAL,
1844
            'functionCall' => [Statistical::class, 'TREND'],
1845
            'argumentCount' => '1-4',
1846
        ],
1847
        'TRIM' => [
1848
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1849
            'functionCall' => [TextData::class, 'TRIMSPACES'],
1850
            'argumentCount' => '1',
1851
        ],
1852
        'TRIMMEAN' => [
1853
            'category' => Category::CATEGORY_STATISTICAL,
1854
            'functionCall' => [Statistical::class, 'TRIMMEAN'],
1855
            'argumentCount' => '2',
1856
        ],
1857
        'TRUE' => [
1858
            'category' => Category::CATEGORY_LOGICAL,
1859
            'functionCall' => [Logical::class, 'TRUE'],
1860
            'argumentCount' => '0',
1861
        ],
1862
        'TRUNC' => [
1863
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1864
            'functionCall' => [MathTrig::class, 'TRUNC'],
1865
            'argumentCount' => '1,2',
1866
        ],
1867
        'TTEST' => [
1868
            'category' => Category::CATEGORY_STATISTICAL,
1869
            'functionCall' => [Functions::class, 'DUMMY'],
1870
            'argumentCount' => '4',
1871
        ],
1872
        'TYPE' => [
1873
            'category' => Category::CATEGORY_INFORMATION,
1874
            'functionCall' => [Functions::class, 'TYPE'],
1875
            'argumentCount' => '1',
1876
        ],
1877
        'UPPER' => [
1878
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1879
            'functionCall' => [TextData::class, 'UPPERCASE'],
1880
            'argumentCount' => '1',
1881
        ],
1882
        'USDOLLAR' => [
1883
            'category' => Category::CATEGORY_FINANCIAL,
1884
            'functionCall' => [Functions::class, 'DUMMY'],
1885
            'argumentCount' => '2',
1886
        ],
1887
        'VALUE' => [
1888
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1889
            'functionCall' => [TextData::class, 'VALUE'],
1890
            'argumentCount' => '1',
1891
        ],
1892
        'VAR' => [
1893
            'category' => Category::CATEGORY_STATISTICAL,
1894
            'functionCall' => [Statistical::class, 'VARFunc'],
1895
            'argumentCount' => '1+',
1896
        ],
1897
        'VARA' => [
1898
            'category' => Category::CATEGORY_STATISTICAL,
1899
            'functionCall' => [Statistical::class, 'VARA'],
1900
            'argumentCount' => '1+',
1901
        ],
1902
        'VARP' => [
1903
            'category' => Category::CATEGORY_STATISTICAL,
1904
            'functionCall' => [Statistical::class, 'VARP'],
1905
            'argumentCount' => '1+',
1906
        ],
1907
        'VARPA' => [
1908
            'category' => Category::CATEGORY_STATISTICAL,
1909
            'functionCall' => [Statistical::class, 'VARPA'],
1910
            'argumentCount' => '1+',
1911
        ],
1912
        'VDB' => [
1913
            'category' => Category::CATEGORY_FINANCIAL,
1914
            'functionCall' => [Functions::class, 'DUMMY'],
1915
            'argumentCount' => '5-7',
1916
        ],
1917
        'VLOOKUP' => [
1918
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1919
            'functionCall' => [LookupRef::class, 'VLOOKUP'],
1920
            'argumentCount' => '3,4',
1921
        ],
1922
        'WEEKDAY' => [
1923
            'category' => Category::CATEGORY_DATE_AND_TIME,
1924
            'functionCall' => [DateTime::class, 'WEEKDAY'],
1925
            'argumentCount' => '1,2',
1926
        ],
1927
        'WEEKNUM' => [
1928
            'category' => Category::CATEGORY_DATE_AND_TIME,
1929
            'functionCall' => [DateTime::class, 'WEEKNUM'],
1930
            'argumentCount' => '1,2',
1931
        ],
1932
        'WEIBULL' => [
1933
            'category' => Category::CATEGORY_STATISTICAL,
1934
            'functionCall' => [Statistical::class, 'WEIBULL'],
1935
            'argumentCount' => '4',
1936
        ],
1937
        'WORKDAY' => [
1938
            'category' => Category::CATEGORY_DATE_AND_TIME,
1939
            'functionCall' => [DateTime::class, 'WORKDAY'],
1940
            'argumentCount' => '2+',
1941
        ],
1942
        'XIRR' => [
1943
            'category' => Category::CATEGORY_FINANCIAL,
1944
            'functionCall' => [Financial::class, 'XIRR'],
1945
            'argumentCount' => '2,3',
1946
        ],
1947
        'XNPV' => [
1948
            'category' => Category::CATEGORY_FINANCIAL,
1949
            'functionCall' => [Financial::class, 'XNPV'],
1950
            'argumentCount' => '3',
1951
        ],
1952
        'YEAR' => [
1953
            'category' => Category::CATEGORY_DATE_AND_TIME,
1954
            'functionCall' => [DateTime::class, 'YEAR'],
1955
            'argumentCount' => '1',
1956
        ],
1957
        'YEARFRAC' => [
1958
            'category' => Category::CATEGORY_DATE_AND_TIME,
1959
            'functionCall' => [DateTime::class, 'YEARFRAC'],
1960
            'argumentCount' => '2,3',
1961
        ],
1962
        'YIELD' => [
1963
            'category' => Category::CATEGORY_FINANCIAL,
1964
            'functionCall' => [Functions::class, 'DUMMY'],
1965
            'argumentCount' => '6,7',
1966
        ],
1967
        'YIELDDISC' => [
1968
            'category' => Category::CATEGORY_FINANCIAL,
1969
            'functionCall' => [Financial::class, 'YIELDDISC'],
1970
            'argumentCount' => '4,5',
1971
        ],
1972
        'YIELDMAT' => [
1973
            'category' => Category::CATEGORY_FINANCIAL,
1974
            'functionCall' => [Financial::class, 'YIELDMAT'],
1975
            'argumentCount' => '5,6',
1976
        ],
1977
        'ZTEST' => [
1978
            'category' => Category::CATEGORY_STATISTICAL,
1979
            'functionCall' => [Statistical::class, 'ZTEST'],
1980
            'argumentCount' => '2-3',
1981
        ],
1982
    ];
1983
1984
    //    Internal functions used for special control purposes
1985
    private static $controlFunctions = [
1986
        'MKMATRIX' => [
1987
            'argumentCount' => '*',
1988
            'functionCall' => 'self::mkMatrix',
1989
        ],
1990
    ];
1991
1992 134
    public function __construct(Spreadsheet $spreadsheet = null)
1993
    {
1994 134
        $this->delta = 1 * pow(10, 0 - ini_get('precision'));
0 ignored issues
show
Documentation Bug introduced by
It seems like 1 * pow(10, 0 - ini_get('precision')) can also be of type integer. However, the property $delta is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1995
1996 134
        $this->spreadsheet = $spreadsheet;
0 ignored issues
show
Documentation Bug introduced by
It seems like $spreadsheet can also be of type object<PhpOffice\PhpSpreadsheet\Spreadsheet>. However, the property $spreadsheet is declared as type object<PhpOffice\PhpSpre...ulation\PhpSpreadsheet>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1997 134
        $this->cyclicReferenceStack = new CyclicReferenceStack();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \PhpOffice\PhpSpread...\CyclicReferenceStack() of type object<PhpOffice\PhpSpre...e\CyclicReferenceStack> is incompatible with the declared type array of property $cyclicReferenceStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1998 134
        $this->debugLog = new Logger($this->cyclicReferenceStack);
1999 134
    }
2000
2001 1
    private static function loadLocales()
2002
    {
2003 1
        $localeFileDirectory = __DIR__ . '/locale/';
2004 1
        foreach (glob($localeFileDirectory . '/*', GLOB_ONLYDIR) as $filename) {
2005 1
            $filename = substr($filename, strlen($localeFileDirectory) + 1);
2006 1
            if ($filename != 'en') {
2007 1
                self::$validLocaleLanguages[] = $filename;
2008
            }
2009
        }
2010 1
    }
2011
2012
    /**
2013
     * Get an instance of this class.
2014
     *
2015
     * @param Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object,
2016
     *                                    or NULL to create a standalone claculation engine
2017
     *
2018
     * @return Calculation
2019
     */
2020 518
    public static function getInstance(Spreadsheet $spreadsheet = null)
2021
    {
2022 518
        if ($spreadsheet !== null) {
2023 87
            $instance = $spreadsheet->getCalculationEngine();
2024 87
            if (isset($instance)) {
2025 87
                return $instance;
2026
            }
2027
        }
2028
2029 445 View Code Duplication
        if (!isset(self::$instance) || (self::$instance === null)) {
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...
2030 14
            self::$instance = new self();
0 ignored issues
show
Documentation Bug introduced by
It seems like new self() of type object<PhpOffice\PhpSpre...alculation\Calculation> is incompatible with the declared type object<PhpOffice\PhpSpreadsheet\Calculation> of property $instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2031
        }
2032
2033 445
        return self::$instance;
0 ignored issues
show
Bug Compatibility introduced by
The expression self::$instance; of type PhpOffice\PhpSpreadsheet...Spreadsheet\Calculation adds the type PhpOffice\PhpSpreadsheet\Calculation to the return on line 2033 which is incompatible with the return type documented by PhpOffice\PhpSpreadsheet...alculation::getInstance of type PhpOffice\PhpSpreadsheet\Calculation\Calculation.
Loading history...
2034
    }
2035
2036
    /**
2037
     * Flush the calculation cache for any existing instance of this class
2038
     *        but only if a Calculation instance exists.
2039
     */
2040
    public function flushInstance()
2041
    {
2042
        $this->clearCalculationCache();
2043
    }
2044
2045
    /**
2046
     * Get the Logger for this calculation engine instance.
2047
     *
2048
     * @return Logger
2049
     */
2050 60
    public function getDebugLog()
2051
    {
2052 60
        return $this->debugLog;
2053
    }
2054
2055
    /**
2056
     * __clone implementation. Cloning should not be allowed in a Singleton!
2057
     *
2058
     * @throws Exception
2059
     */
2060
    final public function __clone()
2061
    {
2062
        throw new Exception('Cloning the calculation engine is not allowed!');
2063
    }
2064
2065
    /**
2066
     * Return the locale-specific translation of TRUE.
2067
     *
2068
     * @return string locale-specific translation of TRUE
2069
     */
2070 37
    public static function getTRUE()
2071
    {
2072 37
        return self::$localeBoolean['TRUE'];
2073
    }
2074
2075
    /**
2076
     * Return the locale-specific translation of FALSE.
2077
     *
2078
     * @return string locale-specific translation of FALSE
2079
     */
2080 30
    public static function getFALSE()
2081
    {
2082 30
        return self::$localeBoolean['FALSE'];
2083
    }
2084
2085
    /**
2086
     * Set the Array Return Type (Array or Value of first element in the array).
2087
     *
2088
     * @param string $returnType Array return type
2089
     *
2090
     * @return bool Success or failure
2091
     */
2092 19
    public static function setArrayReturnType($returnType)
2093
    {
2094 19
        if (($returnType == self::RETURN_ARRAY_AS_VALUE) ||
2095 12
            ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
2096 19
            ($returnType == self::RETURN_ARRAY_AS_ARRAY)) {
2097 19
            self::$returnArrayAsType = $returnType;
2098
2099 19
            return true;
2100
        }
2101
2102
        return false;
2103
    }
2104
2105
    /**
2106
     * Return the Array Return Type (Array or Value of first element in the array).
2107
     *
2108
     * @return string $returnType Array return type
2109
     */
2110 8
    public static function getArrayReturnType()
2111
    {
2112 8
        return self::$returnArrayAsType;
2113
    }
2114
2115
    /**
2116
     * Is calculation caching enabled?
2117
     *
2118
     * @return bool
2119
     */
2120
    public function getCalculationCacheEnabled()
2121
    {
2122
        return $this->calculationCacheEnabled;
2123
    }
2124
2125
    /**
2126
     * Enable/disable calculation cache.
2127
     *
2128
     * @param bool $pValue
2129
     */
2130
    public function setCalculationCacheEnabled($pValue)
2131
    {
2132
        $this->calculationCacheEnabled = $pValue;
2133
        $this->clearCalculationCache();
2134
    }
2135
2136
    /**
2137
     * Enable calculation cache.
2138
     */
2139
    public function enableCalculationCache()
2140
    {
2141
        $this->setCalculationCacheEnabled(true);
2142
    }
2143
2144
    /**
2145
     * Disable calculation cache.
2146
     */
2147
    public function disableCalculationCache()
2148
    {
2149
        $this->setCalculationCacheEnabled(false);
2150
    }
2151
2152
    /**
2153
     * Clear calculation cache.
2154
     */
2155
    public function clearCalculationCache()
2156
    {
2157
        $this->calculationCache = [];
2158
    }
2159
2160
    /**
2161
     * Clear calculation cache for a specified worksheet.
2162
     *
2163
     * @param string $worksheetName
2164
     */
2165 2
    public function clearCalculationCacheForWorksheet($worksheetName)
2166
    {
2167 2
        if (isset($this->calculationCache[$worksheetName])) {
2168
            unset($this->calculationCache[$worksheetName]);
2169
        }
2170 2
    }
2171
2172
    /**
2173
     * Rename calculation cache for a specified worksheet.
2174
     *
2175
     * @param string $fromWorksheetName
2176
     * @param string $toWorksheetName
2177
     */
2178 134
    public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName)
2179
    {
2180 134
        if (isset($this->calculationCache[$fromWorksheetName])) {
2181
            $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
2182
            unset($this->calculationCache[$fromWorksheetName]);
2183
        }
2184 134
    }
2185
2186
    /**
2187
     * Get the currently defined locale code.
2188
     *
2189
     * @return string
2190
     */
2191
    public function getLocale()
2192
    {
2193
        return self::$localeLanguage;
2194
    }
2195
2196
    /**
2197
     * Set the locale code.
2198
     *
2199
     * @param string $locale The locale to use for formula translation, eg: 'en_us'
2200
     *
2201
     * @return bool
2202
     */
2203 426
    public function setLocale($locale)
2204
    {
2205
        //    Identify our locale and language
2206 426
        $language = $locale = strtolower($locale);
2207 426
        if (strpos($locale, '_') !== false) {
2208 426
            list($language) = explode('_', $locale);
2209
        }
2210
2211 426
        if (count(self::$validLocaleLanguages) == 1) {
2212 1
            self::loadLocales();
2213
        }
2214
        //    Test whether we have any language data for this language (any locale)
2215 426
        if (in_array($language, self::$validLocaleLanguages)) {
2216
            //    initialise language/locale settings
2217 426
            self::$localeFunctions = [];
2218 426
            self::$localeArgumentSeparator = ',';
2219 426
            self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('TRUE' => 'TRUE', ...LSE', 'NULL' => 'NULL') of type array<string,string,{"TR...ring","NULL":"string"}> is incompatible with the declared type array<integer,string> of property $localeBoolean.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2220
            //    Default is English, if user isn't requesting english, then read the necessary data from the locale files
2221 426
            if ($locale != 'en_us') {
2222
                //    Search for a file with a list of function names for locale
2223 17
                $functionNamesFile = __DIR__ . '/locale/' . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'functions';
2224 17
                if (!file_exists($functionNamesFile)) {
2225
                    //    If there isn't a locale specific function file, look for a language specific function file
2226
                    $functionNamesFile = __DIR__ . '/locale/' . $language . DIRECTORY_SEPARATOR . 'functions';
2227
                    if (!file_exists($functionNamesFile)) {
2228
                        return false;
2229
                    }
2230
                }
2231
                //    Retrieve the list of locale or language specific function names
2232 17
                $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2233 17
                foreach ($localeFunctions as $localeFunction) {
2234 17
                    list($localeFunction) = explode('##', $localeFunction); //    Strip out comments
2235 17
                    if (strpos($localeFunction, '=') !== false) {
2236 17
                        list($fName, $lfName) = explode('=', $localeFunction);
2237 17
                        $fName = trim($fName);
2238 17
                        $lfName = trim($lfName);
2239 17
                        if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
2240 17
                            self::$localeFunctions[$fName] = $lfName;
2241
                        }
2242
                    }
2243
                }
2244
                //    Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
2245 17
                if (isset(self::$localeFunctions['TRUE'])) {
2246 17
                    self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
2247
                }
2248 17
                if (isset(self::$localeFunctions['FALSE'])) {
2249 17
                    self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
2250
                }
2251
2252 17
                $configFile = __DIR__ . '/locale/' . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'config';
2253 17
                if (!file_exists($configFile)) {
2254
                    $configFile = __DIR__ . '/locale/' . $language . DIRECTORY_SEPARATOR . 'config';
2255
                }
2256 17
                if (file_exists($configFile)) {
2257 17
                    $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2258 17
                    foreach ($localeSettings as $localeSetting) {
2259 17
                        list($localeSetting) = explode('##', $localeSetting); //    Strip out comments
2260 17
                        if (strpos($localeSetting, '=') !== false) {
2261 17
                            list($settingName, $settingValue) = explode('=', $localeSetting);
2262 17
                            $settingName = strtoupper(trim($settingName));
2263
                            switch ($settingName) {
2264 17
                                case 'ARGUMENTSEPARATOR':
2265 17
                                    self::$localeArgumentSeparator = trim($settingValue);
2266
2267 17
                                    break;
2268
                            }
2269
                        }
2270
                    }
2271
                }
2272
            }
2273
2274 426
            self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
2275 426
            self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
2276 426
            self::$localeLanguage = $locale;
2277
2278 426
            return true;
2279
        }
2280
2281
        return false;
2282
    }
2283
2284
    /**
2285
     * @param string $fromSeparator
2286
     * @param string $toSeparator
2287
     * @param string $formula
2288
     * @param bool $inBraces
2289
     *
2290
     * @return string
2291
     */
2292 5
    public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
2293
    {
2294 5
        $strlen = mb_strlen($formula);
2295 5
        for ($i = 0; $i < $strlen; ++$i) {
2296 5
            $chr = mb_substr($formula, $i, 1);
2297
            switch ($chr) {
2298 5
                case '{':
2299
                    $inBraces = true;
2300
2301
                    break;
2302 5
                case '}':
2303
                    $inBraces = false;
2304
2305
                    break;
2306 5
                case $fromSeparator:
2307
                    if (!$inBraces) {
2308
                        $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
2309
                    }
2310
            }
2311
        }
2312
2313 5
        return $formula;
2314
    }
2315
2316
    /**
2317
     * @param string $fromSeparator
2318
     * @param string $toSeparator
2319
     * @param mixed $from
2320
     * @param mixed $to
2321
     * @param mixed $formula
2322
     */
2323
    private static function translateFormula($from, $to, $formula, $fromSeparator, $toSeparator)
2324
    {
2325
        //    Convert any Excel function names to the required language
2326
        if (self::$localeLanguage !== 'en_us') {
2327
            $inBraces = false;
2328
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
2329
            if (strpos($formula, '"') !== false) {
2330
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
2331
                //        the formula
2332
                $temp = explode('"', $formula);
2333
                $i = false;
2334
                foreach ($temp as &$value) {
2335
                    //    Only count/replace in alternating array entries
2336 View Code Duplication
                    if ($i = !$i) {
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...
2337
                        $value = preg_replace($from, $to, $value);
2338
                        $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
2339
                    }
2340
                }
2341
                unset($value);
2342
                //    Then rebuild the formula string
2343
                $formula = implode('"', $temp);
2344 View Code Duplication
            } else {
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...
2345
                //    If there's no quoted strings, then we do a simple count/replace
2346
                $formula = preg_replace($from, $to, $formula);
2347
                $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
0 ignored issues
show
Bug introduced by
It seems like $formula can also be of type array<integer,string>; however, PhpOffice\PhpSpreadsheet...n::translateSeparator() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2348
            }
2349
        }
2350
2351
        return $formula;
2352
    }
2353
2354
    private static $functionReplaceFromExcel = null;
2355
    private static $functionReplaceToLocale = null;
2356
2357 View Code Duplication
    public function _translateFormulaToLocale($formula)
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...
2358
    {
2359
        if (self::$functionReplaceFromExcel === null) {
2360
            self::$functionReplaceFromExcel = [];
2361
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2362
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName) . '([\s]*\()/Ui';
2363
            }
2364
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2365
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean) . '([^\w\.])/Ui';
2366
            }
2367
        }
2368
2369
        if (self::$functionReplaceToLocale === null) {
2370
            self::$functionReplaceToLocale = [];
2371
            foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
2372
                self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
2373
            }
2374
            foreach (array_values(self::$localeBoolean) as $localeBoolean) {
2375
                self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
2376
            }
2377
        }
2378
2379
        return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator);
2380
    }
2381
2382
    private static $functionReplaceFromLocale = null;
2383
    private static $functionReplaceToExcel = null;
2384
2385 View Code Duplication
    public function _translateFormulaToEnglish($formula)
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...
2386
    {
2387
        if (self::$functionReplaceFromLocale === null) {
2388
            self::$functionReplaceFromLocale = [];
2389
            foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
2390
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName) . '([\s]*\()/Ui';
2391
            }
2392
            foreach (array_values(self::$localeBoolean) as $excelBoolean) {
2393
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean) . '([^\w\.])/Ui';
2394
            }
2395
        }
2396
2397
        if (self::$functionReplaceToExcel === null) {
2398
            self::$functionReplaceToExcel = [];
2399
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2400
                self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
2401
            }
2402
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2403
                self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
2404
            }
2405
        }
2406
2407
        return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
2408
    }
2409
2410 45
    public static function localeFunc($function)
2411
    {
2412 45
        if (self::$localeLanguage !== 'en_us') {
2413
            $functionName = trim($function, '(');
2414
            if (isset(self::$localeFunctions[$functionName])) {
2415
                $brace = ($functionName != $function);
2416
                $function = self::$localeFunctions[$functionName];
2417
                if ($brace) {
2418
                    $function .= '(';
2419
                }
2420
            }
2421
        }
2422
2423 45
        return $function;
2424
    }
2425
2426
    /**
2427
     * Wrap string values in quotes.
2428
     *
2429
     * @param mixed $value
2430
     *
2431
     * @return mixed
2432
     */
2433 79
    public static function wrapResult($value)
2434
    {
2435 79
        if (is_string($value)) {
2436
            //    Error values cannot be "wrapped"
2437 67
            if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
2438
                //    Return Excel errors "as is"
2439 9
                return $value;
2440
            }
2441
            //    Return strings wrapped in quotes
2442 59
            return '"' . $value . '"';
2443
            //    Convert numeric errors to NaN error
2444 43
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2445
            return Functions::NAN();
2446
        }
2447
2448 43
        return $value;
2449
    }
2450
2451
    /**
2452
     * Remove quotes used as a wrapper to identify string values.
2453
     *
2454
     * @param mixed $value
2455
     *
2456
     * @return mixed
2457
     */
2458 93
    public static function unwrapResult($value)
2459
    {
2460 93
        if (is_string($value)) {
2461 72
            if ((isset($value[0])) && ($value[0] == '"') && (substr($value, -1) == '"')) {
2462 72
                return substr($value, 1, -1);
2463
            }
2464
            //    Convert numeric errors to NAN error
2465 58
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2466
            return Functions::NAN();
2467
        }
2468
2469 59
        return $value;
2470
    }
2471
2472
    /**
2473
     * Calculate cell value (using formula from a cell ID)
2474
     * Retained for backward compatibility.
2475
     *
2476
     * @param Cell $pCell Cell to calculate
2477
     *
2478
     * @throws Exception
2479
     *
2480
     * @return mixed
2481
     */
2482
    public function calculate(Cell $pCell = null)
2483
    {
2484
        try {
2485
            return $this->calculateCellValue($pCell);
2486
        } catch (\Exception $e) {
2487
            throw new Exception($e->getMessage());
2488
        }
2489
    }
2490
2491
    /**
2492
     * Calculate the value of a cell formula.
2493
     *
2494
     * @param Cell $pCell Cell to calculate
2495
     * @param bool $resetLog Flag indicating whether the debug log should be reset or not
2496
     *
2497
     * @throws Exception
2498
     *
2499
     * @return mixed
2500
     */
2501 48
    public function calculateCellValue(Cell $pCell = null, $resetLog = true)
2502
    {
2503 48
        if ($pCell === null) {
2504
            return null;
2505
        }
2506
2507 48
        $returnArrayAsType = self::$returnArrayAsType;
2508 48
        if ($resetLog) {
2509
            //    Initialise the logging settings if requested
2510 47
            $this->formulaError = null;
2511 47
            $this->debugLog->clearLog();
2512 47
            $this->cyclicReferenceStack->clear();
0 ignored issues
show
Bug introduced by
The method clear cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2513 47
            $this->cyclicFormulaCounter = 1;
2514
2515 47
            self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
2516
        }
2517
2518
        //    Execute the calculation for the cell formula
2519 48
        $this->cellStack[] = [
2520 48
            'sheet' => $pCell->getWorksheet()->getTitle(),
2521 48
            'cell' => $pCell->getCoordinate(),
2522
        ];
2523
2524
        try {
2525 48
            $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
2526 48
            $cellAddress = array_pop($this->cellStack);
2527 48
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2528
        } catch (\Exception $e) {
2529
            $cellAddress = array_pop($this->cellStack);
2530
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2531
2532
            throw new Exception($e->getMessage());
2533
        }
2534
2535 48
        if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
2536 2
            self::$returnArrayAsType = $returnArrayAsType;
2537 2
            $testResult = Functions::flattenArray($result);
2538 2
            if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
2539
                return Functions::VALUE();
2540
            }
2541
            //    If there's only a single cell in the array, then we allow it
2542 2
            if (count($testResult) != 1) {
2543
                //    If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
2544
                $r = array_keys($result);
2545
                $r = array_shift($r);
2546
                if (!is_numeric($r)) {
2547
                    return Functions::VALUE();
2548
                }
2549
                if (is_array($result[$r])) {
2550
                    $c = array_keys($result[$r]);
2551
                    $c = array_shift($c);
2552
                    if (!is_numeric($c)) {
2553
                        return Functions::VALUE();
2554
                    }
2555
                }
2556
            }
2557 2
            $result = array_shift($testResult);
2558
        }
2559 48
        self::$returnArrayAsType = $returnArrayAsType;
2560
2561 48
        if ($result === null) {
2562
            return 0;
2563 48
        } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
2564
            return Functions::NAN();
2565
        }
2566
2567 48
        return $result;
2568
    }
2569
2570
    /**
2571
     * Validate and parse a formula string.
2572
     *
2573
     * @param string $formula Formula to parse
2574
     *
2575
     * @throws Exception
2576
     *
2577
     * @return array
2578
     */
2579
    public function parseFormula($formula)
2580
    {
2581
        //    Basic validation that this is indeed a formula
2582
        //    We return an empty array if not
2583
        $formula = trim($formula);
2584
        if ((!isset($formula[0])) || ($formula[0] != '=')) {
2585
            return [];
2586
        }
2587
        $formula = ltrim(substr($formula, 1));
2588
        if (!isset($formula[0])) {
2589
            return [];
2590
        }
2591
2592
        //    Parse the formula and return the token stack
2593
        return $this->_parseFormula($formula);
2594
    }
2595
2596
    /**
2597
     * Calculate the value of a formula.
2598
     *
2599
     * @param string $formula Formula to parse
2600
     * @param string $cellID Address of the cell to calculate
2601
     * @param Cell $pCell Cell to calculate
2602
     *
2603
     * @throws Exception
2604
     *
2605
     * @return mixed
2606
     */
2607
    public function calculateFormula($formula, $cellID = null, Cell $pCell = null)
2608
    {
2609
        //    Initialise the logging settings
2610
        $this->formulaError = null;
2611
        $this->debugLog->clearLog();
2612
        $this->cyclicReferenceStack->clear();
0 ignored issues
show
Bug introduced by
The method clear cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2613
2614
        if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
2615
            $cellID = 'A1';
2616
            $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
2617
        } else {
2618
            //    Disable calculation cacheing because it only applies to cell calculations, not straight formulae
2619
            //    But don't actually flush any cache
2620
            $resetCache = $this->getCalculationCacheEnabled();
2621
            $this->calculationCacheEnabled = false;
2622
        }
2623
2624
        //    Execute the calculation
2625
        try {
2626
            $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
2627
        } catch (\Exception $e) {
2628
            throw new Exception($e->getMessage());
2629
        }
2630
2631
        if ($this->spreadsheet === null) {
2632
            //    Reset calculation cacheing to its previous state
2633
            $this->calculationCacheEnabled = $resetCache;
0 ignored issues
show
Bug introduced by
The variable $resetCache 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...
2634
        }
2635
2636
        return $result;
2637
    }
2638
2639
    /**
2640
     * @param string $cellReference
2641
     * @param mixed $cellValue
2642
     *
2643
     * @return bool
2644
     */
2645 48
    public function getValueFromCache($cellReference, &$cellValue)
2646
    {
2647
        // Is calculation cacheing enabled?
2648
        // Is the value present in calculation cache?
2649 48
        $this->debugLog->writeDebugLog('Testing cache value for cell ', $cellReference);
2650 48
        if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
2651 40
            $this->debugLog->writeDebugLog('Retrieving value for cell ', $cellReference, ' from cache');
2652
            // Return the cached result
2653 40
            $cellValue = $this->calculationCache[$cellReference];
2654
2655 40
            return true;
2656
        }
2657
2658 48
        return false;
2659
    }
2660
2661
    /**
2662
     * @param string $cellReference
2663
     * @param mixed $cellValue
2664
     */
2665 48
    public function saveValueToCache($cellReference, $cellValue)
2666
    {
2667 48
        if ($this->calculationCacheEnabled) {
2668 48
            $this->calculationCache[$cellReference] = $cellValue;
2669
        }
2670 48
    }
2671
2672
    /**
2673
     * Parse a cell formula and calculate its value.
2674
     *
2675
     * @param string $formula The formula to parse and calculate
2676
     * @param string $cellID The ID (e.g. A3) of the cell that we are calculating
2677
     * @param Cell $pCell Cell to calculate
2678
     *
2679
     * @throws Exception
2680
     *
2681
     * @return mixed
2682
     */
2683 117
    public function _calculateFormulaValue($formula, $cellID = null, Cell $pCell = null)
2684
    {
2685 117
        $cellValue = null;
2686
2687
        //    Basic validation that this is indeed a formula
2688
        //    We simply return the cell value if not
2689 117
        $formula = trim($formula);
2690 117
        if ($formula[0] != '=') {
2691
            return self::wrapResult($formula);
2692
        }
2693 117
        $formula = ltrim(substr($formula, 1));
2694 117
        if (!isset($formula[0])) {
2695
            return self::wrapResult($formula);
2696
        }
2697
2698 117
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
2699 117
        $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
2700 117
        $wsCellReference = $wsTitle . '!' . $cellID;
2701
2702 117
        if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
2703 40
            return $cellValue;
2704
        }
2705
2706 117
        if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
0 ignored issues
show
Bug introduced by
The method onStack cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2707
            if ($this->cyclicFormulaCount <= 0) {
2708
                $this->cyclicFormulaCell = '';
2709
2710
                return $this->raiseFormulaError('Cyclic Reference in Formula');
2711
            } elseif ($this->cyclicFormulaCell === $wsCellReference) {
2712
                ++$this->cyclicFormulaCounter;
2713
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2714
                    $this->cyclicFormulaCell = '';
2715
2716
                    return $cellValue;
2717
                }
2718
            } elseif ($this->cyclicFormulaCell == '') {
2719
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2720
                    return $cellValue;
2721
                }
2722
                $this->cyclicFormulaCell = $wsCellReference;
2723
            }
2724
        }
2725
2726
        //    Parse the formula onto the token stack and calculate the value
2727 117
        $this->cyclicReferenceStack->push($wsCellReference);
0 ignored issues
show
Bug introduced by
The method push cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2728 117
        $cellValue = $this->processTokenStack($this->_parseFormula($formula, $pCell), $cellID, $pCell);
2729 117
        $this->cyclicReferenceStack->pop();
0 ignored issues
show
Bug introduced by
The method pop cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2730
2731
        // Save to calculation cache
2732 117
        if ($cellID !== null) {
2733 48
            $this->saveValueToCache($wsCellReference, $cellValue);
2734
        }
2735
2736
        //    Return the calculated value
2737 117
        return $cellValue;
2738
    }
2739
2740
    /**
2741
     * Ensure that paired matrix operands are both matrices and of the same size.
2742
     *
2743
     * @param mixed &$operand1 First matrix operand
2744
     * @param mixed &$operand2 Second matrix operand
2745
     * @param int $resize Flag indicating whether the matrices should be resized to match
2746
     *                                        and (if so), whether the smaller dimension should grow or the
2747
     *                                        larger should shrink.
2748
     *                                            0 = no resize
2749
     *                                            1 = shrink to fit
2750
     *                                            2 = extend to fit
2751
     */
2752 5
    private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1)
2753
    {
2754
        //    Examine each of the two operands, and turn them into an array if they aren't one already
2755
        //    Note that this function should only be called if one or both of the operand is already an array
2756 5
        if (!is_array($operand1)) {
2757
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand2);
2758
            $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
2759
            $resize = 0;
2760 5
        } elseif (!is_array($operand2)) {
2761 2
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand1);
2762 2
            $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
2763 2
            $resize = 0;
2764
        }
2765
2766 5
        list($matrix1Rows, $matrix1Columns) = self::getMatrixDimensions($operand1);
2767 5
        list($matrix2Rows, $matrix2Columns) = self::getMatrixDimensions($operand2);
2768 5
        if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
2769 5
            $resize = 1;
2770
        }
2771
2772 5
        if ($resize == 2) {
2773
            //    Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
2774
            self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2775 5
        } elseif ($resize == 1) {
2776
            //    Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
2777 5
            self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2778
        }
2779
2780 5
        return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
2781
    }
2782
2783
    /**
2784
     * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0.
2785
     *
2786
     * @param mixed &$matrix matrix operand
2787
     *
2788
     * @return int[] An array comprising the number of rows, and number of columns
2789
     */
2790 5
    private static function getMatrixDimensions(&$matrix)
2791
    {
2792 5
        $matrixRows = count($matrix);
2793 5
        $matrixColumns = 0;
2794 5
        foreach ($matrix as $rowKey => $rowValue) {
2795 5
            $matrixColumns = max(count($rowValue), $matrixColumns);
2796 5
            if (!is_array($rowValue)) {
2797
                $matrix[$rowKey] = [$rowValue];
2798
            } else {
2799 5
                $matrix[$rowKey] = array_values($rowValue);
2800
            }
2801
        }
2802 5
        $matrix = array_values($matrix);
2803
2804 5
        return [$matrixRows, $matrixColumns];
2805
    }
2806
2807
    /**
2808
     * Ensure that paired matrix operands are both matrices of the same size.
2809
     *
2810
     * @param mixed &$matrix1 First matrix operand
2811
     * @param mixed &$matrix2 Second matrix operand
2812
     * @param int $matrix1Rows Row size of first matrix operand
2813
     * @param int $matrix1Columns Column size of first matrix operand
2814
     * @param int $matrix2Rows Row size of second matrix operand
2815
     * @param int $matrix2Columns Column size of second matrix operand
2816
     */
2817 5
    private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
2818
    {
2819 5 View Code Duplication
        if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
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...
2820
            if ($matrix2Rows < $matrix1Rows) {
2821
                for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
2822
                    unset($matrix1[$i]);
2823
                }
2824
            }
2825
            if ($matrix2Columns < $matrix1Columns) {
2826
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2827
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
2828
                        unset($matrix1[$i][$j]);
2829
                    }
2830
                }
2831
            }
2832
        }
2833
2834 5 View Code Duplication
        if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
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...
2835
            if ($matrix1Rows < $matrix2Rows) {
2836
                for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
2837
                    unset($matrix2[$i]);
2838
                }
2839
            }
2840
            if ($matrix1Columns < $matrix2Columns) {
2841
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2842
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
2843
                        unset($matrix2[$i][$j]);
2844
                    }
2845
                }
2846
            }
2847
        }
2848 5
    }
2849
2850
    /**
2851
     * Ensure that paired matrix operands are both matrices of the same size.
2852
     *
2853
     * @param mixed &$matrix1 First matrix operand
2854
     * @param mixed &$matrix2 Second matrix operand
2855
     * @param int $matrix1Rows Row size of first matrix operand
2856
     * @param int $matrix1Columns Column size of first matrix operand
2857
     * @param int $matrix2Rows Row size of second matrix operand
2858
     * @param int $matrix2Columns Column size of second matrix operand
2859
     */
2860
    private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
2861
    {
2862 View Code Duplication
        if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
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...
2863
            if ($matrix2Columns < $matrix1Columns) {
2864
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2865
                    $x = $matrix2[$i][$matrix2Columns - 1];
2866
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
2867
                        $matrix2[$i][$j] = $x;
2868
                    }
2869
                }
2870
            }
2871
            if ($matrix2Rows < $matrix1Rows) {
2872
                $x = $matrix2[$matrix2Rows - 1];
2873
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2874
                    $matrix2[$i] = $x;
2875
                }
2876
            }
2877
        }
2878
2879 View Code Duplication
        if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
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...
2880
            if ($matrix1Columns < $matrix2Columns) {
2881
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2882
                    $x = $matrix1[$i][$matrix1Columns - 1];
2883
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
2884
                        $matrix1[$i][$j] = $x;
2885
                    }
2886
                }
2887
            }
2888
            if ($matrix1Rows < $matrix2Rows) {
2889
                $x = $matrix1[$matrix1Rows - 1];
2890
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2891
                    $matrix1[$i] = $x;
2892
                }
2893
            }
2894
        }
2895
    }
2896
2897
    /**
2898
     * Format details of an operand for display in the log (based on operand type).
2899
     *
2900
     * @param mixed $value First matrix operand
2901
     *
2902
     * @return mixed
2903
     */
2904 116
    private function showValue($value)
2905
    {
2906 116
        if ($this->debugLog->getWriteDebugLog()) {
2907
            $testArray = Functions::flattenArray($value);
2908
            if (count($testArray) == 1) {
2909
                $value = array_pop($testArray);
2910
            }
2911
2912
            if (is_array($value)) {
2913
                $returnMatrix = [];
2914
                $pad = $rpad = ', ';
2915
                foreach ($value as $row) {
2916
                    if (is_array($row)) {
2917
                        $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
2918
                        $rpad = '; ';
2919
                    } else {
2920
                        $returnMatrix[] = $this->showValue($row);
2921
                    }
2922
                }
2923
2924
                return '{ ' . implode($rpad, $returnMatrix) . ' }';
2925
            } elseif (is_string($value) && (trim($value, '"') == $value)) {
2926
                return '"' . $value . '"';
2927
            } elseif (is_bool($value)) {
2928
                return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
2929
            }
2930
        }
2931
2932 116
        return Functions::flattenSingleValue($value);
2933
    }
2934
2935
    /**
2936
     * Format type and details of an operand for display in the log (based on operand type).
2937
     *
2938
     * @param mixed $value First matrix operand
2939
     *
2940
     * @return null|string
2941
     */
2942 116
    private function showTypeDetails($value)
2943
    {
2944 116
        if ($this->debugLog->getWriteDebugLog()) {
2945
            $testArray = Functions::flattenArray($value);
2946
            if (count($testArray) == 1) {
2947
                $value = array_pop($testArray);
2948
            }
2949
2950
            if ($value === null) {
2951
                return 'a NULL value';
2952
            } elseif (is_float($value)) {
2953
                $typeString = 'a floating point number';
2954
            } elseif (is_int($value)) {
2955
                $typeString = 'an integer number';
2956
            } elseif (is_bool($value)) {
2957
                $typeString = 'a boolean';
2958
            } elseif (is_array($value)) {
2959
                $typeString = 'a matrix';
2960
            } else {
2961
                if ($value == '') {
2962
                    return 'an empty string';
2963
                } elseif ($value[0] == '#') {
2964
                    return 'a ' . $value . ' error';
2965
                }
2966
                $typeString = 'a string';
2967
            }
2968
2969
            return $typeString . ' with a value of ' . $this->showValue($value);
2970
        }
2971 116
    }
2972
2973
    /**
2974
     * @param string $formula
2975
     *
2976
     * @return string
2977
     */
2978 117
    private function convertMatrixReferences($formula)
2979
    {
2980 117
        static $matrixReplaceFrom = ['{', ';', '}'];
2981 117
        static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
2982
2983
        //    Convert any Excel matrix references to the MKMATRIX() function
2984 117
        if (strpos($formula, '{') !== false) {
2985
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
2986
            if (strpos($formula, '"') !== false) {
2987
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
2988
                //        the formula
2989
                $temp = explode('"', $formula);
2990
                //    Open and Closed counts used for trapping mismatched braces in the formula
2991
                $openCount = $closeCount = 0;
2992
                $i = false;
2993
                foreach ($temp as &$value) {
2994
                    //    Only count/replace in alternating array entries
2995
                    if ($i = !$i) {
2996
                        $openCount += substr_count($value, '{');
2997
                        $closeCount += substr_count($value, '}');
2998
                        $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
2999
                    }
3000
                }
3001
                unset($value);
3002
                //    Then rebuild the formula string
3003
                $formula = implode('"', $temp);
3004
            } else {
3005
                //    If there's no quoted strings, then we do a simple count/replace
3006
                $openCount = substr_count($formula, '{');
3007
                $closeCount = substr_count($formula, '}');
3008
                $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
3009
            }
3010
            //    Trap for mismatched braces and trigger an appropriate error
3011
            if ($openCount < $closeCount) {
3012
                if ($openCount > 0) {
3013
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
3014
                }
3015
3016
                return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
3017
            } elseif ($openCount > $closeCount) {
3018
                if ($closeCount > 0) {
3019
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
3020
                }
3021
3022
                return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
3023
            }
3024
        }
3025
3026 117
        return $formula;
3027
    }
3028
3029
    private static function mkMatrix(...$args)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
3030
    {
3031
        return $args;
3032
    }
3033
3034
    //    Binary Operators
3035
    //    These operators always work on two values
3036
    //    Array key is the operator, the value indicates whether this is a left or right associative operator
3037
    private static $operatorAssociativity = [
3038
        '^' => 0, //    Exponentiation
3039
        '*' => 0, '/' => 0, //    Multiplication and Division
3040
        '+' => 0, '-' => 0, //    Addition and Subtraction
3041
        '&' => 0, //    Concatenation
3042
        '|' => 0, ':' => 0, //    Intersect and Range
3043
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3044
    ];
3045
3046
    //    Comparison (Boolean) Operators
3047
    //    These operators work on two values, but always return a boolean result
3048
    private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true];
3049
3050
    //    Operator Precedence
3051
    //    This list includes all valid operators, whether binary (including boolean) or unary (such as %)
3052
    //    Array key is the operator, the value is its precedence
3053
    private static $operatorPrecedence = [
3054
        ':' => 8, //    Range
3055
        '|' => 7, //    Intersect
3056
        '~' => 6, //    Negation
3057
        '%' => 5, //    Percentage
3058
        '^' => 4, //    Exponentiation
3059
        '*' => 3, '/' => 3, //    Multiplication and Division
3060
        '+' => 2, '-' => 2, //    Addition and Subtraction
3061
        '&' => 1, //    Concatenation
3062
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3063
    ];
3064
3065
    // Convert infix to postfix notation
3066
3067
    /**
3068
     * @param string $formula
3069
     * @param null|\PhpOffice\PhpSpreadsheet\Cell\Cell $pCell
3070
     *
3071
     * @return bool
3072
     */
3073 117
    private function _parseFormula($formula, Cell $pCell = null)
3074
    {
3075 117
        if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
3076
            return false;
3077
        }
3078
3079
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
3080
        //        so we store the parent worksheet so that we can re-attach it when necessary
3081 117
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3082
3083 117
        $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION .
3084 117
                                '|' . self::CALCULATION_REGEXP_CELLREF .
3085 117
                                '|' . self::CALCULATION_REGEXP_NUMBER .
3086 117
                                '|' . self::CALCULATION_REGEXP_STRING .
3087 117
                                '|' . self::CALCULATION_REGEXP_OPENBRACE .
3088 117
                                '|' . self::CALCULATION_REGEXP_NAMEDRANGE .
3089 117
                                '|' . self::CALCULATION_REGEXP_ERROR .
3090 117
                                ')/si';
3091
3092
        //    Start with initialisation
3093 117
        $index = 0;
3094 117
        $stack = new Stack();
3095 117
        $output = [];
3096 117
        $expectingOperator = false; //    We use this test in syntax-checking the expression to determine when a
3097
                                                    //        - is a negation or + is a positive operator rather than an operation
3098 117
        $expectingOperand = false; //    We use this test in syntax-checking the expression to determine whether an operand
3099
                                                    //        should be null in a function call
3100
        //    The guts of the lexical parser
3101
        //    Loop through the formula extracting each operator and operand in turn
3102 117
        while (true) {
3103 117
            $opCharacter = $formula[$index]; //    Get the first character of the value at the current index position
3104 117
            if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
3105 34
                $opCharacter .= $formula[++$index];
3106
            }
3107
3108
            //    Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
3109 117
            $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match);
3110
3111 117
            if ($opCharacter == '-' && !$expectingOperator) {                //    Is it a negation instead of a minus?
3112 2
                $stack->push('Unary Operator', '~'); //    Put a negation on the stack
3113 2
                ++$index; //        and drop the negation symbol
3114 117
            } elseif ($opCharacter == '%' && $expectingOperator) {
3115
                $stack->push('Unary Operator', '%'); //    Put a percentage on the stack
3116
                ++$index;
3117 117
            } elseif ($opCharacter == '+' && !$expectingOperator) {            //    Positive (unary plus rather than binary operator plus) can be discarded?
3118
                ++$index; //    Drop the redundant plus symbol
3119 117
            } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) {    //    We have to explicitly deny a tilde or pipe, because they are legal
3120
                return $this->raiseFormulaError("Formula Error: Illegal character '~'"); //        on the stack but not in the input expression
3121 117
            } elseif ((isset(self::$operators[$opCharacter]) or $isOperandOrFunction) && $expectingOperator) {    //    Are we putting an operator on the stack?
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
3122 110 View Code Duplication
                while ($stack->count() > 0 &&
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...
3123 110
                    ($o2 = $stack->last()) &&
3124 110
                    isset(self::$operators[$o2['value']]) &&
3125 110
                    @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3126 1
                    $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3127
                }
3128 110
                $stack->push('Binary Operator', $opCharacter); //    Finally put our current operator onto the stack
3129 110
                ++$index;
3130 110
                $expectingOperator = false;
3131 117
            } elseif ($opCharacter == ')' && $expectingOperator) {            //    Are we expecting to close a parenthesis?
3132 45
                $expectingOperand = false;
3133 45 View Code Duplication
                while (($o2 = $stack->pop()) && $o2['value'] != '(') {        //    Pop off the stack back to the last (
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...
3134 39
                    if ($o2 === null) {
3135
                        return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
3136
                    }
3137 39
                    $output[] = $o2;
3138
                }
3139 45
                $d = $stack->last(2);
3140 45
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {    //    Did this parenthesis just close a function?
3141 45
                    $functionName = $matches[1]; //    Get the function name
3142 45
                    $d = $stack->pop();
3143 45
                    $argumentCount = $d['value']; //    See how many arguments there were (argument count is the next value stored on the stack)
3144 45
                    $output[] = $d; //    Dump the argument count on the output
3145 45
                    $output[] = $stack->pop(); //    Pop the function and push onto the output
3146 45
                    if (isset(self::$controlFunctions[$functionName])) {
3147
                        $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
3148
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
0 ignored issues
show
Unused Code introduced by
$functionCall 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...
3149 45
                    } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3150 45
                        $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
3151 45
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
0 ignored issues
show
Unused Code introduced by
$functionCall 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...
3152
                    } else {    // did we somehow push a non-function on the stack? this should never happen
3153
                        return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
3154
                    }
3155
                    //    Check the argument count
3156 45
                    $argumentCountError = false;
3157 45
                    if (is_numeric($expectedArgumentCount)) {
3158 25
                        if ($expectedArgumentCount < 0) {
3159
                            if ($argumentCount > abs($expectedArgumentCount)) {
3160
                                $argumentCountError = true;
3161
                                $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
3162
                            }
3163
                        } else {
3164 25
                            if ($argumentCount != $expectedArgumentCount) {
3165
                                $argumentCountError = true;
3166 25
                                $expectedArgumentCountString = $expectedArgumentCount;
3167
                            }
3168
                        }
3169 39
                    } elseif ($expectedArgumentCount != '*') {
3170 39
                        $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
0 ignored issues
show
Unused Code introduced by
$isOperandOrFunction 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...
3171 39
                        switch ($argMatch[2]) {
3172 39
                            case '+':
3173 38
                                if ($argumentCount < $argMatch[1]) {
3174
                                    $argumentCountError = true;
3175
                                    $expectedArgumentCountString = $argMatch[1] . ' or more ';
3176
                                }
3177
3178 38
                                break;
3179 15 View Code Duplication
                            case '-':
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...
3180 13
                                if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
3181
                                    $argumentCountError = true;
3182
                                    $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
3183
                                }
3184
3185 13
                                break;
3186 2 View Code Duplication
                            case ',':
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...
3187 2
                                if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
3188
                                    $argumentCountError = true;
3189
                                    $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
3190
                                }
3191
3192 2
                                break;
3193
                        }
3194
                    }
3195 45
                    if ($argumentCountError) {
3196
                        return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected');
0 ignored issues
show
Bug introduced by
The variable $expectedArgumentCountString 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...
3197
                    }
3198
                }
3199 45
                ++$index;
3200 117
            } elseif ($opCharacter == ',') {            //    Is this the separator for function arguments?
3201 29 View Code Duplication
                while (($o2 = $stack->pop()) && $o2['value'] != '(') {        //    Pop off the stack back to the last (
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...
3202 24
                    if ($o2 === null) {
3203
                        return $this->raiseFormulaError('Formula Error: Unexpected ,');
3204
                    }
3205 24
                    $output[] = $o2; // pop the argument expression stuff and push onto the output
3206
                }
3207
                //    If we've a comma when we're expecting an operand, then what we actually have is a null operand;
3208
                //        so push a null onto the stack
3209 29
                if (($expectingOperand) || (!$expectingOperator)) {
3210
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3211
                }
3212
                // make sure there was a function
3213 29
                $d = $stack->last(2);
3214 29
                if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {
3215
                    return $this->raiseFormulaError('Formula Error: Unexpected ,');
3216
                }
3217 29
                $d = $stack->pop();
3218 29
                $stack->push($d['type'], ++$d['value'], $d['reference']); // increment the argument count
3219 29
                $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
3220 29
                $expectingOperator = false;
3221 29
                $expectingOperand = true;
3222 29
                ++$index;
3223 117
            } elseif ($opCharacter == '(' && !$expectingOperator) {
3224 3
                $stack->push('Brace', '(');
3225 3
                ++$index;
3226 117
            } elseif ($isOperandOrFunction && !$expectingOperator) {    // do we now have a function/variable/number?
3227 117
                $expectingOperator = true;
3228 117
                $expectingOperand = false;
3229 117
                $val = $match[1];
3230 117
                $length = strlen($val);
3231
3232 117
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) {
3233 45
                    $val = preg_replace('/\s/u', '', $val);
3234 45
                    if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) {    // it's a function
3235 45
                        $stack->push('Function', strtoupper($val));
3236 45
                        $ax = preg_match('/^\s*(\s*\))/ui', substr($formula, $index + $length), $amatch);
3237 45
                        if ($ax) {
3238 9
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 0);
3239 9
                            $expectingOperator = true;
3240
                        } else {
3241 45
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 1);
3242 45
                            $expectingOperator = false;
3243
                        }
3244 45
                        $stack->push('Brace', '(');
3245
                    } else {    // it's a var w/ implicit multiplication
3246 45
                        $output[] = ['type' => 'Value', 'value' => $matches[1], 'reference' => null];
3247
                    }
3248 117
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) {
3249
                    //    Watch for this case-change when modifying to allow cell references in different worksheets...
3250
                    //    Should only be applied to the actual cell column, not the worksheet name
3251
3252
                    //    If the last entry on the stack was a : operator, then we have a cell range reference
3253 59
                    $testPrevOp = $stack->last(1);
3254 59
                    if ($testPrevOp['value'] == ':') {
3255
                        //    If we have a worksheet reference, then we're playing with a 3D reference
3256 51
                        if ($matches[2] == '') {
3257
                            //    Otherwise, we 'inherit' the worksheet reference from the start cell reference
3258
                            //    The start of the cell range reference should be the last entry in $output
3259 51
                            $startCellRef = $output[count($output) - 1]['value'];
3260 51
                            preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $startCellRef, $startMatches);
3261 51
                            if ($startMatches[2] > '') {
3262 51
                                $val = $startMatches[2] . '!' . $val;
3263
                            }
3264
                        } else {
3265
                            return $this->raiseFormulaError('3D Range references are not yet supported');
3266
                        }
3267
                    }
3268
3269 59
                    $output[] = ['type' => 'Cell Reference', 'value' => $val, 'reference' => $val];
3270
                } else {    // it's a variable, constant, string, number or boolean
3271
                    //    If the last entry on the stack was a : operator, then we may have a row or column range reference
3272 94
                    $testPrevOp = $stack->last(1);
3273 94
                    if ($testPrevOp['value'] == ':') {
3274
                        $startRowColRef = $output[count($output) - 1]['value'];
3275
                        $rangeWS1 = '';
3276
                        if (strpos('!', $startRowColRef) !== false) {
3277
                            list($rangeWS1, $startRowColRef) = explode('!', $startRowColRef);
3278
                        }
3279
                        if ($rangeWS1 != '') {
3280
                            $rangeWS1 .= '!';
3281
                        }
3282
                        $rangeWS2 = $rangeWS1;
3283
                        if (strpos('!', $val) !== false) {
3284
                            list($rangeWS2, $val) = explode('!', $val);
3285
                        }
3286
                        if ($rangeWS2 != '') {
3287
                            $rangeWS2 .= '!';
3288
                        }
3289
                        if ((is_int($startRowColRef)) && (ctype_digit($val)) &&
3290
                            ($startRowColRef <= 1048576) && ($val <= 1048576)) {
3291
                            //    Row range
3292
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestColumn() : 'XFD'; //    Max 16,384 columns for Excel2007
3293
                            $output[count($output) - 1]['value'] = $rangeWS1 . 'A' . $startRowColRef;
3294
                            $val = $rangeWS2 . $endRowColRef . $val;
3295
                        } elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) &&
3296
                            (strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) {
3297
                            //    Column range
3298
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestRow() : 1048576; //    Max 1,048,576 rows for Excel2007
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3299
                            $output[count($output) - 1]['value'] = $rangeWS1 . strtoupper($startRowColRef) . '1';
3300
                            $val = $rangeWS2 . $val . $endRowColRef;
3301
                        }
3302
                    }
3303
3304 94
                    $localeConstant = false;
3305 94
                    if ($opCharacter == '"') {
3306
                        //    UnEscape any quotes within the string
3307 59
                        $val = self::wrapResult(str_replace('""', '"', self::unwrapResult($val)));
3308 61
                    } elseif (is_numeric($val)) {
3309 59
                        if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
3310 21
                            $val = (float) $val;
3311
                        } else {
3312 59
                            $val = (int) $val;
3313
                        }
3314 5
                    } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
3315 1
                        $excelConstant = trim(strtoupper($val));
3316 1
                        $val = self::$excelConstants[$excelConstant];
3317 4
                    } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
3318
                        $val = self::$excelConstants[$localeConstant];
3319
                    }
3320 94
                    $details = ['type' => 'Value', 'value' => $val, 'reference' => null];
3321 94
                    if ($localeConstant) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $localeConstant of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3322
                        $details['localeValue'] = $localeConstant;
3323
                    }
3324 94
                    $output[] = $details;
3325
                }
3326 117
                $index += $length;
3327
            } elseif ($opCharacter == '$') {    // absolute row or column range
3328
                ++$index;
3329
            } elseif ($opCharacter == ')') {    // miscellaneous error checking
3330
                if ($expectingOperand) {
3331
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3332
                    $expectingOperand = false;
3333
                    $expectingOperator = true;
3334
                } else {
3335
                    return $this->raiseFormulaError("Formula Error: Unexpected ')'");
3336
                }
3337
            } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
3338
                return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
3339
            } else {    // I don't even want to know what you did to get here
3340
                return $this->raiseFormulaError('Formula Error: An unexpected error occured');
3341
            }
3342
            //    Test for end of formula string
3343 117
            if ($index == strlen($formula)) {
3344
                //    Did we end with an operator?.
3345
                //    Only valid for the % unary operator
3346 117
                if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
3347
                    return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
3348
                }
3349
3350 117
                break;
3351
            }
3352
            //    Ignore white space
3353 116
            while (($formula[$index] == "\n") || ($formula[$index] == "\r")) {
3354
                ++$index;
3355
            }
3356 116
            if ($formula[$index] == ' ') {
3357 54
                while ($formula[$index] == ' ') {
3358 54
                    ++$index;
3359
                }
3360
                //    If we're expecting an operator, but only have a space between the previous and next operands (and both are
3361
                //        Cell References) then we have an INTERSECTION operator
3362 54
                if (($expectingOperator) && (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
3363 54
                    ($output[count($output) - 1]['type'] == 'Cell Reference')) {
3364 View Code Duplication
                    while ($stack->count() > 0 &&
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...
3365
                        ($o2 = $stack->last()) &&
3366
                        isset(self::$operators[$o2['value']]) &&
3367
                        @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3368
                        $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3369
                    }
3370
                    $stack->push('Binary Operator', '|'); //    Put an Intersect Operator on the stack
3371
                    $expectingOperator = false;
3372
                }
3373
            }
3374
        }
3375
3376 117 View Code Duplication
        while (($op = $stack->pop()) !== null) {    // pop everything off the stack and push onto output
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...
3377 98
            if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
3378
                return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
3379
            }
3380 98
            $output[] = $op;
3381
        }
3382
3383 117
        return $output;
3384
    }
3385
3386 110
    private static function dataTestReference(&$operandData)
3387
    {
3388 110
        $operand = $operandData['value'];
3389 110
        if (($operandData['reference'] === null) && (is_array($operand))) {
3390 1
            $rKeys = array_keys($operand);
3391 1
            $rowKey = array_shift($rKeys);
3392 1
            $cKeys = array_keys(array_keys($operand[$rowKey]));
3393 1
            $colKey = array_shift($cKeys);
3394 1
            if (ctype_upper($colKey)) {
3395
                $operandData['reference'] = $colKey . $rowKey;
3396
            }
3397
        }
3398
3399 110
        return $operand;
3400
    }
3401
3402
    // evaluate postfix notation
3403
3404
    /**
3405
     * @param mixed $tokens
3406
     * @param null|string $cellID
3407
     * @param null|Cell $pCell
3408
     *
3409
     * @return bool
3410
     */
3411 117
    private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
3412
    {
3413 117
        if ($tokens == false) {
3414
            return false;
3415
        }
3416
3417
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
3418
        //        so we store the parent cell collection so that we can re-attach it when necessary
3419 117
        $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
3420 117
        $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
3421 117
        $stack = new Stack();
3422
3423
        //    Loop through each token in turn
3424 117
        foreach ($tokens as $tokenData) {
3425 117
            $token = $tokenData['value'];
3426
            // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
3427 117
            if (isset(self::$binaryOperators[$token])) {
3428
                //    We must have two operands, error if we don't
3429 110
                if (($operand2Data = $stack->pop()) === null) {
3430
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3431
                }
3432 110
                if (($operand1Data = $stack->pop()) === null) {
3433
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3434
                }
3435
3436 110
                $operand1 = self::dataTestReference($operand1Data);
3437 110
                $operand2 = self::dataTestReference($operand2Data);
3438
3439
                //    Log what we're doing
3440 110
                if ($token == ':') {
3441 51
                    $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
3442
                } else {
3443 94
                    $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
3444
                }
3445
3446
                //    Process the operation in the appropriate manner
3447
                switch ($token) {
3448
                    //    Comparison (Boolean) Operators
3449 110
                    case '>':            //    Greater than
3450 96
                    case '<':            //    Less than
3451 89
                    case '>=':            //    Greater than or Equal to
3452 81
                    case '<=':            //    Less than or Equal to
3453 74
                    case '=':            //    Equality
3454 65
                    case '<>':            //    Inequality
3455 74
                        $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
3456
3457 74
                        break;
3458
                    //    Binary Operators
3459 58
                    case ':':            //    Range
3460 51
                        $sheet1 = $sheet2 = '';
3461 51
                        if (strpos($operand1Data['reference'], '!') !== false) {
3462 51
                            list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
3463
                        } else {
3464
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
3465
                        }
3466 51
                        if (strpos($operand2Data['reference'], '!') !== false) {
3467 51
                            list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
3468
                        } else {
3469 9
                            $sheet2 = $sheet1;
3470
                        }
3471 51
                        if ($sheet1 == $sheet2) {
3472 51 View Code Duplication
                            if ($operand1Data['reference'] === null) {
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...
3473
                                if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
3474
                                    $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
0 ignored issues
show
Bug introduced by
It seems like $pCell is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
3475
                                } elseif (trim($operand1Data['reference']) == '') {
3476
                                    $operand1Data['reference'] = $pCell->getCoordinate();
3477
                                } else {
3478
                                    $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
3479
                                }
3480
                            }
3481 51 View Code Duplication
                            if ($operand2Data['reference'] === null) {
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...
3482
                                if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
3483
                                    $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
3484
                                } elseif (trim($operand2Data['reference']) == '') {
3485
                                    $operand2Data['reference'] = $pCell->getCoordinate();
3486
                                } else {
3487
                                    $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
3488
                                }
3489
                            }
3490
3491 51
                            $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
3492 51
                            $oCol = $oRow = [];
3493 51
                            foreach ($oData as $oDatum) {
3494 51
                                $oCR = Cell::coordinateFromString($oDatum);
3495 51
                                $oCol[] = Cell::columnIndexFromString($oCR[0]) - 1;
3496 51
                                $oRow[] = $oCR[1];
3497
                            }
3498 51
                            $cellRef = Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
3499 51 View Code Duplication
                            if ($pCellParent !== null) {
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...
3500 51
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
3501
                            } else {
3502
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3503
                            }
3504 51
                            $stack->push('Cell Reference', $cellValue, $cellRef);
3505
                        } else {
3506
                            $stack->push('Error', Functions::REF(), null);
3507
                        }
3508
3509 51
                        break;
3510 32
                    case '+':            //    Addition
3511 23
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);
3512
3513 23
                        break;
3514 28
                    case '-':            //    Subtraction
3515 7
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);
3516
3517 7
                        break;
3518 26
                    case '*':            //    Multiplication
3519 19
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);
3520
3521 19
                        break;
3522 11
                    case '/':            //    Division
3523 8
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);
3524
3525 8
                        break;
3526 6
                    case '^':            //    Exponential
3527
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);
3528
3529
                        break;
3530 6
                    case '&':            //    Concatenation
3531
                        //    If either of the operands is a matrix, we need to treat them both as matrices
3532
                        //        (converting the other operand to a matrix if need be); then perform the required
3533
                        //        matrix operation
3534 6
                        if (is_bool($operand1)) {
3535
                            $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3536
                        }
3537 6
                        if (is_bool($operand2)) {
3538
                            $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3539
                        }
3540 6
                        if ((is_array($operand1)) || (is_array($operand2))) {
3541
                            //    Ensure that both operands are arrays/matrices
3542 5
                            self::checkMatrixOperands($operand1, $operand2, 2);
3543
3544
                            try {
3545
                                //    Convert operand 1 from a PHP array to a matrix
3546 5
                                $matrix = new Shared\JAMA\Matrix($operand1);
3547
                                //    Perform the required operation against the operand 1 matrix, passing in operand 2
3548 5
                                $matrixResult = $matrix->concat($operand2);
3549 5
                                $result = $matrixResult->getArray();
3550
                            } catch (\Exception $ex) {
3551
                                $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
3552 5
                                $result = '#VALUE!';
3553
                            }
3554
                        } else {
3555 1
                            $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"';
3556
                        }
3557 6
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
3558 6
                        $stack->push('Value', $result);
3559
3560 6
                        break;
3561
                    case '|':            //    Intersect
3562
                        $rowIntersect = array_intersect_key($operand1, $operand2);
3563
                        $cellIntersect = $oCol = $oRow = [];
3564
                        foreach (array_keys($rowIntersect) as $row) {
3565
                            $oRow[] = $row;
3566
                            foreach ($rowIntersect[$row] as $col => $data) {
3567
                                $oCol[] = Cell::columnIndexFromString($col) - 1;
3568
                                $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
3569
                            }
3570
                        }
3571
                        $cellRef = Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
3572
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
3573
                        $stack->push('Value', $cellIntersect, $cellRef);
3574
3575 110
                        break;
3576
                }
3577
3578
                // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
3579 117
            } elseif (($token === '~') || ($token === '%')) {
3580 2
                if (($arg = $stack->pop()) === null) {
3581
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3582
                }
3583 2
                $arg = $arg['value'];
3584 2
                if ($token === '~') {
3585 2
                    $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
3586 2
                    $multiplier = -1;
3587
                } else {
3588
                    $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
3589
                    $multiplier = 0.01;
3590
                }
3591 2
                if (is_array($arg)) {
3592
                    self::checkMatrixOperands($arg, $multiplier, 2);
3593
3594
                    try {
3595
                        $matrix1 = new Shared\JAMA\Matrix($arg);
3596
                        $matrixResult = $matrix1->arrayTimesEquals($multiplier);
3597
                        $result = $matrixResult->getArray();
3598
                    } catch (\Exception $ex) {
3599
                        $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
3600
                        $result = '#VALUE!';
3601
                    }
3602
                    $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
3603
                    $stack->push('Value', $result);
3604
                } else {
3605 2
                    $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
3606
                }
3607 117
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
3608 59
                $cellRef = null;
3609 59
                if (isset($matches[8])) {
3610
                    if ($pCell === null) {
3611
                        //                        We can't access the range, so return a REF error
3612
                        $cellValue = Functions::REF();
3613
                    } else {
3614
                        $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
3615
                        if ($matches[2] > '') {
3616
                            $matches[2] = trim($matches[2], "\"'");
3617 View Code Duplication
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
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...
3618
                                //    It's a Reference to an external spreadsheet (not currently supported)
3619
                                return $this->raiseFormulaError('Unable to access External Workbook');
3620
                            }
3621
                            $matches[2] = trim($matches[2], "\"'");
3622
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
3623 View Code Duplication
                            if ($pCellParent !== null) {
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...
3624
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3625
                            } else {
3626
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3627
                            }
3628
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
3629
                        } else {
3630
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
3631
                            if ($pCellParent !== null) {
3632
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3633
                            } else {
3634
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3635
                            }
3636
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
3637
                        }
3638
                    }
3639
                } else {
3640 59
                    if ($pCell === null) {
3641
                        //                        We can't access the cell, so return a REF error
3642
                        $cellValue = Functions::REF();
3643
                    } else {
3644 59
                        $cellRef = $matches[6] . $matches[7];
3645 59
                        if ($matches[2] > '') {
3646 14
                            $matches[2] = trim($matches[2], "\"'");
3647 14 View Code Duplication
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
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...
3648
                                //    It's a Reference to an external spreadsheet (not currently supported)
3649
                                return $this->raiseFormulaError('Unable to access External Workbook');
3650
                            }
3651 14
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
3652 14
                            if ($pCellParent !== null) {
3653 14
                                $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
3654 14
                                if ($cellSheet && $cellSheet->cellExists($cellRef)) {
3655 14
                                    $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3656 14
                                    $pCell->attach($pCellParent);
3657
                                } else {
3658 14
                                    $cellValue = null;
3659
                                }
3660
                            } else {
3661
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3662
                            }
3663 14
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
3664
                        } else {
3665 47
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
3666 47
                            if ($pCellParent->has($cellRef)) {
3667 47
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3668 47
                                $pCell->attach($pCellParent);
0 ignored issues
show
Bug introduced by
It seems like $pCellParent defined by $pCell !== null ? $pCell->getParent() : null on line 3420 can be null; however, PhpOffice\PhpSpreadsheet\Cell\Cell::attach() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
3669
                            } else {
3670 2
                                $cellValue = null;
3671
                            }
3672 47
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
3673
                        }
3674
                    }
3675
                }
3676 59
                $stack->push('Value', $cellValue, $cellRef);
3677
3678
                // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
3679 103
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
3680 45
                $functionName = $matches[1];
3681 45
                $argCount = $stack->pop();
3682 45
                $argCount = $argCount['value'];
3683 45
                if ($functionName != 'MKMATRIX') {
3684 45
                    $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
3685
                }
3686 45
                if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {    // function
3687 45
                    if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3688 45
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
3689 45
                        $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
3690 45
                        $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
3691
                    } elseif (isset(self::$controlFunctions[$functionName])) {
3692
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
3693
                        $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
3694
                        $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
3695
                    }
3696
                    // get the arguments for this function
3697 45
                    $args = $argArrayVals = [];
3698 45
                    for ($i = 0; $i < $argCount; ++$i) {
3699 45
                        $arg = $stack->pop();
3700 45
                        $a = $argCount - $i - 1;
3701 45
                        if (($passByReference) &&
0 ignored issues
show
Bug introduced by
The variable $passByReference 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...
3702 45
                            (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
3703 45
                            (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) {
3704
                            if ($arg['reference'] === null) {
3705
                                $args[] = $cellID;
3706
                                if ($functionName != 'MKMATRIX') {
3707
                                    $argArrayVals[] = $this->showValue($cellID);
3708
                                }
3709
                            } else {
3710
                                $args[] = $arg['reference'];
3711
                                if ($functionName != 'MKMATRIX') {
3712
                                    $argArrayVals[] = $this->showValue($arg['reference']);
3713
                                }
3714
                            }
3715
                        } else {
3716 45
                            $args[] = self::unwrapResult($arg['value']);
3717 45
                            if ($functionName != 'MKMATRIX') {
3718 45
                                $argArrayVals[] = $this->showValue($arg['value']);
3719
                            }
3720
                        }
3721
                    }
3722
                    //    Reverse the order of the arguments
3723 45
                    krsort($args);
3724
3725 45
                    if (($passByReference) && ($argCount == 0)) {
3726
                        $args[] = $cellID;
3727
                        $argArrayVals[] = $this->showValue($cellID);
3728
                    }
3729
3730 45
                    if ($functionName != 'MKMATRIX') {
3731 45
                        if ($this->debugLog->getWriteDebugLog()) {
3732
                            krsort($argArrayVals);
3733
                            $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )');
3734
                        }
3735
                    }
3736
3737
                    //    Process the argument with the appropriate function call
3738 45
                    if ($passCellReference) {
0 ignored issues
show
Bug introduced by
The variable $passCellReference 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...
3739 1
                        $args[] = $pCell;
3740
                    }
3741
3742 45
                    if (!is_array($functionCall)) {
0 ignored issues
show
Bug introduced by
The variable $functionCall 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...
3743 1
                        foreach ($args as &$arg) {
3744
                            $arg = Functions::flattenSingleValue($arg);
3745
                        }
3746 1
                        unset($arg);
3747
                    }
3748 45
                    $result = call_user_func_array($functionCall, $args);
3749
3750 45
                    if ($functionName != 'MKMATRIX') {
3751 45
                        $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
3752
                    }
3753 45
                    $stack->push('Value', self::wrapResult($result));
3754
                }
3755
            } else {
3756
                // if the token is a number, boolean, string or an Excel error, push it onto the stack
3757 103
                if (isset(self::$excelConstants[strtoupper($token)])) {
3758
                    $excelConstant = strtoupper($token);
3759
                    $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
3760
                    $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
3761 103
                } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) {
3762 103
                    $stack->push('Value', $token);
3763
                    // if the token is a named range, push the named range name onto the stack
3764 4
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
3765 4
                    $namedRange = $matches[6];
3766 4
                    $this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);
3767
3768 4
                    if (substr($namedRange, 0, 6) === '_xlfn.') {
3769
                        return $this->raiseFormulaError("undefined named range / function '$token'");
3770
                    }
3771
3772 4
                    $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false);
3773 4
                    $pCell->attach($pCellParent);
0 ignored issues
show
Bug introduced by
It seems like $pCellParent defined by $pCell !== null ? $pCell->getParent() : null on line 3420 can be null; however, PhpOffice\PhpSpreadsheet\Cell\Cell::attach() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
3774 4
                    $this->debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue));
3775 4
                    $stack->push('Named Range', $cellValue, $namedRange);
3776
                } else {
3777 117
                    return $this->raiseFormulaError("undefined variable '$token'");
3778
                }
3779
            }
3780
        }
3781
        // when we're out of tokens, the stack should have a single element, the final result
3782 117
        if ($stack->count() != 1) {
3783
            return $this->raiseFormulaError('internal error');
3784
        }
3785 117
        $output = $stack->pop();
3786 117
        $output = $output['value'];
3787
3788 117
        return $output;
3789
    }
3790
3791 31
    private function validateBinaryOperand(&$operand, &$stack)
3792
    {
3793 31
        if (is_array($operand)) {
3794 22
            if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
3795
                do {
3796 22
                    $operand = array_pop($operand);
3797 22
                } while (is_array($operand));
3798
            }
3799
        }
3800
        //    Numbers, matrices and booleans can pass straight through, as they're already valid
3801 31
        if (is_string($operand)) {
3802
            //    We only need special validations for the operand if it is a string
3803
            //    Start by stripping off the quotation marks we use to identify true excel string values internally
3804 2
            if ($operand > '' && $operand[0] == '"') {
3805
                $operand = self::unwrapResult($operand);
3806
            }
3807
            //    If the string is a numeric value, we treat it as a numeric, so no further testing
3808 2
            if (!is_numeric($operand)) {
3809
                //    If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
3810 2
                if ($operand > '' && $operand[0] == '#') {
3811
                    $stack->push('Value', $operand);
3812
                    $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand));
3813
3814
                    return false;
3815 2
                } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) {
3816
                    //    If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
3817 2
                    $stack->push('Value', '#VALUE!');
3818 2
                    $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!'));
3819
3820 2
                    return false;
3821
                }
3822
            }
3823
        }
3824
3825
        //    return a true if the value of the operand is one that we can use in normal binary operations
3826 30
        return true;
3827
    }
3828
3829
    /**
3830
     * @param null|string $cellID
3831
     * @param mixed $operand1
3832
     * @param mixed $operand2
3833
     * @param string $operation
3834
     * @param Stack $stack
3835
     * @param bool $recursingArrays
3836
     *
3837
     * @return bool
3838
     */
3839 74
    private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false)
3840
    {
3841
        //    If we're dealing with matrix operations, we want a matrix result
3842 74
        if ((is_array($operand1)) || (is_array($operand2))) {
3843 12
            $result = [];
3844 12
            if ((is_array($operand1)) && (!is_array($operand2))) {
3845 12 View Code Duplication
                foreach ($operand1 as $x => $operandData) {
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...
3846 12
                    $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2));
3847 12
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack);
3848 12
                    $r = $stack->pop();
3849 12
                    $result[$x] = $r['value'];
3850
                }
3851
            } elseif ((!is_array($operand1)) && (is_array($operand2))) {
3852 View Code Duplication
                foreach ($operand2 as $x => $operandData) {
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...
3853
                    $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData));
3854
                    $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack);
3855
                    $r = $stack->pop();
3856
                    $result[$x] = $r['value'];
3857
                }
3858
            } else {
3859
                if (!$recursingArrays) {
3860
                    self::checkMatrixOperands($operand1, $operand2, 2);
3861
                }
3862
                foreach ($operand1 as $x => $operandData) {
3863
                    $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x]));
3864
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true);
3865
                    $r = $stack->pop();
3866
                    $result[$x] = $r['value'];
3867
                }
3868
            }
3869
            //    Log the result details
3870 12
            $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result));
3871
            //    And push the result onto the stack
3872 12
            $stack->push('Array', $result);
3873
3874 12
            return true;
3875
        }
3876
3877
        //    Simple validate the two operands if they are string values
3878 74
        if (is_string($operand1) && $operand1 > '' && $operand1[0] == '"') {
3879 43
            $operand1 = self::unwrapResult($operand1);
3880
        }
3881 74
        if (is_string($operand2) && $operand2 > '' && $operand2[0] == '"') {
3882 55
            $operand2 = self::unwrapResult($operand2);
3883
        }
3884
3885
        // Use case insensitive comparaison if not OpenOffice mode
3886 74
        if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
3887 74
            if (is_string($operand1)) {
3888 43
                $operand1 = strtoupper($operand1);
3889
            }
3890 74
            if (is_string($operand2)) {
3891 55
                $operand2 = strtoupper($operand2);
3892
            }
3893
        }
3894
3895 74
        $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE;
3896
3897
        //    execute the necessary operation
3898
        switch ($operation) {
3899
            //    Greater than
3900 74 View Code Duplication
            case '>':
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...
3901 18
                if ($useLowercaseFirstComparison) {
3902 9
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0;
3903
                } else {
3904 18
                    $result = ($operand1 > $operand2);
3905
                }
3906
3907 18
                break;
3908
            //    Less than
3909 60 View Code Duplication
            case '<':
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...
3910 8
                if ($useLowercaseFirstComparison) {
3911 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0;
3912
                } else {
3913 8
                    $result = ($operand1 < $operand2);
3914
                }
3915
3916 8
                break;
3917
            //    Equality
3918 53 View Code Duplication
            case '=':
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...
3919 19
                if (is_numeric($operand1) && is_numeric($operand2)) {
3920 3
                    $result = (abs($operand1 - $operand2) < $this->delta);
3921
                } else {
3922 16
                    $result = strcmp($operand1, $operand2) == 0;
3923
                }
3924
3925 19
                break;
3926
            //    Greater than or equal
3927 34
            case '>=':
3928 8
                if (is_numeric($operand1) && is_numeric($operand2)) {
3929 4
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2));
3930 4
                } elseif ($useLowercaseFirstComparison) {
3931 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0;
3932
                } else {
3933 4
                    $result = strcmp($operand1, $operand2) >= 0;
3934
                }
3935
3936 8
                break;
3937
            //    Less than or equal
3938 26
            case '<=':
3939 7
                if (is_numeric($operand1) && is_numeric($operand2)) {
3940 3
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2));
3941 4
                } elseif ($useLowercaseFirstComparison) {
3942 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0;
3943
                } else {
3944 4
                    $result = strcmp($operand1, $operand2) <= 0;
3945
                }
3946
3947 7
                break;
3948
            //    Inequality
3949 19 View Code Duplication
            case '<>':
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...
3950 19
                if (is_numeric($operand1) && is_numeric($operand2)) {
3951 3
                    $result = (abs($operand1 - $operand2) > 1E-14);
3952
                } else {
3953 16
                    $result = strcmp($operand1, $operand2) != 0;
3954
                }
3955
3956 19
                break;
3957
        }
3958
3959
        //    Log the result details
3960 74
        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The variable $result 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...
3961
        //    And push the result onto the stack
3962 74
        $stack->push('Value', $result);
3963
3964 74
        return true;
3965
    }
3966
3967
    /**
3968
     * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
3969
     *
3970
     * @param string $str1 First string value for the comparison
3971
     * @param string $str2 Second string value for the comparison
3972
     *
3973
     * @return int
3974
     */
3975 21
    private function strcmpLowercaseFirst($str1, $str2)
3976
    {
3977 21
        $inversedStr1 = Shared\StringHelper::strCaseReverse($str1);
3978 21
        $inversedStr2 = Shared\StringHelper::strCaseReverse($str2);
3979
3980 21
        return strcmp($inversedStr1, $inversedStr2);
3981
    }
3982
3983
    /**
3984
     * @param mixed $operand1
3985
     * @param mixed $operand2
3986
     * @param mixed $operation
3987
     * @param string $matrixFunction
3988
     * @param mixed $stack
3989
     *
3990
     * @return bool
3991
     */
3992 31
    private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
3993
    {
3994
        //    Validate the two operands
3995 31
        if (!$this->validateBinaryOperand($operand1, $stack)) {
3996 2
            return false;
3997
        }
3998 30
        if (!$this->validateBinaryOperand($operand2, $stack)) {
3999 1
            return false;
4000
        }
4001
4002
        //    If either of the operands is a matrix, we need to treat them both as matrices
4003
        //        (converting the other operand to a matrix if need be); then perform the required
4004
        //        matrix operation
4005 30
        if ((is_array($operand1)) || (is_array($operand2))) {
4006
            //    Ensure that both operands are arrays/matrices of the same size
4007
            self::checkMatrixOperands($operand1, $operand2, 2);
4008
4009
            try {
4010
                //    Convert operand 1 from a PHP array to a matrix
4011
                $matrix = new Shared\JAMA\Matrix($operand1);
4012
                //    Perform the required operation against the operand 1 matrix, passing in operand 2
4013
                $matrixResult = $matrix->$matrixFunction($operand2);
4014
                $result = $matrixResult->getArray();
4015
            } catch (\Exception $ex) {
4016
                $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
4017
                $result = '#VALUE!';
4018
            }
4019
        } else {
4020 30
            if ((Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) &&
4021 30
                ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
4022 30
                 (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))) {
4023
                $result = Functions::VALUE();
4024
            } else {
4025
                //    If we're dealing with non-matrix operations, execute the necessary operation
4026
                switch ($operation) {
4027
                    //    Addition
4028 30
                    case '+':
4029 22
                        $result = $operand1 + $operand2;
4030
4031 22
                        break;
4032
                    //    Subtraction
4033 26
                    case '-':
4034 7
                        $result = $operand1 - $operand2;
4035
4036 7
                        break;
4037
                    //    Multiplication
4038 24
                    case '*':
4039 19
                        $result = $operand1 * $operand2;
4040
4041 19
                        break;
4042
                    //    Division
4043 8
                    case '/':
4044 8
                        if ($operand2 == 0) {
4045
                            //    Trap for Divide by Zero error
4046 8
                            $stack->push('Value', '#DIV/0!');
4047 8
                            $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!'));
4048
4049 8
                            return false;
4050
                        }
4051
                            $result = $operand1 / $operand2;
4052
4053
                        break;
4054
                    //    Power
4055
                    case '^':
4056
                        $result = pow($operand1, $operand2);
4057
4058
                        break;
4059
                }
4060
            }
4061
        }
4062
4063
        //    Log the result details
4064 25
        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The variable $result 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...
4065
        //    And push the result onto the stack
4066 25
        $stack->push('Value', $result);
4067
4068 25
        return true;
4069
    }
4070
4071
    // trigger an error, but nicely, if need be
4072
    protected function raiseFormulaError($errorMessage)
4073
    {
4074
        $this->formulaError = $errorMessage;
4075
        $this->cyclicReferenceStack->clear();
0 ignored issues
show
Bug introduced by
The method clear cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
4076
        if (!$this->suppressFormulaErrors) {
4077
            throw new Exception($errorMessage);
4078
        }
4079
        trigger_error($errorMessage, E_USER_ERROR);
4080
    }
4081
4082
    /**
4083
     * Extract range values.
4084
     *
4085
     * @param string &$pRange String based range representation
4086
     * @param Worksheet $pSheet Worksheet
4087
     * @param bool $resetLog Flag indicating whether calculation log should be reset or not
4088
     *
4089
     * @throws Exception
4090
     *
4091
     * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4092
     */
4093 59
    public function extractCellRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4094
    {
4095
        // Return value
4096 59
        $returnValue = [];
4097
4098 59
        if ($pSheet !== null) {
4099 59
            $pSheetName = $pSheet->getTitle();
4100 59 View Code Duplication
            if (strpos($pRange, '!') !== false) {
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...
4101
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4102
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4103
            }
4104
4105
            // Extract range
4106 59
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
4107 59
            $pRange = $pSheetName . '!' . $pRange;
4108 59
            if (!isset($aReferences[1])) {
4109
                //    Single cell in range
4110 59
                sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
0 ignored issues
show
Bug introduced by
The variable $currentRow does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
4111 59 View Code Duplication
                if ($pSheet->cellExists($aReferences[0])) {
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...
4112 59
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4113
                } else {
4114 59
                    $returnValue[$currentRow][$currentCol] = null;
4115
                }
4116
            } else {
4117
                // Extract cell data for all cells in the range
4118 51
                foreach ($aReferences as $reference) {
4119
                    // Extract range
4120 51
                    sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
4121 51 View Code Duplication
                    if ($pSheet->cellExists($reference)) {
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...
4122 51
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4123
                    } else {
4124 51
                        $returnValue[$currentRow][$currentCol] = null;
4125
                    }
4126
                }
4127
            }
4128
        }
4129
4130 59
        return $returnValue;
4131
    }
4132
4133
    /**
4134
     * Extract range values.
4135
     *
4136
     * @param string &$pRange String based range representation
4137
     * @param Worksheet $pSheet Worksheet
4138
     * @param bool $resetLog Flag indicating whether calculation log should be reset or not
4139
     *
4140
     * @throws Exception
4141
     *
4142
     * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4143
     */
4144 4
    public function extractNamedRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4145
    {
4146
        // Return value
4147 4
        $returnValue = [];
4148
4149 4
        if ($pSheet !== null) {
4150 4
            $pSheetName = $pSheet->getTitle();
4151 4 View Code Duplication
            if (strpos($pRange, '!') !== false) {
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...
4152
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4153
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4154
            }
4155
4156
            // Named range?
4157 4
            $namedRange = NamedRange::resolveRange($pRange, $pSheet);
4158 4
            if ($namedRange !== null) {
4159 2
                $pSheet = $namedRange->getWorksheet();
4160 2
                $pRange = $namedRange->getRange();
4161 2
                $splitRange = Cell::splitRange($pRange);
4162
                //    Convert row and column references
4163 2
                if (ctype_alpha($splitRange[0][0])) {
4164
                    $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
4165 2
                } elseif (ctype_digit($splitRange[0][0])) {
4166 2
                    $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
4167
                }
4168
            } else {
4169 2
                return Functions::REF();
4170
            }
4171
4172
            // Extract range
4173 2
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
4174 2
            if (!isset($aReferences[1])) {
4175
                //    Single cell (or single column or row) in range
4176 1
                list($currentCol, $currentRow) = Cell::coordinateFromString($aReferences[0]);
4177 1 View Code Duplication
                if ($pSheet->cellExists($aReferences[0])) {
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...
4178 1
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4179
                } else {
4180 1
                    $returnValue[$currentRow][$currentCol] = null;
4181
                }
4182
            } else {
4183
                // Extract cell data for all cells in the range
4184 1
                foreach ($aReferences as $reference) {
4185
                    // Extract range
4186 1
                    list($currentCol, $currentRow) = Cell::coordinateFromString($reference);
4187 1 View Code Duplication
                    if ($pSheet->cellExists($reference)) {
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...
4188 1
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4189
                    } else {
4190 1
                        $returnValue[$currentRow][$currentCol] = null;
4191
                    }
4192
                }
4193
            }
4194
        }
4195
4196 2
        return $returnValue;
4197
    }
4198
4199
    /**
4200
     * Is a specific function implemented?
4201
     *
4202
     * @param string $pFunction Function Name
4203
     *
4204
     * @return bool
4205
     */
4206 3
    public function isImplemented($pFunction)
4207
    {
4208 3
        $pFunction = strtoupper($pFunction);
4209 3
        $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction]['functionCall']) && self::$phpSpreadsheetFunctions[$pFunction]['functionCall'][1] === 'DUMMY');
4210
4211 3
        return !$notImplemented;
4212
    }
4213
4214
    /**
4215
     * Get a list of all implemented functions as an array of function objects.
4216
     *
4217
     * @return array of Category
4218
     */
4219
    public function getFunctions()
4220
    {
4221
        return self::$phpSpreadsheetFunctions;
4222
    }
4223
4224
    /**
4225
     * Get a list of implemented Excel function names.
4226
     *
4227
     * @return array
4228
     */
4229 2
    public function getImplementedFunctionNames()
4230
    {
4231 2
        $returnValue = [];
4232 2
        foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
4233 2
            if ($this->isImplemented($functionName)) {
4234 2
                $returnValue[] = $functionName;
4235
            }
4236
        }
4237
4238 2
        return $returnValue;
4239
    }
4240
}
4241