silverstripe-framework/src/Forms/NumericField.php

320 lines
7.8 KiB
PHP
Raw Normal View History

<?php
namespace SilverStripe\Forms;
use NumberFormatter;
use SilverStripe\i18n\i18n;
/**
* Text input field with validation for numeric values. Supports validating
* the numeric value as to the {@link i18n::get_locale()} value, or an
* overridden locale specific to this field.
*/
2016-11-29 00:31:16 +01:00
class NumericField extends TextField
{
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DECIMAL;
/**
* Used to determine if the number given is in the correct format when validating
*
* @var mixed
*/
protected $originalValue = null;
2016-11-29 00:31:16 +01:00
/**
* Override locale for this field.
*
* @var string
*/
protected $locale = null;
/**
* Use HTML5 number input type.
* Note that enabling html5 disables certain localisation features.
2016-11-29 00:31:16 +01:00
*
* @var bool
*/
protected $html5 = false;
/**
* Number of decimal places allowed, if bound.
* Null means unbound.
* Defaults to 0, which is integer value.
*
* @var string
*/
protected $scale = 0;
/**
* Get number formatter for localising this field
2016-11-29 00:31:16 +01:00
*
* @return NumberFormatter
2016-11-29 00:31:16 +01:00
*/
protected function getFormatter()
2016-11-29 00:31:16 +01:00
{
if ($this->getHTML5()) {
// Locale-independent html5 number formatter
2017-02-22 04:14:53 +01:00
$formatter = NumberFormatter::create(
i18n::config()->uninherited('default_locale'),
NumberFormatter::DECIMAL
);
$formatter->setAttribute(NumberFormatter::GROUPING_USED, false);
$formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, '.');
} else {
// Locale-specific number formatter
$formatter = NumberFormatter::create($this->getLocale(), NumberFormatter::DECIMAL);
}
2016-11-29 00:31:16 +01:00
// Set decimal precision
$scale = $this->getScale();
if ($scale === 0) {
$formatter->setAttribute(NumberFormatter::DECIMAL_ALWAYS_SHOWN, false);
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
} else {
$formatter->setAttribute(NumberFormatter::DECIMAL_ALWAYS_SHOWN, true);
if ($scale === null) {
// At least one digit to distinguish floating point from integer
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 1);
} else {
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $scale);
}
}
return $formatter;
}
2016-11-29 00:31:16 +01:00
/**
* Get type argument for parse / format calls. one of TYPE_INT32, TYPE_INT64 or TYPE_DOUBLE
*
* @return int
*/
protected function getNumberType()
{
$scale = $this->getScale();
if ($scale === 0) {
return PHP_INT_SIZE > 4
? NumberFormatter::TYPE_INT64
: NumberFormatter::TYPE_INT32;
}
return NumberFormatter::TYPE_DOUBLE;
}
2016-11-29 00:31:16 +01:00
public function setSubmittedValue($value, $data = null)
{
// Save original value in case parse fails
$value = trim($value);
$this->originalValue = $value;
// Empty string is no-number (not 0)
if (strlen($value) === 0) {
$this->value = null;
return $this;
2016-11-29 00:31:16 +01:00
}
// Format number
$formatter = $this->getFormatter();
$parsed = 0;
$this->value = $formatter->parse($value, $this->getNumberType(), $parsed); // Note: may store literal `false` for invalid values
// Ensure that entire string is parsed
if ($parsed < strlen($value)) {
$this->value = false;
}
2016-11-29 00:31:16 +01:00
return $this;
}
/**
* Format value for output
2016-11-29 00:31:16 +01:00
*
* @return string
*/
public function Value()
2016-11-29 00:31:16 +01:00
{
// Show invalid value back to user in case of error
if ($this->value === false) {
return $this->originalValue;
}
$formatter = $this->getFormatter();
return $formatter->format($this->value, $this->getNumberType());
}
2016-11-29 00:31:16 +01:00
public function setValue($value, $data = null)
{
$this->originalValue = $value;
$this->value = $this->cast($value);
return $this;
2016-11-29 00:31:16 +01:00
}
/**
* Helper to cast non-localised strings to their native type
2016-11-29 00:31:16 +01:00
*
* @param string $value
* @return float|int
2016-11-29 00:31:16 +01:00
*/
protected function cast($value)
2016-11-29 00:31:16 +01:00
{
if ($this->getScale() === 0) {
return (int)$value;
}
return (float)$value;
2016-11-29 00:31:16 +01:00
}
/**
* {@inheritdoc}
*/
public function Type()
{
return 'numeric text';
}
public function getAttributes()
{
$attributes = parent::getAttributes();
if ($this->getHTML5()) {
$attributes['type'] = 'number';
$attributes['step'] = $this->getStep();
}
return $attributes;
}
2016-11-29 00:31:16 +01:00
/**
* Validate this field
*
* @param Validator $validator
* @return bool
*/
public function validate($validator)
{
// false signifies invalid value due to failed parse()
if ($this->value !== false) {
2016-11-29 00:31:16 +01:00
return true;
}
$validator->validationError(
$this->name,
_t(
2017-04-20 03:15:24 +02:00
'SilverStripe\\Forms\\NumericField.VALIDATION',
2016-11-29 00:31:16 +01:00
"'{value}' is not a number, only numbers can be accepted for this field",
['value' => $this->originalValue]
)
2016-11-29 00:31:16 +01:00
);
return false;
}
public function getSchemaValidation()
{
$rules = parent::getSchemaValidation();
$rules['numeric'] = true;
return $rules;
}
/**
* Get internal database value
2016-11-29 00:31:16 +01:00
*
* @return int|float
2016-11-29 00:31:16 +01:00
*/
public function dataValue()
{
return $this->cast($this->value);
}
2016-11-29 00:31:16 +01:00
/**
* Gets the current locale this field is set to.
*
* @return string
*/
public function getLocale()
{
if ($this->locale) {
return $this->locale;
2016-11-29 00:31:16 +01:00
}
return i18n::get_locale();
}
2016-11-29 00:31:16 +01:00
/**
* Override the locale for this field.
*
* @param string $locale
*
* @return $this
*/
public function setLocale($locale)
{
$this->locale = $locale;
2016-11-29 00:31:16 +01:00
return $this;
2016-11-29 00:31:16 +01:00
}
/**
* Determine if we should use html5 number input
2016-11-29 00:31:16 +01:00
*
* @return bool
2016-11-29 00:31:16 +01:00
*/
public function getHTML5()
2016-11-29 00:31:16 +01:00
{
return $this->html5;
}
2016-11-29 00:31:16 +01:00
/**
* Set whether this field should use html5 number input type.
* Note: If setting to true this will disable all number localisation.
*
* @param bool $html5
* @return $this
*/
public function setHTML5($html5)
{
$this->html5 = $html5;
return $this;
2016-11-29 00:31:16 +01:00
}
/**
* Step attribute for html5. E.g. '0.01' to enable two decimal places.
* Ignored if html5 isn't enabled.
2016-11-29 00:31:16 +01:00
*
* @return string
*/
public function getStep()
2016-11-29 00:31:16 +01:00
{
$scale = $this->getScale();
if ($scale === null) {
return 'any';
2016-11-29 00:31:16 +01:00
}
if ($scale === 0) {
return '1';
}
return '0.'.str_repeat('0', $scale - 1).'1';
2016-11-29 00:31:16 +01:00
}
/**
* Get number of digits to show to the right of the decimal point.
* 0 for integer, any number for floating point, or null to flexible
2016-11-29 00:31:16 +01:00
*
* @return int|null
*/
public function getScale()
{
return $this->scale;
}
/**
* Get number of digits to show to the right of the decimal point.
* 0 for integer, any number for floating point, or null to flexible
2016-11-29 00:31:16 +01:00
*
* @param int|null $scale
2016-11-29 00:31:16 +01:00
* @return $this
*/
public function setScale($scale)
2016-11-29 00:31:16 +01:00
{
$this->scale = $scale;
2016-11-29 00:31:16 +01:00
return $this;
}
public function performReadonlyTransformation()
{
$field = clone $this;
$field->setReadonly(true);
return $field;
}
}