Skip to content

Commit b00dd47

Browse files
committed
Instance Variable for Array Return Type
Till now we have used a static variable/getter/setter to decide what type of result should be returned when a formula is evaluated and an array is the result. This is messy; it would be much better to use an instance variable instead. We cannot eliminate `setArrayReturnType` and `getArrayReturnType` because that would be a BC break. I am considering whether they should be deprecated. In the meantime, I have added a new instance property `instanceArrayReturnType` with getter and setter methods. The property is initially null, and, if it remains so when needed, the static property will be used instead. However, if it is set, its value will be used.
1 parent fda4855 commit b00dd47

24 files changed

+137
-266
lines changed

docs/topics/recipes.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,10 +378,17 @@ $value = $spreadsheet->getActiveSheet()->getCell('B8')->getCalculatedValue();
378378
With version 2.0.3 of PhpSpreadsheet, we've introduced support for Excel "array formulas".
379379
**It is an opt-in feature.** You need to enable it with the following code:
380380
```php
381+
// preferred method
382+
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance($spreadsheet)
383+
->setInstanceArrayReturnType(
384+
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::RETURN_ARRAY_AS_ARRAY);
385+
// or less preferred
381386
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::setArrayReturnType(
382387
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::RETURN_ARRAY_AS_ARRAY);
383388
```
384-
This is not a new function or constant, but it has till now not had much effect.
389+
This is not a new constant, and setArrayReturnType is also not new, but it has till now not had much effect.
390+
The instance variable set by the new setInstanceArrayReturnType
391+
will always be checked first, and the static variable used only if the instance variable is uninitialized.
385392

386393
As a basic example, let's look at a receipt for buying some fruit:
387394

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,12 @@ class Calculation
6767
const FORMULA_CLOSE_MATRIX_BRACE = '}';
6868
const FORMULA_STRING_QUOTE = '"';
6969

70+
/** Preferable to use instance variable instanceArrayReturnType rather than this static property. */
7071
private static string $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
7172

73+
/** Preferable to use this instance variable rather than static returnArrayAsType */
74+
private ?string $instanceArrayReturnType = null;
75+
7276
/**
7377
* Instance of this class.
7478
*
@@ -3003,6 +3007,38 @@ public static function getArrayReturnType(): string
30033007
return self::$returnArrayAsType;
30043008
}
30053009

3010+
/**
3011+
* Set the Instance Array Return Type (Array or Value of first element in the array).
3012+
*
3013+
* @param string $returnType Array return type
3014+
*
3015+
* @return bool Success or failure
3016+
*/
3017+
public function setInstanceArrayReturnType(string $returnType): bool
3018+
{
3019+
if (
3020+
($returnType == self::RETURN_ARRAY_AS_VALUE)
3021+
|| ($returnType == self::RETURN_ARRAY_AS_ERROR)
3022+
|| ($returnType == self::RETURN_ARRAY_AS_ARRAY)
3023+
) {
3024+
$this->instanceArrayReturnType = $returnType;
3025+
3026+
return true;
3027+
}
3028+
3029+
return false;
3030+
}
3031+
3032+
/**
3033+
* Return the Array Return Type (Array or Value of first element in the array).
3034+
*
3035+
* @return string $returnType Array return type for instance if non-null, otherwise static property
3036+
*/
3037+
public function getInstanceArrayReturnType(): string
3038+
{
3039+
return $this->instanceArrayReturnType ?? self::$returnArrayAsType;
3040+
}
3041+
30063042
/**
30073043
* Is calculation caching enabled?
30083044
*/
@@ -3506,9 +3542,9 @@ public function calculateCellValue(?Cell $cell = null, bool $resetLog = true): m
35063542
throw new Exception($e->getMessage(), $e->getCode(), $e);
35073543
}
35083544

