Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to documentation concerning Value Binders #2138

Merged
merged 12 commits into from
Jun 3, 2021
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Added

- Nothing.
- More flexibility in the StringValueBinder to determine what datatypes should be treated as strings [PR #2138](https://github.com/PHPOffice/PhpSpreadsheet/pull/2138)

### Changed

Expand Down
36 changes: 31 additions & 5 deletions docs/topics/accessing-cells.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ values beginning with `=` will be converted to a formula. Strings that
aren't numeric, or that don't begin with a leading `=` will be treated
as genuine string values.

Note that a numeric string that begins with a leading zero (that isn't
immediately followed by a decimal separator) will not be converted to a
numeric, so values like phone numbers (e.g. `01615991375``will remain as
strings).

This "conversion" is handled by a cell "value binder", and you can write
custom "value binders" to change the behaviour of these "conversions".
The standard PhpSpreadsheet package also provides an "advanced value
Expand Down Expand Up @@ -138,8 +143,10 @@ Formats handled by the advanced value binder include:
- When strings contain a newline character (`\n`), then the cell styling is
set to wrap.

You can read more about value binders later in this section of the
documentation.
Basically, it attempts to mimic the behaviour of the MS Excel GUI.

You can read more about value binders [later in this section of the
documentation](#using-value-binders-to-facilitate-data-entry).

### Setting a formula in a Cell

Expand Down Expand Up @@ -551,8 +558,27 @@ $spreadsheet->getActiveSheet()->setCellValue('A5', 'Date/time value:');
$spreadsheet->getActiveSheet()->setCellValue('B5', '21 December 1983');
```

**Creating your own value binder is easy.** When advanced value binding
is required, you can implement the
`\PhpOffice\PhpSpreadsheet\Cell\IValueBinder` interface or extend the
Alternatively, a `\PhpOffice\PhpSpreadsheet\Cell\StringValueBinder` class is available
if you want to preserve all content as strings. This might be appropriate if you
were loading a file containing values that could be interpreted as numbers (e.g. numbers
with leading sign such as international phone numbers like `+441615579382`), but that
should be retained as strings (non-international phone numbers with leading zeroes are
already maintained as strings).

By default, the StringValueBinder will cast any datatype passed to it into a string. However, there are a number of settings which allow you to specify that certain datatypes shouldn't be cast to strings, but left "as is":

```php
// Set value binder
$stringValueBinder = new \PhpOffice\PhpSpreadsheet\Cell\StringValueBinder();
$stringValueBinder->setNumericConversion(false)
->setBooleanConversion(false)
->setNullConversion(false)
->setFormulaConversion(false);
\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( $stringValueBinder );
```

**Creating your own value binder is relatively straightforward.** When more specialised
value binding is required, you can implement the
`\PhpOffice\PhpSpreadsheet\Cell\IValueBinder` interface or extend the existing
`\PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder` or
`\PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder` classes.
5 changes: 3 additions & 2 deletions src/PhpSpreadsheet/Cell/DefaultValueBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function bindValue(Cell $cell, $value)
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
} elseif (!($value instanceof RichText)) {
// Attempt to cast any unexpected objects to string
$value = (string) $value;
}
}
Expand Down Expand Up @@ -57,11 +58,11 @@ public static function dataTypeForValue($value)
return DataType::TYPE_STRING;
} elseif ($value instanceof RichText) {
return DataType::TYPE_INLINE;
} elseif (is_string($value) && $value[0] === '=' && strlen($value) > 1) {
} elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=') {
return DataType::TYPE_FORMULA;
} elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) {
$tValue = ltrim($value, '+-');
if (is_string($value) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') {
if (is_string($value) && strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') {
return DataType::TYPE_STRING;
} elseif ((strpos($value, '.') === false) && ($value > PHP_INT_MAX)) {
return DataType::TYPE_STRING;
Expand Down
96 changes: 93 additions & 3 deletions src/PhpSpreadsheet/Cell/StringValueBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,118 @@

namespace PhpOffice\PhpSpreadsheet\Cell;

use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;

class StringValueBinder implements IValueBinder
{
/**
* @var bool
*/
protected $convertNull = true;

/**
* @var bool
*/
protected $convertBoolean = true;

/**
* @var bool
*/
protected $convertNumeric = true;

/**
* @var bool
*/
protected $convertFormula = true;

public function setNullConversion(bool $suppressConversion = false): self
{
$this->convertNull = $suppressConversion;

return $this;
}

public function setBooleanConversion(bool $suppressConversion = false): self
{
$this->convertBoolean = $suppressConversion;

return $this;
}

public function setNumericConversion(bool $suppressConversion = false): self
{
$this->convertNumeric = $suppressConversion;

return $this;
}

public function setFormulaConversion(bool $suppressConversion = false): self
{
$this->convertFormula = $suppressConversion;

return $this;
}

public function setConversionForAllValueTypes(bool $suppressConversion = false): self
{
$this->convertNull = $suppressConversion;
$this->convertBoolean = $suppressConversion;
$this->convertNumeric = $suppressConversion;
$this->convertFormula = $suppressConversion;

return $this;
}

/**
* Bind value to a cell.
*
* @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell
*
* @return bool
*/
public function bindValue(Cell $cell, $value)
{
if (is_object($value)) {
return $this->bindObjectValue($cell, $value);
}

// sanitize UTF-8 strings
if (is_string($value)) {
$value = StringHelper::sanitizeUTF8($value);
}

if ($value === null && $this->convertNull === false) {
$cell->setValueExplicit($value, DataType::TYPE_NULL);
} elseif (is_bool($value) && $this->convertBoolean === false) {
$cell->setValueExplicit($value, DataType::TYPE_BOOL);
} elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) {
$cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
} elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false) {
$cell->setValueExplicit($value, DataType::TYPE_FORMULA);
} else {
if (is_string($value) && strlen($value) > 1 && $value[0] === '=') {
$cell->getStyle()->setQuotePrefix(true);
}
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
}

return true;
}

protected function bindObjectValue(Cell $cell, object $value): bool
{
// Handle any objects that might be injected
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
} elseif ($value instanceof RichText) {
$cell->setValueExplicit($value, DataType::TYPE_INLINE);

return true;
}

$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);

// Done!
return true;
}
}
19 changes: 19 additions & 0 deletions tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class AdvancedValueBinderTest extends TestCase
Expand Down Expand Up @@ -42,6 +43,23 @@ protected function tearDown(): void
StringHelper::setThousandsSeparator($this->thousandsSeparator);
}

public function testNullValue(): void
{
/** @var Cell&MockObject $cellStub */
$cellStub = $this->getMockBuilder(Cell::class)
->disableOriginalConstructor()
->getMock();

// Configure the stub.
$cellStub->expects(self::once())
->method('setValueExplicit')
->with(null, DataType::TYPE_NULL)
->willReturn(true);

$binder = new AdvancedValueBinder();
$binder->bindValue($cellStub, null);
}

/**
* @dataProvider currencyProvider
*
Expand Down Expand Up @@ -105,6 +123,7 @@ public function currencyProvider(): array
['€2.020,20', 2020.2, $currencyEURO, '.', ',', '€'],
['€ 2.020,20', 2020.2, $currencyEURO, '.', ',', '€'],
['€2,020.22', 2020.22, $currencyEURO, ',', '.', '€'],
['$10.11', 10.11, $currencyUSD, ',', '.', '€'],
];
}

Expand Down
Loading