Skip to content

Commit e27ccc8

Browse files
committed
Better Handling of Stringable Objects
1 parent 0152f57 commit e27ccc8

File tree

3 files changed

+50
-9
lines changed

3 files changed

+50
-9
lines changed

src/PhpSpreadsheet/Cell/StringValueBinder.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public function bindValue(Cell $cell, mixed $value): bool
9090
$value = StringHelper::sanitizeUTF8($value);
9191
}
9292

93+
$ignoredErrors = false;
9394
if ($value === null && $this->convertNull === false) {
9495
$cell->setValueExplicit($value, DataType::TYPE_NULL);
9596
} elseif (is_bool($value) && $this->convertBoolean === false) {
@@ -99,28 +100,35 @@ public function bindValue(Cell $cell, mixed $value): bool
99100
} elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false && parent::dataTypeForValue($value) === DataType::TYPE_FORMULA) {
100101
$cell->setValueExplicit($value, DataType::TYPE_FORMULA);
101102
} else {
102-
if ($this->setIgnoredErrors && is_numeric($value)) {
103-
$cell->getIgnoredErrors()->setNumberStoredAsText(true);
104-
}
103+
$ignoredErrors = is_numeric($value);
105104
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
106105
}
106+
if ($this->setIgnoredErrors) {
107+
$cell->getIgnoredErrors()->setNumberStoredAsText($ignoredErrors);
108+
}
107109

108110
return true;
109111
}
110112

111113
protected function bindObjectValue(Cell $cell, object $value): bool
112114
{
113115
// Handle any objects that might be injected
116+
$ignoredErrors = false;
114117
if ($value instanceof DateTimeInterface) {
115118
$value = $value->format('Y-m-d H:i:s');
116119
$cell->setValueExplicit($value, DataType::TYPE_STRING);
117120
} elseif ($value instanceof RichText) {
118121
$cell->setValueExplicit($value, DataType::TYPE_INLINE);
122+
$ignoredErrors = is_numeric($value->getPlainText());
119123
} elseif ($value instanceof Stringable) {
120124
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
125+
$ignoredErrors = is_numeric((string) $value);
121126
} else {
122127
throw new SpreadsheetException('Unable to bind unstringable object of type ' . get_class($value));
123128
}
129+
if ($this->setIgnoredErrors) {
130+
$cell->getIgnoredErrors()->setNumberStoredAsText($ignoredErrors);
131+
}
124132

125133
return true;
126134
}

tests/PhpSpreadsheetTests/Cell/StringValueBinder2Test.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Cell;
66

7+
use DateTime;
78
use PhpOffice\PhpSpreadsheet\Cell\Cell;
89
use PhpOffice\PhpSpreadsheet\Cell\DataType;
910
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
1011
use PhpOffice\PhpSpreadsheet\Cell\StringValueBinder;
12+
use PhpOffice\PhpSpreadsheet\RichText\RichText;
1113
use PhpOffice\PhpSpreadsheet\Spreadsheet;
1214
use PHPUnit\Framework\TestCase;
1315

