Skip to content

Commit 3eff3b4

Browse files
authored
Merge branch 'master' into issue4521
2 parents 3cefc50 + b271cbf commit 3eff3b4

File tree

8 files changed

+247
-114
lines changed

8 files changed

+247
-114
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3030
### Fixed
3131

3232
- Recognize application/x-empty mimetype. [Issue #4521](https://github.com/PHPOffice/PhpSpreadsheet/issues/4521) [PR #4524](https://github.com/PHPOffice/PhpSpreadsheet/pull/4524)
33+
- Micro-optimization in getSheetByName. [PR #4499](https://github.com/PHPOffice/PhpSpreadsheet/pull/4499)
34+
- Bug in resizeMatricesExtend. [Issue #4451](https://github.com/PHPOffice/PhpSpreadsheet/issues/4451) [PR #4474](https://github.com/PHPOffice/PhpSpreadsheet/pull/4474)
3335

3436
## 2025-06-22 - 4.4.0
3537

composer.lock

Lines changed: 87 additions & 85 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/topics/The Dating Game.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ Open/Libre Office and Gnumeric don't have this limitation, and negative date/tim
1414
To write a date in a cell using PhpSpreadsheet, we need to calculate the serialized Excel datestamp for that date. Methods to do this are available in the Shared\Date class, which provides a number of methods for conversion between different date options typically used in PHP applications (Unix timestamp, PHP DateTime objects and some recognisable formatted strings) and the Excel serialized value; and vice versa.
1515

1616
- Shared\Date::convertIsoDate()
17-
- Converts a date/time in [ISO-8601 standard format](https://en.wikipedia.org/wiki/ISO_8601) to an Excel serialized timestamp
17+
- Converts a date/time in [ISO-8601 standard format](https://en.wikipedia.org/wiki/ISO_8601) to an Excel serialized timestamp
1818
- Shared\Date::PHPToExcel()
19-
- Converts a Unix timestamp, a PHP DateTime object, or a recognisable formatted string to an Excel serialized timestamp
19+
- Converts a Unix timestamp, a PHP DateTime object, or a recognisable formatted string to an Excel serialized timestamp
2020
- Shared\Date::dateTimeToExcel()
21-
- Converts a Unix timestamp to an Excel serialized timestamp
21+
- Converts a Unix timestamp to an Excel serialized timestamp
2222
- Shared\Date::timestampToExcel()
23-
- Converts a PHP DateTime object to an Excel serialized timestamp
23+
- Converts a PHP DateTime object to an Excel serialized timestamp
2424
- Shared\Date::formattedPHPToExcel()
25-
- Converts year, month, day, hour, minute, and second to an Excel serialized timestamp
25+
- Converts year, month, day, hour, minute, and second to an Excel serialized timestamp
2626
- Shared\Date::excelToDateTimeObject()
27-
- Converts an Excel serialized timestamp to a PHP DateTime object
27+
- Converts an Excel serialized timestamp to a PHP DateTime object
2828
- Shared\Date::excelToTimestamp()
29-
- Converts an Excel serialized timestamp to a Unix timestamp.
30-
- The use of Unix timestamps, and therefore this function, is discouraged: they are not Y2038-safe on a 32-bit system, and have no timezone info.
29+
- Converts an Excel serialized timestamp to a Unix timestamp.
30+
- The use of Unix timestamps, and therefore this function, is discouraged: they are not Y2038-safe on a 32-bit system, and have no timezone info.
3131

3232
We probably also want to set the number format mask for the cell so that it will be displayed as a human-readable date.
3333
```php

docs/topics/defined-names.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -545,17 +545,18 @@ $this->spreadsheet->addDefinedName(
545545
### Naming Names
546546

547547
The names that you assign to Defined Name must follow the following set of rules:
548+
548549
- The first character of a name must be one of the following characters:
549-
- letter (including UTF-8 letters)
550-
- underscore (`_`)
550+
- letter (including UTF-8 letters)
551+
- underscore (`_`)
551552
- Remaining characters in the name can be
552-
- letters (including UTF-8 letters)
553-
- numbers (including UTF-8 numbers)
554-
- periods (`.`)
555-
- underscore characters (`_`)
553+
- letters (including UTF-8 letters)
554+
- numbers (including UTF-8 numbers)
555+
- periods (`.`)
556+
- underscore characters (`_`)
556557
- The following are not allowed:
557-
- Space characters are not allowed as part of a name.
558-
- Names can't look like cell addresses, such as A35 or R2C2
558+
- Space characters are not allowed as part of a name.
559+
- Names can't look like cell addresses, such as A35 or R2C2
559560
- Names are not case sensitive. For example, `North` and `NORTH` are treated as the same name.
560561

561562
### Limitations

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -852,15 +852,15 @@ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, i
852852
if ($matrix2Columns < $matrix1Columns) {
853853
for ($i = 0; $i < $matrix2Rows; ++$i) {
854854
/** @var mixed[][] $matrix2 */
855-
$x = $matrix2[$i][$matrix2Columns - 1];
855+
$x = ($matrix2Columns === 1) ? $matrix2[$i][0] : null;
856856
for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
857857
$matrix2[$i][$j] = $x;
858858
}
859859
}
860860
}
861861
if ($matrix2Rows < $matrix1Rows) {
862-
$x = $matrix2[$matrix2Rows - 1];
863-
for ($i = 0; $i < $matrix1Rows; ++$i) {
862+
$x = ($matrix2Rows === 1) ? $matrix2[0] : array_fill(0, $matrix2Columns, null);
863+
for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
864864
$matrix2[$i] = $x;
865865
}
866866
}
@@ -870,15 +870,15 @@ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, i
870870
if ($matrix1Columns < $matrix2Columns) {
871871
for ($i = 0; $i < $matrix1Rows; ++$i) {
872872
/** @var mixed[][] $matrix1 */
873-
$x = $matrix1[$i][$matrix1Columns - 1];
873+
$x = ($matrix1Columns === 1) ? $matrix1[$i][0] : null;
874874
for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
875875
$matrix1[$i][$j] = $x;
876876
}
877877
}
878878
}
879879
if ($matrix1Rows < $matrix2Rows) {
880-
$x = $matrix1[$matrix1Rows - 1];
881-
for ($i = 0; $i < $matrix2Rows; ++$i) {
880+
$x = ($matrix1Rows === 1) ? $matrix1[0] : array_fill(0, $matrix1Columns, null);
881+
for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
882882
$matrix1[$i] = $x;
883883
}
884884
}
@@ -2375,15 +2375,15 @@ private function executeNumericBinaryOperation(mixed $operand1, mixed $operand2,
23752375
for ($row = 0; $row < $rows; ++$row) {
23762376
for ($column = 0; $column < $columns; ++$column) {
23772377
/** @var mixed[][] $operand1 */
2378-
if ($operand1[$row][$column] === null) {
2378+
if (($operand1[$row][$column] ?? null) === null) {
23792379
$operand1[$row][$column] = 0;
23802380
} elseif (!self::isNumericOrBool($operand1[$row][$column])) {
23812381
$operand1[$row][$column] = self::makeError($operand1[$row][$column]);
23822382

23832383
continue;
23842384
}
23852385
/** @var mixed[][] $operand2 */
2386-
if ($operand2[$row][$column] === null) {
2386+
if (($operand2[$row][$column] ?? null) === null) {
23872387
$operand2[$row][$column] = 0;
23882388
} elseif (!self::isNumericOrBool($operand2[$row][$column])) {
23892389
$operand1[$row][$column] = self::makeError($operand2[$row][$column]);

src/PhpSpreadsheet/Calculation/LookupRef/Filter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ private static function filterByRow(array $lookupArray, array $matchArray): arra
6262

6363
return array_filter(
6464
array_values($lookupArray),
65-
fn ($index): bool => (bool) $matchArray[$index],
65+
fn ($index): bool => (bool) ($matchArray[$index] ?? null),
6666
ARRAY_FILTER_USE_KEY
6767
);
6868
}

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,10 +691,10 @@ public function getAllSheets(): array
691691
*/
692692
public function getSheetByName(string $worksheetName): ?Worksheet
693693
{
694-
$worksheetCount = count($this->workSheetCollection);
695-
for ($i = 0; $i < $worksheetCount; ++$i) {
696-
if (strcasecmp($this->workSheetCollection[$i]->getTitle(), trim($worksheetName, "'")) === 0) {
697-
return $this->workSheetCollection[$i];
694+
$trimWorksheetName = trim($worksheetName, "'");
695+
foreach ($this->workSheetCollection as $worksheet) {
696+
if (strcasecmp($worksheet->getTitle(), $trimWorksheetName) === 0) {
697+
return $worksheet;
698698
}
699699
}
700700

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
6+
7+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PHPUnit\Framework\TestCase;
10+
use ReflectionMethod;
11+
12+
class Issue4451Test extends TestCase
13+
{
14+
public static function testReflect(): void
15+
{
16+
// Sample matrices to test with
17+
$matrix1 = [[1], [3]];
18+
$matrix2 = [[5], [8], [11]];
19+
20+
// Use reflection to make the protected method accessible
21+
$calculation = new Calculation();
22+
$reflectionMethod = new ReflectionMethod(Calculation::class, 'resizeMatricesExtend');
23+
$reflectionMethod->setAccessible(true);
24+
25+
// Call the method using reflection
26+
$reflectionMethod->invokeArgs($calculation, [&$matrix1, &$matrix2, count($matrix1), 1, count($matrix2), 1]);
27+
28+
self::assertSame([[1], [3], [null]], $matrix1); //* @phpstan-ignore-line
29+
}
30+
31+
/**
32+
* These 2 tests are contrived. They prove that method
33+
* works as desired, but Excel will actually return
34+
* a CALC error, a result I don't know how to duplicate.
35+
*/
36+
public static function testExtendFirstColumn(): void
37+
{
38+
$spreadsheet = new Spreadsheet();
39+
$sheet = $spreadsheet->getActiveSheet();
40+
$sheet->setTitle('Products');
41+
$calculationEngine = Calculation::getInstance($spreadsheet);
42+
$calculationEngine->setInstanceArrayReturnType(
43+
Calculation::RETURN_ARRAY_AS_ARRAY
44+
);
45+
46+
$sheet->getCell('D5')->setValue(5);
47+
$sheet->getCell('E5')->setValue(20);
48+
$sheet->fromArray(
49+
[
50+
[5, 20, 'Apples'],
51+
[10, 20, 'Bananas'],
52+
[5, 20, 'Cherries'],
53+
[5, 40, 'Grapes'],
54+
[25, 50, 'Peaches'],
55+
[30, 60, 'Pears'],
56+
[35, 70, 'Papayas'],
57+
[40, 80, 'Mangos'],
58+
[null, 20, 'Unknown'],
59+
],
60+
null,
61+
'K1',
62+
true
63+
);
64+
$kRows = $sheet->getHighestDataRow('K');
65+
self::assertSame(8, $kRows);
66+
$lRows = $sheet->getHighestDataRow('L');
67+
self::assertSame(9, $lRows);
68+
$mRows = $sheet->getHighestDataRow('M');
69+
self::assertSame(9, $mRows);
70+
$sheet->getCell('A1')
71+
->setValue(
72+
"=FILTER(Products!M1:M$mRows,"
73+
. "(Products!K1:K$kRows=D5)"
74+
. "*(Products!L1:L$lRows=E5))"
75+
);
76+
77+
$result = $sheet->getCell('A1')->getCalculatedValue();
78+
self::assertSame([['Apples'], ['Cherries']], $result);
79+
$spreadsheet->disconnectWorksheets();
80+
}
81+
82+
public static function testExtendSecondColumn(): void
83+
{
84+
$spreadsheet = new Spreadsheet();
85+
$sheet = $spreadsheet->getActiveSheet();
86+
$sheet->setTitle('Products');
87+
$calculationEngine = Calculation::getInstance($spreadsheet);
88+
$calculationEngine->setInstanceArrayReturnType(
89+
Calculation::RETURN_ARRAY_AS_ARRAY
90+
);
91+
92+
$sheet->getCell('D5')->setValue(5);
93+
$sheet->getCell('E5')->setValue(20);
94+
$sheet->fromArray(
95+
[
96+
[5, 20, 'Apples'],
97+
[10, 20, 'Bananas'],
98+
[5, 20, 'Cherries'],
99+
[5, 40, 'Grapes'],
100+
[25, 50, 'Peaches'],
101+
[30, 60, 'Pears'],
102+
[35, 70, 'Papayas'],
103+
[40, 80, 'Mangos'],
104+
[null, 20, 'Unknown'],
105+
],
106+
null,
107+
'K1',
108+
true
109+
);
110+
$kRows = $sheet->getHighestDataRow('K');
111+
self::assertSame(8, $kRows);
112+
//$lRows = $sheet->getHighestDataRow('L');
113+
//self::assertSame(9, $lRows);
114+
$lRows = 2;
115+
$mRows = $sheet->getHighestDataRow('M');
116+
self::assertSame(9, $mRows);
117+
$sheet->getCell('A1')
118+
->setValue(
119+
"=FILTER(Products!M1:M$mRows,"
120+
. "(Products!K1:K$kRows=D5)"
121+
. "*(Products!L1:L$lRows=E5))"
122+
);
123+
124+
$result = $sheet->getCell('A1')->getCalculatedValue();
125+
self::assertSame([['Apples']], $result);
126+
$spreadsheet->disconnectWorksheets();
127+
}
128+
}

0 commit comments

Comments
 (0)