add force_full_scale option to handle all cases

This commit is contained in:
Thomas Calvet 2018-03-13 16:10:41 +01:00 committed by Bernhard Schussek
parent 40f25121c3
commit fb2b37a8f3
4 changed files with 205 additions and 36 deletions

View File

@ -0,0 +1,91 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class NumberToStringTransformer implements DataTransformerInterface
{
/**
* @var bool
*/
private $forceFullScale;
/**
* @var int|null
*/
private $scale;
/**
* @param bool $forceFullScale
* @param int|null $scale
*/
public function __construct($forceFullScale = false, $scale = null)
{
$this->forceFullScale = $forceFullScale;
$this->scale = $scale;
}
/**
* @param mixed $value
*
* @return string|null
*/
public function transform($value)
{
if (null === $value) {
return null;
}
if (!is_string($value)) {
throw new TransformationFailedException('Expected a string.');
}
return $value;
}
/**
* @param mixed $value
*
* @return string|null
*/
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
if (is_string($value)) {
return $value;
}
$valueIsInt = is_int($value);
if (!$valueIsInt && !is_float($value)) {
throw new TransformationFailedException('Expected an int or a float.');
}
if ($this->forceFullScale && is_int($this->scale)) {
if ($valueIsInt) {
$value = floatval($value);
}
return number_format($value, $this->scale, '.', '');
}
try {
return (string) $value;
} catch (\Exception $e) {
throw new TransformationFailedException();
}
}
}

View File