3509-
if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
3545+
if (is_array($result) && self::getInstanceArrayReturnType() !== self::RETURN_ARRAY_AS_ARRAY) {
35103546
$testResult = Functions::flattenArray($result);
3511-
if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
3547+
if (self::getInstanceArrayReturnType() == self::RETURN_ARRAY_AS_ERROR) {
35123548
return ExcelError::VALUE();
35133549
}
35143550
$result = array_shift($testResult);
@@ -4556,9 +4592,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
45564592
// help us to know when pruning ['branchTestId' => true/false]
45574593
$branchStore = [];
45584594
// Loop through each token in turn
4559-
$tokenIdx = -1;
4560-
foreach ($tokens as $tokenData) {
4561-
++$tokenIdx;
4595+
foreach ($tokens as $tokenIdx => $tokenData) {
45624596
$this->processingAnchorArray = false;
45634597
if ($tokenData['type'] === 'Cell Reference' && isset($tokens[$tokenIdx + 1]) && $tokens[$tokenIdx + 1]['type'] === 'Operand Count for Function ANCHORARRAY()') {
45644598
$this->processingAnchorArray = true;
@@ -4969,7 +5003,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
49695003
}
49705004
}
49715005

4972-
if (self::$returnArrayAsType === self::RETURN_ARRAY_AS_ARRAY && !$this->processingAnchorArray && is_array($cellValue)) {
5006+
if (self::getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY && !$this->processingAnchorArray && is_array($cellValue)) {
49735007
while (is_array($cellValue)) {
49745008
$cellValue = array_shift($cellValue);
49755009
}
@@ -5433,7 +5467,7 @@ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet =
54335467
sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
54345468
if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) {
54355469
$temp = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
5436-
if (self::$returnArrayAsType === self::RETURN_ARRAY_AS_ARRAY) {
5470+
if (self::getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY) {
54375471
while (is_array($temp)) {
54385472
$temp = array_shift($temp);
54395473
}
@@ -5449,7 +5483,7 @@ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet =
54495483
sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
54505484
if ($worksheet !== null && $worksheet->cellExists($reference)) {
54515485
$temp = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
5452-
if (self::$returnArrayAsType === self::RETURN_ARRAY_AS_ARRAY) {
5486+
if (self::getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY) {
54535487
while (is_array($temp)) {
54545488
$temp = array_shift($temp);
54555489
}

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -404,17 +404,16 @@ public function getCalculatedValue(bool $resetLog = true): mixed
404404
try {
405405
$currentCalendar = SharedDate::getExcelCalendar();
406406
SharedDate::setExcelCalendar($this->getWorksheet()->getParent()?->getExcelCalendar());
407-
$index = $this->getWorksheet()->getParentOrThrow()->getActiveSheetIndex();
408-
$selected = $this->getWorksheet()->getSelectedCells();
409407
$thisworksheet = $this->getWorksheet();
408+
$index = $thisworksheet->getParentOrThrow()->getActiveSheetIndex();
409+
$selected = $thisworksheet->getSelectedCells();
410410
$title = $thisworksheet->getTitle();
411-
$result = Calculation::getInstance(
412-
$thisworksheet->getParent()
413-
)->calculateCellValue($this, $resetLog);
411+
$calculation = Calculation::getInstance($thisworksheet->getParent());
412+
$result = $calculation->calculateCellValue($this, $resetLog);
414413
$result = $this->convertDateTimeInt($result);
415414
$thisworksheet->setSelectedCells($selected);
416415
$thisworksheet->getParentOrThrow()->setActiveSheetIndex($index);
417-
if (is_array($result) && Calculation::getArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) {
416+
if (is_array($result) && $calculation->getInstanceArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) {
418417
while (is_array($result)) {
419418
$result = array_shift($result);
420419
}
@@ -475,7 +474,7 @@ public function getCalculatedValue(bool $resetLog = true): mixed
475474
$thisworksheet->getCell($column . $row);
476475
}
477476
if (is_array($result)) {
478-
if ($oldAttributes !== null && Calculation::getArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY) {
477+
if ($oldAttributes !== null && $calculation->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY) {
479478
if (($oldAttributesT) === 'array') {
480479
$thisworksheet = $this->getWorksheet();
481480
$coordinate = $this->getCoordinate();

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3686,7 +3686,7 @@ public function copyCells(string $fromCell, string $toCells, bool $copyStyle = t
36863686

36873687
public function calculateArrays(bool $preCalculateFormulas = true): void
36883688
{
3689-
if ($preCalculateFormulas && Calculation::getArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY) {
3689+
if ($preCalculateFormulas && Calculation::getInstance($this->parent)->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY) {
36903690
$keys = $this->cellCollection->getCoordinates();
36913691
foreach ($keys as $key) {
36923692
if ($this->getCell($key)->getDataType() === DataType::TYPE_FORMULA) {
@@ -3698,7 +3698,7 @@ public function calculateArrays(bool $preCalculateFormulas = true): void
36983698

36993699
public function isCellInSpillRange(string $coordinate): bool
37003700
{
3701-
if (Calculation::getArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) {
3701+
if (Calculation::getInstance($this->parent)->getInstanceArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) {
37023702
return false;
37033703
}
37043704
$this->calculateArrays();

src/PhpSpreadsheet/Writer/Xlsx.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,6 @@ public function useDynamicArrays(): bool
737737

738738
private function determineUseDynamicArrays(): void
739739
{
740-
$this->useDynamicArray = $this->preCalculateFormulas && Calculation::getInstance($this->spreadSheet)->getArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY && !$this->useCSEArrays;
740+
$this->useDynamicArray = $this->preCalculateFormulas && Calculation::getInstance($this->spreadSheet)->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY && !$this->useCSEArrays;
741741
}
742742
}

tests/PhpSpreadsheetTests/Calculation/ArrayTest.php

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

55
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
66

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

1012
class ArrayTest extends TestCase
@@ -33,4 +35,40 @@ public function testMultiDimensionalArrayIsFlattened(): void
3335
self::assertIsNotArray($values[0]);
3436
self::assertIsNotArray($values[1]);
3537
}
38+
39+
public function testPropagateStatic(): void
40+
{
41+
$oldValue = Calculation::getArrayReturnType();
42+
$calculation = new Calculation();
43+
self::assertTrue(Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY));
44+
self::assertFalse(Calculation::setArrayReturnType('xxx'));
45+
self::assertSame(Calculation::RETURN_ARRAY_AS_ARRAY, Calculation::getArrayReturnType());
46+
self::assertFalse($calculation->setArrayReturnType('xxx'));
47+
self::assertSame(Calculation::RETURN_ARRAY_AS_ARRAY, $calculation->getInstanceArrayReturnType());
48+
self::assertTrue($calculation->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ERROR));
49+
self::assertSame(Calculation::RETURN_ARRAY_AS_ARRAY, Calculation::getArrayReturnType());
50+
self::assertSame(Calculation::RETURN_ARRAY_AS_ERROR, $calculation->getInstanceArrayReturnType());
51+
Calculation::setArrayReturnType($oldValue);
52+
}
53+
54+
public function testReturnTypes(): void
55+
{
56+
$spreadsheet = new Spreadsheet();
57+
$sheet = $spreadsheet->getActiveSheet();
58+
$calculation = Calculation::getInstance($spreadsheet);
59+
$sheet->setCellValue('A1', 2.0);
60+
$sheet->setCellValue('A2', 0.0);
61+
$sheet->setCellValue('B1', 0.0);
62+
$sheet->setCellValue('B2', 1.0);
63+
$sheet->setCellValue('D1', '=MINVERSE(A1:B2)');
64+
$calculation->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ERROR);
65+
self::assertSame('#VALUE!', $sheet->getCell('D1')->getCalculatedValue());
66+
$calculation->flushInstance();
67+
$calculation->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
68+
self::assertSame(0.5, $sheet->getCell('D1')->getCalculatedValue());
69+
$calculation->flushInstance();
70+
$calculation->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
71+
self::assertSame([[0.5, 0.0], [0.0, 1.0]], $sheet->getCell('D1')->getCalculatedValue());
72+
$spreadsheet->disconnectWorksheets();
73+
}
3674
}

tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateRangeTest.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,6 @@
88

99
class ConcatenateRangeTest extends AllSetupTeardown
1010
{
11-
private string $arrayReturnType;
12-
13-
protected function setUp(): void
14-
{
15-
parent::setUp();
16-
$this->arrayReturnType = Calculation::getArrayReturnType();
17-
}
18-
19-
protected function tearDown(): void
20-
{
21-
parent::tearDown();
22-
Calculation::setArrayReturnType($this->arrayReturnType);
23-
}
24-
2511
public function testIssue4061(): void
2612
{
2713
$sheet = $this->getSheet();
@@ -37,7 +23,7 @@ public function testIssue4061(): void
3723
self::assertSame('a-1', $sheet->getCell('X1')->getCalculatedValue());
3824
$sheet->getCell('D1')->setValue('=CONCAT(A1:A3, "-", C1:C3)');
3925
self::assertSame('abc-123', $sheet->getCell('D1')->getCalculatedValue());
40-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
26+
Calculation::getInstance($this->getSpreadsheet())->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
4127
$sheet->getCell('E1')->setValue('=CONCATENATE(A1:A3, "-", C1:C3)');
4228
self::assertSame([['a-1'], ['b-2'], ['c-3']], $sheet->getCell('E1')->getCalculatedValue());
4329
$sheet->getCell('Y1')->setValue('=A1:A3&"-"&C1:C3');

tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextSplitTest.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@
99

1010
class TextSplitTest extends AllSetupTeardown
1111
{
12-
private string $returnType;
13-
14-
protected function setUp(): void
15-
{
16-
parent::setUp();
17-
$this->returnType = Calculation::getInstance($this->getSpreadsheet())->getArrayReturnType();
18-
Calculation::getInstance($this->getSpreadsheet())->setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
19-
}
20-
21-
protected function tearDown(): void
22-
{
23-
Calculation::getInstance($this->getSpreadsheet())->setArrayReturnType($this->returnType);
24-
parent::tearDown();
25-
}
26-
2712
private function setDelimiterArgument(array $argument, string $column): string
2813
{
2914
return '{' . $column . implode(',' . $column, range(1, count($argument))) . '}';
@@ -46,6 +31,7 @@ private function setDelimiterValues(Worksheet $worksheet, string $column, mixed
4631
*/
4732
public function testTextSplit(array $expectedResult, array $arguments): void
4833
{
34+
Calculation::getInstance($this->getSpreadsheet())->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
4935
$text = $arguments[0];
5036
$columnDelimiter = $arguments[1];
5137
$rowDelimiter = $arguments[2];

tests/PhpSpreadsheetTests/Calculation/InternalFunctionsTest.php

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,13 @@
1010

1111
class InternalFunctionsTest extends TestCase
1212
{
13-
private string $arrayReturnType;
14-
15-
protected function setUp(): void
16-
{
17-
$this->arrayReturnType = Calculation::getArrayReturnType();
18-
}
19-
20-
protected function tearDown(): void
21-
{
22-
Calculation::setArrayReturnType($this->arrayReturnType);
23-
}
24-
2513
/**
2614
* @dataProvider anchorArrayDataProvider
2715
*/
2816
public function testAnchorArrayFormula(string $reference, string $range, array $expectedResult): void
2917
{
30-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
3118
$spreadsheet = new Spreadsheet();
19+
Calculation::getInstance($spreadsheet)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
3220
$sheet1 = $spreadsheet->getActiveSheet();
3321
$sheet1->setTitle('SheetOne'); // no space in sheet title
3422
$sheet2 = $spreadsheet->createSheet();
@@ -68,8 +56,8 @@ public static function anchorArrayDataProvider(): array
6856
*/
6957
public function testSingleArrayFormula(string $reference, mixed $expectedResult): void
7058
{
71-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
7259
$spreadsheet = new Spreadsheet();
60+
Calculation::getInstance($spreadsheet)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
7361
$sheet1 = $spreadsheet->getActiveSheet();
7462
$sheet1->setTitle('SheetOne'); // no space in sheet title
7563
$sheet2 = $spreadsheet->createSheet();

tests/PhpSpreadsheetTests/Cell/CellArrayFormulaTest.php

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,12 @@
99

1010
class CellArrayFormulaTest extends TestCase
1111
{
12-
private string $arrayReturnType;
13-
1412
private bool $skipUpdateInSpillageRange = true;
1513

16-
protected function setUp(): void
17-
{
18-
$this->arrayReturnType = Calculation::getArrayReturnType();
19-
}
20-
21-
protected function tearDown(): void
22-
{
23-
Calculation::setArrayReturnType($this->arrayReturnType);
24-
}
25-
2614
public function testSetValueArrayFormulaNoSpillage(): void
2715
{
28-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
2916
$spreadsheet = new Spreadsheet();
17+
Calculation::getInstance($spreadsheet)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
3018
$cell = $spreadsheet->getActiveSheet()->getCell('A1');
3119
$cell->setValue('=MAX(ABS({5, -3; 1, -12}))');
3220

@@ -37,8 +25,8 @@ public function testSetValueArrayFormulaNoSpillage(): void
3725

3826
public function testSetValueArrayFormulaWithSpillage(): void
3927
{
40-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
4128
$spreadsheet = new Spreadsheet();
29+
Calculation::getInstance($spreadsheet)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
4230
$cell = $spreadsheet->getActiveSheet()->getCell('A1');
4331
$cell->setValue('=SEQUENCE(3, 3, 1, 1)');
4432

@@ -49,8 +37,8 @@ public function testSetValueArrayFormulaWithSpillage(): void
4937

5038
public function testSetValueInSpillageRangeCell(): void
5139
{
52-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
5340
$spreadsheet = new Spreadsheet();
41+
Calculation::getInstance($spreadsheet)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
5442
$sheet = $spreadsheet->getActiveSheet();
5543
$cell = $sheet->getCell('A1');
5644
$cell->setValue('=SEQUENCE(3, 3, 1, 1)');
@@ -66,8 +54,8 @@ public function testSetValueInSpillageRangeCell(): void
6654

6755
public function testUpdateValueInSpillageRangeCell(): void
6856
{
69-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
7057
$spreadsheet = new Spreadsheet();
58+
Calculation::getInstance($spreadsheet)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
7159
$sheet = $spreadsheet->getActiveSheet();
7260
$sheet->getCell('A1')->setValue('=SEQUENCE(3, 3, 1, 1)');
7361
$sheet->getCell('A1')->getCalculatedValue();
@@ -92,9 +80,9 @@ public function testUpdateValueInSpillageRangeCell(): void
9280

9381
public function testUpdateArrayFormulaForSpillageRange(): void
9482
{
95-
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
9683
$spreadsheet = new Spreadsheet();
9784
$calculation = Calculation::getInstance($spreadsheet);
85+
$calculation->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
9886
$sheet = $spreadsheet->getActiveSheet();
9987
$sheet->getCell('A1')->setValue('=SEQUENCE(3, 3, 1, 1)');
10088
$sheet->getCell('A1')->getCalculatedValue();

0 commit comments

Comments
 (0)