Skip to content

Commit a432882

Browse files
authored
Merge pull request #4328 from oleibman/arraysexplicit
Make Explicit Array Return Type When Tests Require It
2 parents 06f0b4c + 4ad6b32 commit a432882

37 files changed

+238
-78
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
4646
### Fixed
4747

4848
- Xls Writer Parser Mishandling True/False Argument. [Issue #4331](https://github.com/PHPOffice/PhpSpreadsheet/issues/4331) [PR #4333](https://github.com/PHPOffice/PhpSpreadsheet/pull/4333)
49+
- Minor changes to dynamic array calculations exposed by using explicit array return types in some tests. [PR #4328](https://github.com/PHPOffice/PhpSpreadsheet/pull/4328)
4950

5051
## 2025-01-26 - 3.9.0
5152

samples/LookupRef/COLUMN.php

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
34
use PhpOffice\PhpSpreadsheet\Spreadsheet;
45

56
require __DIR__ . '/../Header.php';
@@ -8,6 +9,10 @@
89

910
// Create new PhpSpreadsheet object
1011
$spreadsheet = new Spreadsheet();
12+
$calculation = Calculation::getInstance($spreadsheet);
13+
$calculation->setInstanceArrayReturnType(
14+
Calculation::RETURN_ARRAY_AS_VALUE
15+
);
1116
$worksheet = $spreadsheet->getActiveSheet();
1217

1318
$worksheet->getCell('A1')->setValue('=COLUMN(C13)');

src/PhpSpreadsheet/Calculation/Calculation.php

+10-6
Original file line numberDiff line numberDiff line change
@@ -4887,17 +4887,18 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
48874887
}
48884888
$result = $operand1;
48894889
} else {
4890-
// In theory, we should truncate here.
4891-
// But I can't figure out a formula
4892-
// using the concatenation operator
4893-
// with literals that fits in 32K,
4894-
// so I don't think we can overflow here.
48954890
if (Information\ErrorValue::isError($operand1)) {
48964891
$result = $operand1;
48974892
} elseif (Information\ErrorValue::isError($operand2)) {
48984893
$result = $operand2;
48994894
} else {
4900-
$result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
4895+
$result = str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2));
4896+
$result = Shared\StringHelper::substring(
4897+
$result,
4898+
0,
4899+
DataType::MAX_STRING_LENGTH
4900+
);
4901+
$result = self::FORMULA_STRING_QUOTE . $result . self::FORMULA_STRING_QUOTE;
49014902
}
49024903
}
49034904
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
@@ -5046,6 +5047,9 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
50465047
while (is_array($cellValue)) {
50475048
$cellValue = array_shift($cellValue);
50485049
}
5050+
if (is_string($cellValue)) {
5051+
$cellValue = preg_replace('/"/', '""', $cellValue);
5052+
}
50495053
$this->debugLog->writeDebugLog('Scalar Result for cell %s is %s', $cellRef, $this->showTypeDetails($cellValue));
50505054
}
50515055
$this->processingAnchorArray = false;

