Skip to content

Commit 1cdc6cd

Browse files
authored
Breaking Change to toFormattedString and 3 toArray Methods (#3304)
* Breaking Change to toFormattedString and 3 toArray Methods DocBlock says `toFormattedString` returns string, but code can return int, float, bool, null, or RichText. I think the DocBlock is correct (function name clearly indicates "to string"), and the code needs to change to match it. This is not that big a deal on its own. However, some methods in `Worksheet` call this function by default - `rangeToArray`, `toArray`, and `namedRangeToArray`. All 3 can be called with a parameter so that they don't call `toFormattedString`, but, by default, they do call, and so are affected by this change. Some unit test results are changed as a result of this code change. In all those cases, an additional test is added which would match the previous result. * Update Change Log I often skip this till later, but, since it's a breaking change ... * Scrutinizer, Plus Strange Cast to -0 Avoid Scrutinizer complaint by casting string to float, but ... Php decides resulted should sometimes be `-0`? That caused test failures. Very odd.
1 parent c77c39b commit 1cdc6cd

File tree

14 files changed

+164
-186
lines changed

14 files changed

+164
-186
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3939

4040
### Changed
4141

42-
- Nothing
42+
- `toFormattedString` will now always return a string. This can affect the results of `toArray`, `namedRangeToArray`, and `rangeToArray`. [PR #3304](https://github.com/PHPOffice/PhpSpreadsheet/pull/3304)
4343

4444
### Deprecated
4545

phpstan-baseline.neon

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -790,56 +790,6 @@ parameters:
790790
count: 1
791791
path: src/PhpSpreadsheet/Shared/Trend/Trend.php
792792

793-
-
794-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has no return type specified\\.$#"
795-
count: 1
796-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
797-
798-
-
799-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has parameter \\$sections with no type specified\\.$#"
800-
count: 1
801-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
802-
803-
-
804-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has parameter \\$value with no type specified\\.$#"
805-
count: 1
806-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
807-
808-
-
809-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has no return type specified\\.$#"
810-
count: 1
811-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
812-
813-
-
814-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$cond with no type specified\\.$#"
815-
count: 1
816-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
817-
818-
-
819-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$dfcond with no type specified\\.$#"
820-
count: 1
821-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
822-
823-
-
824-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$dfval with no type specified\\.$#"
825-
count: 1
826-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
827-
828-
-
829-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$val with no type specified\\.$#"
830-
count: 1
831-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
832-
833-
-
834-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$value with no type specified\\.$#"
835-
count: 1
836-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
837-
838-
-
839-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:toFormattedString\\(\\) should return string but returns float\\|int\\|string\\.$#"
840-
count: 1
841-
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
842-
843793
-
844794
message: "#^Negated boolean expression is always false\\.$#"
845795
count: 1

src/PhpSpreadsheet/Style/NumberFormat/Formatter.php

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6+
use PhpOffice\PhpSpreadsheet\RichText\RichText;
57
use PhpOffice\PhpSpreadsheet\Style\Color;
68
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
79

810
class Formatter
911
{
10-
private static function splitFormatCompare($value, $cond, $val, $dfcond, $dfval)
12+
/**
13+
* @param mixed $value
14+
* @param mixed $val
15+
* @param mixed $dfval
16+
*/
17+
private static function splitFormatCompare($value, ?string $cond, $val, string $dfcond, $dfval): bool
1118
{
1219
if (!$cond) {
1320
$cond = $dfcond;
@@ -33,7 +40,8 @@ private static function splitFormatCompare($value, $cond, $val, $dfcond, $dfval)
3340
return $value >= $val;
3441
}
3542

36-
private static function splitFormat($sections, $value)
43+
/** @param mixed $value */
44+
private static function splitFormat(array $sections, $value): array
3745
{
3846
// Extract the relevant section depending on whether number is positive, negative, or zero?
3947
// Text not supported yet.
@@ -93,23 +101,26 @@ private static function splitFormat($sections, $value)
93101
/**
94102
* Convert a value in a pre-defined format to a PHP string.
95103
*
96-
* @param mixed $value Value to format
104+
* @param null|bool|float|int|RichText|string $value Value to format
97105
* @param string $format Format code, see = NumberFormat::FORMAT_*
98106
* @param array $callBack Callback function for additional formatting of string
99107
*
100108
* @return string Formatted string
101109
*/
102110
public static function toFormattedString($value, $format, $callBack = null)
103111
{
112+
if (is_bool($value)) {
113+
return $value ? Calculation::getTRUE() : Calculation::getFALSE();
114+
}
104115
// For now we do not treat strings although section 4 of a format code affects strings
105116
if (!is_numeric($value)) {
106-
return $value;
117+
return (string) $value;
107118
}
108119

109120
// For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
110121
// it seems to round numbers to a total of 10 digits.
111122
if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) {
112-
return $value;
123+
return (string) $value;
113124
}
114125

115126
// Ignore square-$-brackets prefix in format string, like "[$-411]ge.m.d", "[$-010419]0%", etc
@@ -127,7 +138,7 @@ function ($matches) {
127138
$format = (string) preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format);
128139

129140
// Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
130-
$sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
141+
$sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format) ?: [];
131142

132143
[$colors, $format, $value] = self::splitFormat($sections, $value);
133144

@@ -145,8 +156,8 @@ function ($matches) {
145156
if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) {
146157
$value = substr($format, 1, -1);
147158
} elseif (preg_match('/[0#, ]%/', $format)) {
148-
// % number format
149-
$value = PercentageFormatter::format($value, $format);
159+
// % number format - avoid weird '-0' problem
160+
$value = PercentageFormatter::format(0 + (float) $value, $format);
150161
} else {
151162
$value = NumberFormatter::format($value, $format);
152163
}

tests/PhpSpreadsheetTests/Reader/Ods/HiddenMergeCellsTest.php

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,43 @@
33
namespace PhpOffice\PhpSpreadsheetTests\Reader\Ods;
44

55
use PhpOffice\PhpSpreadsheet\Reader\Ods;
6-
use PhpOffice\PhpSpreadsheet\Spreadsheet;
76
use PHPUnit\Framework\TestCase;
87

98
class HiddenMergeCellsTest extends TestCase
109
{
11-
/**
12-
* @var Spreadsheet
13-
*/
14-
private $spreadsheet;
15-
16-
protected function setup(): void
17-
{
18-
$filename = 'tests/data/Reader/Ods/HiddenMergeCellsTest.ods';
19-
$reader = new Ods();
20-
$this->spreadsheet = $reader->load($filename);
21-
}
10+
private const FILENAME = 'tests/data/Reader/Ods/HiddenMergeCellsTest.ods';
2211

2312
public function testHiddenMergeCells(): void
2413
{
25-
$c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
14+
$reader = new Ods();
15+
$spreadsheet = $reader->load(self::FILENAME);
16+
$c2InMergeRange = $spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
2617
self::assertTrue($c2InMergeRange);
27-
$a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
18+
$a2InMergeRange = $spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
2819
self::assertTrue($a2InMergeRange);
29-
$a2MergeRangeValue = $this->spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell();
20+
$a2MergeRangeValue = $spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell();
3021
self::assertTrue($a2MergeRangeValue);
3122

32-
$cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2');
23+
$cellArray = $spreadsheet->getActiveSheet()->rangeToArray('A2:C2');
24+
self::assertSame([['12', '4', '3']], $cellArray);
25+
$cellArray = $spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, true, false);
3326
self::assertSame([[12, 4, 3]], $cellArray);
27+
$spreadsheet->disconnectWorksheets();
3428
}
3529

3630
public function testUnmergeHiddenMergeCells(): void
3731
{
38-
$this->spreadsheet->getActiveSheet()->unmergeCells('A2:C2');
32+
$reader = new Ods();
33+
$spreadsheet = $reader->load(self::FILENAME);
34+
$spreadsheet->getActiveSheet()->unmergeCells('A2:C2');
3935

40-
$c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
36+
$c2InMergeRange = $spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
4137
self::assertFalse($c2InMergeRange);
42-
$a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
38+
$a2InMergeRange = $spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
4339
self::assertFalse($a2InMergeRange);
4440

45-
$cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false);
41+
$cellArray = $spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false);
4642
self::assertSame([[12, '=6-B1', '=A2/B2']], $cellArray);
43+
$spreadsheet->disconnectWorksheets();
4744
}
4845
}

tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,19 @@
33
namespace PhpOffice\PhpSpreadsheetTests\Reader\Ods;
44

55
use PhpOffice\PhpSpreadsheet\Reader\Ods;
6-
use PhpOffice\PhpSpreadsheet\Spreadsheet;
76
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
87
use PHPUnit\Framework\TestCase;
98

109
class HiddenWorksheetTest extends TestCase
1110
{
12-
/**
13-
* @var Spreadsheet
14-
*/
15-
private $spreadsheet;
16-
17-
protected function setup(): void
11+
public function testPageSetup(): void
1812
{
1913
$filename = 'tests/data/Reader/Ods/HiddenSheet.ods';
2014
$reader = new Ods();
21-
$this->spreadsheet = $reader->load($filename);
22-
}
23-
24-
public function testPageSetup(): void
25-
{
15+
$spreadsheet = $reader->load($filename);
2616
$assertions = $this->worksheetAssertions();
2717

28-
foreach ($this->spreadsheet->getAllSheets() as $worksheet) {
18+
foreach ($spreadsheet->getAllSheets() as $worksheet) {
2919
if (!array_key_exists($worksheet->getTitle(), $assertions)) {
3020
continue;
3121
}
@@ -40,6 +30,7 @@ public function testPageSetup(): void
4030
);
4131
}
4232
}
33+
$spreadsheet->disconnectWorksheets();
4334
}
4435

4536
private function worksheetAssertions(): array

tests/PhpSpreadsheetTests/Reader/Xls/HiddenMergeCellsTest.php

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,43 @@
33
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
44

55
use PhpOffice\PhpSpreadsheet\Reader\Xls;
6-
use PhpOffice\PhpSpreadsheet\Spreadsheet;
76
use PHPUnit\Framework\TestCase;
87

98
class HiddenMergeCellsTest extends TestCase
109
{
11-
/**
12-
* @var Spreadsheet
13-
*/
14-
private $spreadsheet;
15-
16-
protected function setup(): void
17-
{
18-
$filename = 'tests/data/Reader/XLS/HiddenMergeCellsTest.xls';
19-
$reader = new Xls();
20-
$this->spreadsheet = $reader->load($filename);
21-
}
10+
private const FILENAME = 'tests/data/Reader/XLS/HiddenMergeCellsTest.xls';
2211

2312
public function testHiddenMergeCells(): void
2413
{
25-
$c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
14+
$reader = new Xls();
15+
$spreadsheet = $reader->load(self::FILENAME);
16+
$c2InMergeRange = $spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
2617
self::assertTrue($c2InMergeRange);
27-
$a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
18+
$a2InMergeRange = $spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
2819
self::assertTrue($a2InMergeRange);
29-
$a2MergeRangeValue = $this->spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell();
20+
$a2MergeRangeValue = $spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell();
3021
self::assertTrue($a2MergeRangeValue);
3122

32-
$cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2');
23+
$cellArray = $spreadsheet->getActiveSheet()->rangeToArray('A2:C2');
24+
self::assertSame([['12', '4', '3']], $cellArray);
25+
$cellArray = $spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, true, false);
3326
self::assertSame([[12, 4, 3]], $cellArray);
27+
$spreadsheet->disconnectWorksheets();
3428
}
3529

3630
public function testUnmergeHiddenMergeCells(): void
3731
{
38-
$this->spreadsheet->getActiveSheet()->unmergeCells('A2:C2');
32+
$reader = new Xls();
33+
$spreadsheet = $reader->load(self::FILENAME);
34+
$spreadsheet->getActiveSheet()->unmergeCells('A2:C2');
3935

40-
$c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
36+
$c2InMergeRange = $spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange();
4137
self::assertFalse($c2InMergeRange);
42-
$a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
38+
$a2InMergeRange = $spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange();
4339
self::assertFalse($a2InMergeRange);
4440

45-
$cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false);
41+
$cellArray = $spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false);
4642
self::assertSame([[12, '=6-B1', '=A2/B2']], $cellArray);
43+
$spreadsheet->disconnectWorksheets();
4744
}
4845
}

tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,19 @@
33
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
44

55
use PhpOffice\PhpSpreadsheet\Reader\Xls;
6-
use PhpOffice\PhpSpreadsheet\Spreadsheet;
76
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
87
use PHPUnit\Framework\TestCase;
98

109
class HiddenWorksheetTest extends TestCase
1110
{
12-
/**
13-
* @var Spreadsheet
14-
*/
15-
private $spreadsheet;
16-
17-
protected function setup(): void
11+
public function testPageSetup(): void
1812
{
1913
$filename = 'tests/data/Reader/XLS/HiddenSheet.xls';
2014
$reader = new Xls();
21-
$this->spreadsheet = $reader->load($filename);
22-
}
23-
24-
public function testPageSetup(): void
25-
{
15+
$spreadsheet = $reader->load($filename);
2616
$assertions = $this->worksheetAssertions();
2717

28-
foreach ($this->spreadsheet->getAllSheets() as $worksheet) {
18+
foreach ($spreadsheet->getAllSheets() as $worksheet) {
2919
if (!array_key_exists($worksheet->getTitle(), $assertions)) {
3020
continue;
3121
}
@@ -40,6 +30,7 @@ public function testPageSetup(): void
4030
);
4131
}
4232
}
33+
$spreadsheet->disconnectWorksheets();
4334
}
4435

4536
private function worksheetAssertions(): array

tests/PhpSpreadsheetTests/Reader/Xls/NumberFormatGeneralTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function testGeneral(): void
2121
self::assertSame(2.95, $sheet->getCell('D2')->getValue());
2222
self::assertSame(2.95, $sheet->getCell('D2')->getCalculatedValue());
2323
self::assertSame('€ 2.95', $sheet->getCell('D2')->getFormattedValue());
24-
self::assertSame(21, $array[1][4]);
24+
self::assertSame('21', $array[1][4]);
2525
self::assertSame(21, $sheet->getCell('E2')->getValue());
2626
self::assertSame(21, $sheet->getCell('E2')->getCalculatedValue());
2727
self::assertSame('21', $sheet->getCell('E2')->getFormattedValue());

0 commit comments

Comments
 (0)