Skip to content

Commit 990e3ec

Browse files
committed
Handle #REF! As Argument to COUNTIF, AVERAGEIF, SUMIF
Fix #4381. The report refers to COUNTIF, but AVERAGEIF and SUMIF, which are implemented in the same module, exhibit the same behavior. (There may be others, but, for now, I will just fix those 3.) Most methods which implement Excel functions should accept mixed arguments, so that they won't throw exceptions when calculated. Of course, MS often doesn't give much guidance as to how unexpected arguments should be handled. It at least seems clear that MS will often substitute #REF! for some arguments, and will return #REF! as the result in such cases. My test indicates that a formula using, say, #DIV/0! in lieu of #REF! will cause Excel to deem the spreadsheet corrupt. So, I think I am just going to deal with #REF! and let other unexpected values continue to throw exceptions.
1 parent cd7386f commit 990e3ec

File tree

4 files changed

+70
-6
lines changed

4 files changed

+70
-6
lines changed

src/PhpSpreadsheet/Calculation/Statistical/Conditional.php

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
1010
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
1111
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
12+
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
1213

1314
class Conditional
1415
{
@@ -24,13 +25,17 @@ class Conditional
2425
* Excel Function:
2526
* AVERAGEIF(range,condition[, average_range])
2627
*
27-
* @param mixed $range Data values
28+
* @param mixed $range Data values, expect array
2829
* @param null|array|string $condition the criteria that defines which cells will be checked
2930
* @param mixed $averageRange Data values
3031
*/
3132
public static function AVERAGEIF(mixed $range, null|array|string $condition, mixed $averageRange = []): null|int|float|string
3233
{
3334
if (!is_array($range) || !is_array($averageRange) || array_key_exists(0, $range) || array_key_exists(0, $averageRange)) {
35+
if ($range === ExcelError::REF()) {
36+
return $range;
37+
}
38+
3439
throw new CalcException('Must specify range of cells, not any kind of literal');
3540
}
3641
$database = self::databaseFromRangeAndValue($range, $averageRange);
@@ -76,11 +81,21 @@ public static function AVERAGEIFS(mixed ...$args): null|int|float|string
7681
* Excel Function:
7782
* COUNTIF(range,condition)
7883
*
79-
* @param mixed[] $range Data values
84+
* @param mixed $range Data values, expect array
8085
* @param null|array|string $condition the criteria that defines which cells will be counted
8186
*/
82-
public static function COUNTIF(array $range, null|array|string $condition): string|int
87+
public static function COUNTIF(mixed $range, null|array|string $condition): string|int
8388
{
89+
if (
90+
!is_array($range)
91+
|| array_key_exists(0, $range)
92+
) {
93+
if ($range === ExcelError::REF()) {
94+
return $range;
95+
}
96+
97+
throw new CalcException('Must specify range of cells, not any kind of literal');
98+
}
8499
// Filter out any empty values that shouldn't be included in a COUNT
85100
$range = array_filter(
86101
Functions::flattenArray($range),
@@ -169,10 +184,20 @@ public static function MINIFS(mixed ...$args): null|float|string
169184
* Excel Function:
170185
* SUMIF(range, criteria, [sum_range])
171186
*
172-
* @param array $range Data values
187+
* @param mixed $range Data values, expecting array
173188
*/
174-
public static function SUMIF(array $range, mixed $condition, array $sumRange = []): null|float|string
189+
public static function SUMIF(mixed $range, mixed $condition, array $sumRange = []): null|float|string
175190
{
191+
if (
192+
!is_array($range)
193+
|| array_key_exists(0, $range)
194+
) {
195+
if ($range === ExcelError::REF()) {
196+
return $range;
197+
}
198+
199+
throw new CalcException('Must specify range of cells, not any kind of literal');
200+
}
176201
$database = self::databaseFromRangeAndValue($range, $sumRange);
177202
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
178203

tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig;
66

7+
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
78
use PHPUnit\Framework\Attributes\DataProvider;
89

910
class SumIfTest extends AllSetupTeardown
@@ -38,4 +39,20 @@ public static function providerSUMIF(): array
3839
{
3940
return require 'tests/data/Calculation/MathTrig/SUMIF.php';
4041
}
42+
43+
public function testOutliers(): void
44+
{
45+
$sheet = $this->getSheet();
46+
$sheet->getCell('A1')->setValue('=SUMIF(5,"<32")');
47+
48+
try {
49+
$sheet->getCell('A1')->getCalculatedValue();
50+
self::fail('Should receive exception for non-array arg');
51+
} catch (CalcException $e) {
52+
self::assertStringContainsString('Must specify range of cells', $e->getMessage());
53+
}
54+
55+
$sheet->getCell('A4')->setValue('=SUMIF(#REF!,"<32")');
56+
self::assertSame('#REF!', $sheet->getCell('A4')->getCalculatedValue());
57+
}
4158
}

tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,8 @@ public function testOutliers(): void
4141
}
4242
$sheet->getCell('A3')->setValue('=AVERAGEIF(C1:C1,"<32")');
4343
self::assertSame(5, $sheet->getCell('A3')->getCalculatedValue(), 'first arg is single cell');
44+
45+
$sheet->getCell('A4')->setValue('=AVERAGEIF(#REF!,1)');
46+
self::assertSame('#REF!', $sheet->getCell('A4')->getCalculatedValue());
4447
}
4548
}

tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/CountIfTest.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
66

7+
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
710
class CountIfTest extends AllSetupTeardown
811
{
9-
#[\PHPUnit\Framework\Attributes\DataProvider('providerCOUNTIF')]
12+
#[DataProvider('providerCOUNTIF')]
1013
public function testCOUNTIF(mixed $expectedResult, mixed ...$args): void
1114
{
1215
$this->runTestCaseNoBracket('COUNTIF', $expectedResult, ...$args);
@@ -27,4 +30,20 @@ public static function providerCOUNTIF(): array
2730
{
2831
return require 'tests/data/Calculation/Statistical/COUNTIF.php';
2932
}
33+
34+
public function testOutliers(): void
35+
{
36+
$sheet = $this->getSheet();
37+
$sheet->getCell('A1')->setValue('=COUNTIF(5,"<32")');
38+
39+
try {
40+
$sheet->getCell('A1')->getCalculatedValue();
41+
self::fail('Should receive exception for non-array arg');
42+
} catch (CalcException $e) {
43+
self::assertStringContainsString('Must specify range of cells', $e->getMessage());
44+
}
45+
46+
$sheet->getCell('A4')->setValue('=COUNTIF(#REF!,1)');
47+
self::assertSame('#REF!', $sheet->getCell('A4')->getCalculatedValue());
48+
}
3049
}

0 commit comments

Comments
 (0)