@ -11,11 +11,11 @@
namespace Symfony\Bridge\Doctrine\Form\Type;
use Symfony\Bridge\Doctrine\Form\DataTransformer\NumberToStringTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DecimalType extends AbstractType
{
@ -24,27 +24,20 @@ class DecimalType extends AbstractType
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new CallbackTransformer(function ($value) {
if (null === $value) {
return null;
}
$builder->addModelTransformer(new NumberToStringTransformer($options['force_full_scale'], $options['scale']));
}
if (!is_string($value)) {
throw new TransformationFailedException('Expected a string.');
}
return $value;
}, function ($value) {
if (null === $value) {
return null;
}
if (!is_int($value) && !is_float($value)) {
throw new TransformationFailedException('Expected an int or a float.');
}
return (string) $value;
}));
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'force_full_scale' => false
));
$resolver->setAllowedTypes('force_full_scale', array(
'boolean'
));
}
/**

View File

@ -21,8 +21,11 @@ class Price
/** @Id @Column(type="integer") */
public $id;
/** @Column(type="decimal") */
public $value;
/** @Column(type="decimal", scale=2) */
public $doesNotPreserveFullScaleValue;
/** @Column(type="string") */
public $preserveFullScaleValueSimulation;
/**
* @param int $id
@ -31,6 +34,7 @@ class Price
public function __construct(int $id, float $value)
{
$this->id = $id;
$this->value = $value;
$this->doesNotPreserveFullScaleValue = $value;
$this->preserveFullScaleValueSimulation = number_format($value, 2, '.', '');
}
}

View File

@ -60,33 +60,114 @@ class DecimalTypeTest extends BaseTypeTest
$this->em = null;
}
public function testSubmitWithSameStringValue()
// On some platforms, fetched decimal values are rounded (the full scale is not preserved)
// eg : on SQLite, inserted float value 4.50 will be fetched as string value "4.5"
public function testSubmitWithSameStringValueOnAPlatformThatDoesNotPreserveFullScaleValueWithoutForceFullScale()
{
$price = new Price(1, 1.23);
$this->em->persist($price);
$fullScalePrice = new Price(1, 1.23);
$nonFullScalePrice = new Price(2, 4.50);
$this->em->persist($fullScalePrice);
$this->em->persist($nonFullScalePrice);
$this->em->flush();
$this->em->refresh($price);
$this->em->refresh($fullScalePrice);
$this->em->refresh($nonFullScalePrice);
$this->assertInternalType('string', $price->value);
$stringValue = $price->value;
$this->assertInternalType('string', $fullScalePrice->doesNotPreserveFullScaleValue);
$fullScalePriceStringValue = $fullScalePrice->doesNotPreserveFullScaleValue;
$formBuilder = $this->factory->createBuilder(FormType::class, $price, array(
$formBuilder = $this->factory->createBuilder(FormType::class, $fullScalePrice, array(
'data_class' => Price::class
));
$formBuilder->add('value', static::TESTED_TYPE);
$formBuilder->add('doesNotPreserveFullScaleValue', static::TESTED_TYPE, array(
'force_full_scale' => false
));
$form = $formBuilder->getForm();
$form->submit(array(
'value' => $stringValue
'doesNotPreserveFullScaleValue' => $fullScalePriceStringValue
));
$this->assertSame($stringValue, $price->value);
$this->assertSame($fullScalePriceStringValue, $fullScalePrice->doesNotPreserveFullScaleValue);
$this->assertInternalType('string', $nonFullScalePrice->doesNotPreserveFullScaleValue);
$nonFullScalePriceStringValue = $nonFullScalePrice->doesNotPreserveFullScaleValue;
$formBuilder = $this->factory->createBuilder(FormType::class, $nonFullScalePrice, array(
'data_class' => Price::class
));
$formBuilder->add('doesNotPreserveFullScaleValue', static::TESTED_TYPE, array(
'force_full_scale' => false
));
$form = $formBuilder->getForm();
$form->submit(array(
'doesNotPreserveFullScaleValue' => $nonFullScalePriceStringValue
));
$this->assertSame($nonFullScalePriceStringValue, $nonFullScalePrice->doesNotPreserveFullScaleValue);
$unitOfWork = $this->em->getUnitOfWork();
$unitOfWork->computeChangeSets();
$this->assertSame(array(), $unitOfWork->getEntityChangeSet($price));
$this->assertSame(array(), $unitOfWork->getEntityChangeSet($fullScalePrice));
$this->assertSame(array(), $unitOfWork->getEntityChangeSet($nonFullScalePrice));
}
// On some platforms, fetched decimal values are not rounded at all (the full scale is preserved)
// eg : on PostgreSQL, inserted float value 4.50 will be fetched as string value "4.50"
public function testSubmitWithSameStringValueOnAPlatformThatPreserveFullScaleValueWithForceFullScale()
{
$fullScalePrice = new Price(1, 1.23);
$nonFullScalePrice = new Price(2, 4.50);
$this->em->persist($fullScalePrice);
$this->em->persist($nonFullScalePrice);
$this->em->flush();
$this->em->refresh($fullScalePrice);
$this->em->refresh($nonFullScalePrice);
$this->assertInternalType('string', $fullScalePrice->preserveFullScaleValueSimulation);
$fullScalePriceStringValue = $fullScalePrice->preserveFullScaleValueSimulation;
$formBuilder = $this->factory->createBuilder(FormType::class, $fullScalePrice, array(
'data_class' => Price::class
));
$formBuilder->add('preserveFullScaleValueSimulation', static::TESTED_TYPE, array(
'force_full_scale' => true,
'scale' => 2
));
$form = $formBuilder->getForm();
$form->submit(array(
'preserveFullScaleValueSimulation' => $fullScalePriceStringValue
));
$this->assertSame($fullScalePriceStringValue, $fullScalePrice->preserveFullScaleValueSimulation);
$this->assertInternalType('string', $nonFullScalePrice->preserveFullScaleValueSimulation);
$nonFullScalePriceStringValue = $nonFullScalePrice->preserveFullScaleValueSimulation;
$formBuilder = $this->factory->createBuilder(FormType::class, $nonFullScalePrice, array(
'data_class' => Price::class
));
$formBuilder->add('preserveFullScaleValueSimulation', static::TESTED_TYPE, array(
'force_full_scale' => true,
'scale' => 2
));
$form = $formBuilder->getForm();
$form->submit(array(
'preserveFullScaleValueSimulation' => $nonFullScalePriceStringValue
));
$this->assertSame($nonFullScalePriceStringValue, $nonFullScalePrice->preserveFullScaleValueSimulation);
$unitOfWork = $this->em->getUnitOfWork();
$unitOfWork->computeChangeSets();
$this->assertSame(array(), $unitOfWork->getEntityChangeSet($fullScalePrice));
$this->assertSame(array(), $unitOfWork->getEntityChangeSet($nonFullScalePrice));
}
public function testSubmitNull($expected = null, $norm = null, $view = null)