Browse Source

Merge pull request #5880 from CauanCabral/3.0-FloatI18n

Make FloatType locale aware.

This enables localized floats to be marshalled into PHP native floats.
Mark Story 11 years ago
parent
commit
9bae44e765

+ 55 - 0
src/Database/Type/FloatType.php

@@ -26,6 +26,21 @@ class FloatType extends \Cake\Database\Type
 {
 
     /**
+     * The class to use for representing number objects
+     *
+     * @var string
+     */
+    public static $numberClass = 'Cake\I18n\Number';
+
+    /**
+     * Whether numbers should be parsed using a locale aware parser
+     * when marshalling string inputs.
+     *
+     * @var bool
+     */
+    protected $_useLocaleParser = false;
+
+    /**
      * Convert integer data into the database format.
      *
      * @param string|resource $value The value to convert.
@@ -81,7 +96,47 @@ class FloatType extends \Cake\Database\Type
         }
         if (is_numeric($value)) {
             return (float)$value;
+        } elseif (is_string($value) && $this->_useLocaleParser) {
+            return $this->_parseValue($value);
         }
+
         return $value;
     }
+
+    /**
+     * Sets whether or not to parse numbers passed to the marshal() function
+     * by using a locale aware parser.
+     *
+     * @param bool $enable Whether or not to enable
+     * @return $this
+     */
+    public function useLocaleParser($enable = true)
+    {
+        if ($enable === false) {
+            $this->_useLocaleParser = $enable;
+            return $this;
+        }
+        if (static::$numberClass === 'Cake\I18n\Number' ||
+            is_subclass_of(static::$numberClass, 'Cake\I18n\Number')
+        ) {
+            $this->_useLocaleParser = $enable;
+            return $this;
+        }
+        throw new RuntimeException(
+            sprintf('Cannot use locale parsing with the %s class', static::$numberClass)
+        );
+    }
+
+    /**
+     * Converts a string into a float point after parseing it using the locale
+     * aware parser.
+     *
+     * @param string $value The value to parse and convert to an float.
+     * @return float
+     */
+    protected function _parseValue($value)
+    {
+        $class = static::$numberClass;
+        return $class::parseFloat($value);
+    }
 }

+ 19 - 0
src/I18n/Number.php

@@ -129,6 +129,25 @@ class Number
     }
 
     /**
+     * Parse a localized numeric string and transform it in a float point
+     *
+     * Options:
+     *
+     * - `locale` - The locale name to use for parsing the number, e.g. fr_FR
+     * - `type` - The formatter type to construct, set it to `currency` if you need to parse
+     *    numbers representing money.
+     *
+     * @param string $value A numeric string.
+     * @param array $options An array with options.
+     * @return float point number
+     */
+    public static function parseFloat($value, array $options = [])
+    {
+        $formatter = static::formatter($options);
+        return (float)$formatter->parse($value, NumberFormatter::TYPE_DOUBLE);
+    }
+
+    /**
      * Formats a number into the correct locale format to show deltas (signed differences in value).
      *
      * ### Options

+ 41 - 0
tests/TestCase/Database/Type/FloatTypeTest.php

@@ -16,6 +16,7 @@ namespace Cake\Test\TestCase\Database\Type;
 
 use Cake\Database\Type;
 use Cake\Database\Type\FloatType;
+use Cake\I18n\I18n;
 use Cake\TestSuite\TestCase;
 use \PDO;
 
@@ -35,6 +36,20 @@ class FloatTypeTest extends TestCase
         parent::setUp();
         $this->type = Type::build('float');
         $this->driver = $this->getMock('Cake\Database\Driver');
+        $this->locale = I18n::locale();
+
+        I18n::locale($this->locale);
+    }
+
+    /**
+     * tearDown method
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        parent::tearDown();
+        I18n::locale($this->locale);
     }
 
     /**
@@ -94,6 +109,32 @@ class FloatTypeTest extends TestCase
     }
 
     /**
+     * Tests marshalling numbers using the locale aware parser
+     *
+     * @return void
+     */
+    public function testMarshalWithLocaleParsing()
+    {
+        I18n::locale('de_DE');
+        $this->type->useLocaleParser();
+        $expected = 1234.53;
+        $result = $this->type->marshal('1.234,53');
+        $this->assertEquals($expected, $result);
+
+        I18n::locale('en_US');
+        $this->type->useLocaleParser();
+        $expected = 1234;
+        $result = $this->type->marshal('1,234');
+        $this->assertEquals($expected, $result);
+
+        I18n::locale('pt_BR');
+        $this->type->useLocaleParser();
+        $expected = 5987123.231;
+        $result = $this->type->marshal('5.987.123,231');
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
      * Test that the PDO binding type is correct.
      *
      * @return void

+ 25 - 0
tests/TestCase/I18n/NumberTest.php

@@ -95,6 +95,31 @@ class NumberTest extends TestCase
     }
 
     /**
+     * testParseFloat method
+     *
+     * @return void
+     */
+    public function testParseFloat()
+    {
+        I18n::locale('de_DE');
+        $value = '1.234.567,891';
+        $result = $this->Number->parseFloat($value);
+        $expected = 1234567.891;
+        $this->assertEquals($expected, $result);
+
+        I18n::locale('pt_BR');
+        $value = '1.234,37';
+        $result = $this->Number->parseFloat($value);
+        $expected = 1234.37;
+        $this->assertEquals($expected, $result);
+
+        $value = '1,234.37';
+        $result = $this->Number->parseFloat($value, ['locale' => 'en_US']);
+        $expected = 1234.37;
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
      * testFormatDelta method
      *
      * @return void