@@ -31,15 +33,24 @@ public function testStringValueBinderIgnoredErrorsDefault(): void
3133
Cell::setValueBinder($valueBinder);
3234
$spreadsheet = new Spreadsheet();
3335
$sheet = $spreadsheet->getActiveSheet();
36+
$richText = new RichText();
37+
$richText->createTextRun('6');
38+
$richText2 = new RichText();
39+
$richText2->createTextRun('a');
3440
$sheet->fromArray([
3541
[1, 'x', 3.2],
3642
['y', -5, 'z'],
43+
[new DateTime(), $richText, $richText2],
44+
[new StringableObject('a'), new StringableObject(2), 'z'],
3745
]);
3846
$ignoredCells = [];
3947
foreach ($sheet->getRowIterator() as $row) {
4048
foreach ($row->getCellIterator() as $cell) {
4149
$coordinate = $cell->getCoordinate();
42-
self::assertSame(DataType::TYPE_STRING, $cell->getDataType(), "not string for cell $coordinate");
50+
$dataType = $cell->getDataType();
51+
if ($dataType !== DataType::TYPE_INLINE) {
52+
self::assertSame(DataType::TYPE_STRING, $dataType, "not string for cell $coordinate");
53+
}
4354
if ($cell->getIgnoredErrors()->getNumberStoredAsText()) {
4455
$ignoredCells[] = $coordinate;
4556
}
@@ -56,21 +67,30 @@ public function testStringValueBinderIgnoredErrorsTrue(): void
5667
Cell::setValueBinder($valueBinder);
5768
$spreadsheet = new Spreadsheet();
5869
$sheet = $spreadsheet->getActiveSheet();
70+
$richText = new RichText();
71+
$richText->createTextRun('6');
72+
$richText2 = new RichText();
73+
$richText2->createTextRun('a');
5974
$sheet->fromArray([
6075
[1, 'x', 3.2],
6176
['y', -5, 'z'],
77+
[new DateTime(), $richText, $richText2],
78+
[new StringableObject('a'), new StringableObject(2), 'z'],
6279
]);
6380
$ignoredCells = [];
6481
foreach ($sheet->getRowIterator() as $row) {
6582
foreach ($row->getCellIterator() as $cell) {
6683
$coordinate = $cell->getCoordinate();
67-
self::assertSame(DataType::TYPE_STRING, $cell->getDataType(), "not string for cell $coordinate");
84+
$dataType = $cell->getDataType();
85+
if ($dataType !== DataType::TYPE_INLINE) {
86+
self::assertSame(DataType::TYPE_STRING, $dataType, "not string for cell $coordinate");
87+
}
6888
if ($cell->getIgnoredErrors()->getNumberStoredAsText()) {
6989
$ignoredCells[] = $coordinate;
7090
}
7191
}
7292
}
73-
self::assertSame(['A1', 'C1', 'B2'], $ignoredCells);
93+
self::assertSame(['A1', 'C1', 'B2', 'B3', 'B4'], $ignoredCells);
7494
$spreadsheet->disconnectWorksheets();
7595
}
7696

@@ -82,22 +102,28 @@ public function testStringValueBinderPreserveNumeric(): void
82102
Cell::setValueBinder($valueBinder);
83103
$spreadsheet = new Spreadsheet();
84104
$sheet = $spreadsheet->getActiveSheet();
105+
$richText = new RichText();
106+
$richText->createTextRun('6');
107+
$richText2 = new RichText();
108+
$richText2->createTextRun('a');
85109
$sheet->fromArray([
86110
[1, 'x', 3.2],
87111
['y', -5, 'z'],
112+
[new DateTime(), $richText, $richText2],
113+
[new StringableObject('a'), new StringableObject(2), 'z'],
88114
]);
89115
$ignoredCells = [];
90116
foreach ($sheet->getRowIterator() as $row) {
91117
foreach ($row->getCellIterator() as $cell) {
92118
$coordinate = $cell->getCoordinate();
93-
$expected = is_numeric($cell->getValue()) ? DataType::TYPE_NUMERIC : DataType::TYPE_STRING;
119+
$expected = (is_int($cell->getValue()) || is_float($cell->getValue())) ? DataType::TYPE_NUMERIC : (($cell->getValue() instanceof RichText) ? DataType::TYPE_INLINE : DataType::TYPE_STRING);
94120
self::assertSame($expected, $cell->getDataType(), "wrong type for cell $coordinate");
95121
if ($cell->getIgnoredErrors()->getNumberStoredAsText()) {
96122
$ignoredCells[] = $coordinate;
97123
}
98124
}
99125
}
100-
self::assertSame([], $ignoredCells);
126+
self::assertSame(['B3', 'B4'], $ignoredCells);
101127
$spreadsheet->disconnectWorksheets();
102128
}
103129
}

tests/PhpSpreadsheetTests/Cell/StringableObject.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66

77
class StringableObject
88
{
9+
private int|string $value;
10+
11+
public function __construct(int|string $value = 'abc')
12+
{
13+
$this->value = $value;
14+
}
15+
916
public function __toString(): string
1017
{
11-
return 'abc';
18+
return (string) $this->value;
1219
}
1320
}

0 commit comments

Comments
 (0)