diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 04b5c5e1bf..bcd993be82 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -850,16 +850,15 @@ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, i if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { if ($matrix2Columns < $matrix1Columns) { for ($i = 0; $i < $matrix2Rows; ++$i) { - /** @var mixed[][] $matrix2 */ - $x = $matrix2[$i][$matrix2Columns - 1]; + $x = ($matrix2Columns === 1) ? $matrix2[$i][0] : null; for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) { $matrix2[$i][$j] = $x; } } } if ($matrix2Rows < $matrix1Rows) { - $x = $matrix2[$matrix2Rows - 1]; - for ($i = 0; $i < $matrix1Rows; ++$i) { + $x = ($matrix2Rows === 1) ? $matrix2[0] : array_fill(0, $matrix2Columns, null); + for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) { $matrix2[$i] = $x; } } @@ -868,16 +867,15 @@ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, i if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) { if ($matrix1Columns < $matrix2Columns) { for ($i = 0; $i < $matrix1Rows; ++$i) { - /** @var mixed[][] $matrix1 */ - $x = $matrix1[$i][$matrix1Columns - 1]; + $x = ($matrix1Columns === 1) ? $matrix1[$i][0] : null; for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) { $matrix1[$i][$j] = $x; } } } if ($matrix1Rows < $matrix2Rows) { - $x = $matrix1[$matrix1Rows - 1]; - for ($i = 0; $i < $matrix2Rows; ++$i) { + $x = ($matrix1Rows === 1) ? $matrix1[0] : array_fill(0, $matrix1Columns, null); + for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) { $matrix1[$i] = $x; } } @@ -2367,16 +2365,14 @@ private function executeNumericBinaryOperation(mixed $operand1, mixed $operand2, for ($row = 0; $row < $rows; ++$row) { for ($column = 0; $column < $columns; ++$column) { - /** @var mixed[][] $operand1 */ - if ($operand1[$row][$column] === null) { + if (($operand1[$row][$column] ?? null) === null) { $operand1[$row][$column] = 0; } elseif (!self::isNumericOrBool($operand1[$row][$column])) { $operand1[$row][$column] = self::makeError($operand1[$row][$column]); continue; } - /** @var mixed[][] $operand2 */ - if ($operand2[$row][$column] === null) { + if (($operand2[$row][$column] ?? null) === null) { $operand2[$row][$column] = 0; } elseif (!self::isNumericOrBool($operand2[$row][$column])) { $operand1[$row][$column] = self::makeError($operand2[$row][$column]); diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php b/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php index daedcd0b9f..1a2553beb6 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php @@ -45,7 +45,7 @@ private static function filterByRow(array $lookupArray, array $matchArray): arra return array_filter( array_values($lookupArray), - fn ($index): bool => (bool) $matchArray[$index], + fn ($index): bool => (bool) ($matchArray[$index] ?? null), ARRAY_FILTER_USE_KEY ); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Issue4451Test.php b/tests/PhpSpreadsheetTests/Calculation/Issue4451Test.php new file mode 100644 index 0000000000..3b9107d18f --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Issue4451Test.php @@ -0,0 +1,128 @@ +setAccessible(true); + + // Call the method using reflection + $reflectionMethod->invokeArgs($calculation, [&$matrix1, &$matrix2, count($matrix1), 1, count($matrix2), 1]); + + self::assertSame([[1], [3], [null]], $matrix1); //* @phpstan-ignore-line + } + + /** + * These 2 tests are contrived. They prove that method + * works as desired, but Excel will actually return + * a CALC error, a result I don't know how to duplicate. + */ + public static function testExtendFirstColumn(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Products'); + $calculationEngine = Calculation::getInstance($spreadsheet); + $calculationEngine->setInstanceArrayReturnType( + Calculation::RETURN_ARRAY_AS_ARRAY + ); + + $sheet->getCell('D5')->setValue(5); + $sheet->getCell('E5')->setValue(20); + $sheet->fromArray( + [ + [5, 20, 'Apples'], + [10, 20, 'Bananas'], + [5, 20, 'Cherries'], + [5, 40, 'Grapes'], + [25, 50, 'Peaches'], + [30, 60, 'Pears'], + [35, 70, 'Papayas'], + [40, 80, 'Mangos'], + [null, 20, 'Unknown'], + ], + null, + 'K1', + true + ); + $kRows = $sheet->getHighestDataRow('K'); + self::assertSame(8, $kRows); + $lRows = $sheet->getHighestDataRow('L'); + self::assertSame(9, $lRows); + $mRows = $sheet->getHighestDataRow('M'); + self::assertSame(9, $mRows); + $sheet->getCell('A1') + ->setValue( + "=FILTER(Products!M1:M$mRows," + . "(Products!K1:K$kRows=D5)" + . "*(Products!L1:L$lRows=E5))" + ); + + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertSame([['Apples'], ['Cherries']], $result); + $spreadsheet->disconnectWorksheets(); + } + + public static function testExtendSecondColumn(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Products'); + $calculationEngine = Calculation::getInstance($spreadsheet); + $calculationEngine->setInstanceArrayReturnType( + Calculation::RETURN_ARRAY_AS_ARRAY + ); + + $sheet->getCell('D5')->setValue(5); + $sheet->getCell('E5')->setValue(20); + $sheet->fromArray( + [ + [5, 20, 'Apples'], + [10, 20, 'Bananas'], + [5, 20, 'Cherries'], + [5, 40, 'Grapes'], + [25, 50, 'Peaches'], + [30, 60, 'Pears'], + [35, 70, 'Papayas'], + [40, 80, 'Mangos'], + [null, 20, 'Unknown'], + ], + null, + 'K1', + true + ); + $kRows = $sheet->getHighestDataRow('K'); + self::assertSame(8, $kRows); + //$lRows = $sheet->getHighestDataRow('L'); + //self::assertSame(9, $lRows); + $lRows = 2; + $mRows = $sheet->getHighestDataRow('M'); + self::assertSame(9, $mRows); + $sheet->getCell('A1') + ->setValue( + "=FILTER(Products!M1:M$mRows," + . "(Products!K1:K$kRows=D5)" + . "*(Products!L1:L$lRows=E5))" + ); + + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertSame([['Apples']], $result); + $spreadsheet->disconnectWorksheets(); + } +}