src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ public static function periods(mixed $rate, mixed $presentValue, mixed $futureVa
7777
*
7878
* Calculates the interest rate required for an investment to grow to a specified future value .
7979
*
80-
* @param array|float $periods The number of periods over which the investment is made
81-
* @param array|float $presentValue Present Value
82-
* @param array|float $futureValue Future Value
80+
* @param mixed $periods The number of periods over which the investment is made, expect array|float
81+
* @param mixed $presentValue Present Value, expect array|float
82+
* @param mixed $futureValue Future Value, expect array|float
8383
*
8484
* @return float|string Result, or a string containing an error
8585
*/
86-
public static function interestRate(array|float $periods = 0.0, array|float $presentValue = 0.0, array|float $futureValue = 0.0): string|float
86+
public static function interestRate(mixed $periods = 0.0, mixed $presentValue = 0.0, mixed $futureValue = 0.0): string|float
8787
{
8888
$periods = Functions::flattenSingleValue($periods);
8989
$presentValue = Functions::flattenSingleValue($presentValue);

src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php

+6-6
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ class NonPeriodic
2323
* Excel Function:
2424
* =XIRR(values,dates,guess)
2525
*
26-
* @param float[] $values A series of cash flow payments
26+
* @param mixed $values A series of cash flow payments, expecting float[]
2727
* The series of values must contain at least one positive value & one negative value
2828
* @param mixed[] $dates A series of payment dates
2929
* The first payment date indicates the beginning of the schedule of payments
3030
* All other dates must be later than this date, but they may occur in any order
3131
* @param mixed $guess An optional guess at the expected answer
3232
*/
33-
public static function rate(array $values, array $dates, mixed $guess = self::DEFAULT_GUESS): float|string
33+
public static function rate(mixed $values, mixed $dates, mixed $guess = self::DEFAULT_GUESS): float|string
3434
{
3535
$rslt = self::xirrPart1($values, $dates);
3636
if ($rslt !== '') {
@@ -106,18 +106,18 @@ public static function rate(array $values, array $dates, mixed $guess = self::DE
106106
* Excel Function:
107107
* =XNPV(rate,values,dates)
108108
*
109-
* @param array|float $rate the discount rate to apply to the cash flows
110-
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
109+
* @param mixed $rate the discount rate to apply to the cash flows, expect array|float
110+
* @param mixed $values A series of cash flows that corresponds to a schedule of payments in dates, expecting floag[].
111111
* The first payment is optional and corresponds to a cost or payment that occurs
112112
* at the beginning of the investment.
113113
* If the first value is a cost or payment, it must be a negative value.
114114
* All succeeding payments are discounted based on a 365-day year.
115115
* The series of values must contain at least one positive value and one negative value.
116-
* @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments.
116+
* @param mixed $dates A schedule of payment dates that corresponds to the cash flow payments, expecting mixed[].
117117
* The first payment date indicates the beginning of the schedule of payments.
118118
* All other dates must be later than this date, but they may occur in any order.
119119
*/
120-
public static function presentValue(array|float $rate, array $values, array $dates): float|string
120+
public static function presentValue(mixed $rate, mixed $values, mixed $dates): float|string
121121
{
122122
return self::xnpvOrdered($rate, $values, $dates, true);
123123
}

src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php

+9
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ public static function index(mixed $matrix, mixed $rowNum = 0, mixed $columnNum
8282

8383
$rowNum = $rowNum ?? 0;
8484
$columnNum = $columnNum ?? 0;
85+
if (is_scalar($matrix)) {
86+
if ($rowNum === 0 || $rowNum === 1) {
87+
if ($columnNum === 0 || $columnNum === 1) {
88+
if ($columnNum === 1 || $rowNum === 1) {
89+
return $matrix;
90+
}
91+
}
92+
}
93+
}
8594

8695
try {
8796
$rowNum = LookupRefValidations::validatePositiveInt($rowNum);

src/PhpSpreadsheet/Cell/Cell.php

+10-3
Original file line numberDiff line numberDiff line change
@@ -408,9 +408,6 @@ public function getCalculatedValue(bool $resetLog = true): mixed
408408
$oldAttributesT = $oldAttributes['t'] ?? '';
409409
$coordinate = $this->getCoordinate();
410410
$oldAttributesRef = $oldAttributes['ref'] ?? $coordinate;
411-
if (!str_contains($oldAttributesRef, ':')) {
412-
$oldAttributesRef .= ":$oldAttributesRef";
413-
}
414411
$originalValue = $this->value;
415412
$originalDataType = $this->dataType;
416413
$this->formulaAttributes = [];
@@ -434,6 +431,14 @@ public function getCalculatedValue(bool $resetLog = true): mixed
434431
$result = array_shift($result);
435432
}
436433
}
434+
if (
435+
!is_array($result)
436+
&& $calculation->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY
437+
&& $oldAttributesT === 'array'
438+
&& ($oldAttributesRef === $coordinate || $oldAttributesRef === "$coordinate:$coordinate")
439+
) {
440+
$result = [$result];
441+
}
437442
// if return_as_array for formula like '=sheet!cell'
438443
if (is_array($result) && count($result) === 1) {
439444
$resultKey = array_keys($result)[0];
@@ -560,6 +565,8 @@ public function getCalculatedValue(bool $resetLog = true): mixed
560565
SharedDate::setExcelCalendar($currentCalendar);
561566

562567
if ($result === Functions::NOT_YET_IMPLEMENTED) {
568+
$this->formulaAttributes = $oldAttributes;
569+
563570
return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
564571
}
565572

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

+3
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,9 @@ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell
15131513

15141514
if (isset($attributes['ref'])) {
15151515
$ref = $this->parseRef($coordinate, $attributes['ref']);
1516+
if ($ref === "$coordinate:$coordinate") {
1517+
$ref = $coordinate;
1518+
}
15161519
} else {
15171520
$ref = $coordinate;
15181521
}

tests/PhpSpreadsheetTests/Calculation/ArrayFormulaTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public static function providerArrayFormulae(): array
6262
public function testArrayFormulaUsingCells(): void
6363
{
6464
$spreadsheet = new Spreadsheet();
65+
$calculation = Calculation::getInstance($spreadsheet);
66+
$calculation->setInstanceArrayReturnType(
67+
Calculation::RETURN_ARRAY_AS_VALUE
68+
);
6569
$sheet = $spreadsheet->getActiveSheet();
6670
$sheet->getCell('A4')->setValue(-3);
6771
$sheet->getCell('B4')->setValue(4);

tests/PhpSpreadsheetTests/Calculation/CalculationLoggingTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public function testFormulaWithLogging(): void
4242
public function testFormulaWithMultipleCellLogging(): void
4343
{
4444
$spreadsheet = new Spreadsheet();
45+
$calculation = Calculation::getInstance($spreadsheet);
46+
$calculation->setInstanceArrayReturnType(
47+
Calculation::RETURN_ARRAY_AS_VALUE
48+
);
4549
$sheet = $spreadsheet->getActiveSheet();
4650

4751
$sheet->fromArray(

tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
66

7+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
78
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PHPUnit\Framework\Attributes\DataProvider;
810
use PHPUnit\Framework\TestCase;
911

1012
class FormulaAsStringTest extends TestCase
1113
{
12-
#[\PHPUnit\Framework\Attributes\DataProvider('providerFunctionsAsString')]
14+
#[DataProvider('providerFunctionsAsString')]
1315
public function testFunctionsAsString(mixed $expectedResult, string $formula): void
1416
{
1517
$spreadsheet = new Spreadsheet();
18+
$calculation = Calculation::getInstance($spreadsheet);
19+
$calculation->setInstanceArrayReturnType(
20+
Calculation::RETURN_ARRAY_AS_VALUE
21+
);
1622
$workSheet = $spreadsheet->getActiveSheet();
1723
$workSheet->setCellValue('A1', 10);
1824
$workSheet->setCellValue('A2', 20);

tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsFormulaTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class IsFormulaTest extends TestCase
1515
public function testIsFormula(): void
1616
{
1717
$spreadsheet = new Spreadsheet();
18+
$calculation = Calculation::getInstance($spreadsheet);
19+
$calculation->setInstanceArrayReturnType(
20+
Calculation::RETURN_ARRAY_AS_VALUE
21+
);
1822
$sheet1 = $spreadsheet->getActiveSheet();
1923
$sheet1->setTitle('SheetOne'); // no space in sheet title
2024
$sheet2 = $spreadsheet->createSheet();

tests/PhpSpreadsheetTests/Calculation/Functions/Logical/AllSetupTeardown.php

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

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

7+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
78
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
89
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
910
use PhpOffice\PhpSpreadsheet\Cell\DataType;
@@ -100,4 +101,13 @@ protected function runTestCase(string $functionName, mixed $expectedResult, mixe
100101
$this->setCell('B1', $formula);
101102
self::assertSame($expectedResult, $sheet->getCell('B1')->getCalculatedValue());
102103
}
104+
105+
protected function setArrayAsValue(): void
106+
{
107+
$spreadsheet = $this->getSpreadsheet();
108+
$calculation = Calculation::getInstance($spreadsheet);
109+
$calculation->setInstanceArrayReturnType(
110+
Calculation::RETURN_ARRAY_AS_VALUE
111+
);
112+
}
103113
}

tests/PhpSpreadsheetTests/Calculation/Functions/Logical/AndTest.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

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

7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
79
class AndTest extends AllSetupTeardown
810
{
9-
#[\PHPUnit\Framework\Attributes\DataProvider('providerAND')]
11+
#[DataProvider('providerAND')]
1012
public function testAND(mixed $expectedResult, mixed ...$args): void
1113
{
14+
$this->setArrayAsValue();
1215
$this->runTestCase('AND', $expectedResult, ...$args);
1316
}
1417

@@ -17,7 +20,7 @@ public static function providerAND(): array
1720
return require 'tests/data/Calculation/Logical/AND.php';
1821
}
1922

20-
#[\PHPUnit\Framework\Attributes\DataProvider('providerANDLiteral')]
23+
#[DataProvider('providerANDLiteral')]
2124
public function testANDLiteral(bool|string $expectedResult, float|int|string $formula): void
2225
{
2326
$sheet = $this->getSheet();

tests/PhpSpreadsheetTests/Calculation/Functions/Logical/XorTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class XorTest extends AllSetupTeardown
99
#[\PHPUnit\Framework\Attributes\DataProvider('providerXOR')]
1010
public function testXOR(mixed $expectedResult, mixed ...$args): void
1111
{
12+
$this->setArrayAsValue();
1213
$this->runTestCase('XOR', $expectedResult, ...$args);
1314
}
1415

tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AllSetupTeardown.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class AllSetupTeardown extends TestCase
1818

1919
protected string $arrayReturnType;
2020

21-
private ?Spreadsheet $spreadsheet = null;
21+
protected ?Spreadsheet $spreadsheet = null;
2222

2323
private ?Worksheet $sheet = null;
2424

@@ -86,4 +86,13 @@ protected function getSheet(): Worksheet
8686

8787
return $this->sheet;
8888
}
89+
90+
protected function setArrayAsValue(): void
91+
{
92+
$spreadsheet = $this->getSpreadsheet();
93+
$calculation = Calculation::getInstance($spreadsheet);
94+
$calculation->setInstanceArrayReturnType(
95+
Calculation::RETURN_ARRAY_AS_VALUE
96+
);
97+
}
8998
}

tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
66

77
use PhpOffice\PhpSpreadsheet\NamedRange;
8+
use PHPUnit\Framework\Attributes\DataProvider;
89

910
class ColumnOnSpreadsheetTest extends AllSetupTeardown
1011
{
11-
#[\PHPUnit\Framework\Attributes\DataProvider('providerCOLUMNonSpreadsheet')]
12+
#[DataProvider('providerCOLUMNonSpreadsheet')]
1213
public function testColumnOnSpreadsheet(mixed $expectedResult, string $cellReference = 'omitted'): void
1314
{
1415
$this->mightHaveException($expectedResult);
16+
$this->setArrayAsValue();
1517
$sheet = $this->getSheet();
1618
$this->getSpreadsheet()->addNamedRange(new NamedRange('namedrangex', $sheet, '$E$2:$E$6'));
1719
$this->getSpreadsheet()->addNamedRange(new NamedRange('namedrangey', $sheet, '$F$2:$H$2'));
@@ -37,6 +39,7 @@ public static function providerCOLUMNonSpreadsheet(): array
3739

3840
public function testCOLUMNLocalDefinedName(): void
3941
{
42+
$this->setArrayAsValue();
4043
$sheet = $this->getSheet();
4144

4245
$sheet1 = $this->getSpreadsheet()->createSheet();

0 commit comments

Comments
 (0)