From 8ea9c5393f4a03711e0b1847097c8ec70de6136b Mon Sep 17 00:00:00 2001 From: George Mponos Date: Sun, 2 Dec 2018 18:52:47 +0200 Subject: [PATCH 1/6] Parse exponential float as Money --- src/Parser/ExponentialMoneyParser.php | 120 ++++++++++++++++++++ tests/Parser/ExponentialMoneyParserTest.php | 97 ++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 src/Parser/ExponentialMoneyParser.php create mode 100644 tests/Parser/ExponentialMoneyParserTest.php diff --git a/src/Parser/ExponentialMoneyParser.php b/src/Parser/ExponentialMoneyParser.php new file mode 100644 index 000000000..74969ce3e --- /dev/null +++ b/src/Parser/ExponentialMoneyParser.php @@ -0,0 +1,120 @@ + + * @author Teoh Han Hui + */ +final class ExponentialMoneyParser implements MoneyParser +{ + const EXPO_DECIMAL_PATTERN = '/^(?P-)?(?P0|[1-9]\d*)?\.?(?P\d+)?[eE][-+]\d+$/'; + const DECIMAL_PATTERN = '/^(?P-)?(?P0|[1-9]\d*)?\.?(?P\d+)?$/'; + + /** + * @var Currencies + */ + private $currencies; + + /** + * @param Currencies $currencies + */ + public function __construct(Currencies $currencies) + { + $this->currencies = $currencies; + } + + /** + * {@inheritdoc} + */ + public function parse($money, $forceCurrency = null) + { + if (!is_string($money)) { + throw new ParserException('Formatted raw money should be string, e.g. 1.00'); + } + + if (null === $forceCurrency) { + throw new ParserException( + 'DecimalMoneyParser cannot parse currency symbols. Use forceCurrency argument' + ); + } + + /* + * This conversion is only required whilst currency can be either a string or a + * Currency object. + */ + $currency = $forceCurrency; + if (!$currency instanceof Currency) { + @trigger_error('Passing a currency as string is deprecated since 3.1 and will be removed in 4.0. Please pass a '.Currency::class.' instance instead.', E_USER_DEPRECATED); + $currency = new Currency($currency); + } + + $expo = trim($money); + if ($expo === '') { + return new Money(0, $currency); + } + + $subunit = $this->currencies->subunitFor($currency); + + if (!preg_match(self::EXPO_DECIMAL_PATTERN, $expo, $matches) || !isset($matches['digits'])) { + throw new ParserException(sprintf( + 'Cannot parse "%s" to Money.', + $expo + )); + } + + $number = number_format($expo, $subunit, '.', ''); + if (!preg_match(self::DECIMAL_PATTERN, $number, $matches) || !isset($matches['digits'])) { + throw new ParserException(sprintf( + 'Cannot parse "%s" to Money.', + $expo + )); + } + + + $negative = isset($matches['sign']) && $matches['sign'] === '-'; + + $decimal = $matches['digits']; + + if ($negative) { + $decimal = '-'.$decimal; + } + + if (isset($matches['fraction'])) { + $fractionDigits = strlen($matches['fraction']); + $decimal .= $matches['fraction']; + $decimal = Number::roundMoneyValue($decimal, $subunit, $fractionDigits); + + if ($fractionDigits > $subunit) { + $decimal = substr($decimal, 0, $subunit - $fractionDigits); + } elseif ($fractionDigits < $subunit) { + $decimal .= str_pad('', $subunit - $fractionDigits, '0'); + } + } else { + $decimal .= str_pad('', $subunit, '0'); + } + + if ($negative) { + $decimal = '-'.ltrim(substr($decimal, 1), '0'); + } else { + $decimal = ltrim($decimal, '0'); + } + + if ($decimal === '' || $decimal === '-') { + $decimal = '0'; + } + + return new Money($decimal, $currency); + } +} diff --git a/tests/Parser/ExponentialMoneyParserTest.php b/tests/Parser/ExponentialMoneyParserTest.php new file mode 100644 index 000000000..79bb8abc7 --- /dev/null +++ b/tests/Parser/ExponentialMoneyParserTest.php @@ -0,0 +1,97 @@ +prophesize(Currencies::class); + + $currencies->subunitFor(Argument::allOf( + Argument::type(Currency::class), + Argument::which('getCode', $currency) + ))->willReturn($subunit); + + $parser = new ExponentialMoneyParser($currencies->reveal()); + + $this->assertEquals($result, $parser->parse($decimal, new Currency($currency))->getAmount()); + } + + /** + * @dataProvider invalidMoneyExamples + * @test + */ + public function it_throws_an_exception_upon_invalid_inputs($input) + { + $this->expectException(ParserException::class); + + $currencies = $this->prophesize(Currencies::class); + + $currencies->subunitFor(Argument::allOf( + Argument::type(Currency::class), + Argument::which('getCode', 'USD') + ))->willReturn(2); + + $parser = new ExponentialMoneyParser($currencies->reveal()); + + $parser->parse($input, new Currency('USD'))->getAmount(); + } + + /** + * @group legacy + * @expectedDeprecation Passing a currency as string is deprecated since 3.1 and will be removed in 4.0. Please + * pass a Money\Currency instance instead. + * @test + */ + public function it_accepts_only_a_currency_object() + { + $currencies = $this->prophesize(Currencies::class); + + $currencies->subunitFor(Argument::allOf( + Argument::type(Currency::class), + Argument::which('getCode', 'USD') + ))->willReturn(2); + + $parser = new ExponentialMoneyParser($currencies->reveal()); + + $parser->parse('1.0', 'USD')->getAmount(); + } + + public function formattedMoneyExamples() + { + return [ + ['2.8865798640254e+15', 'USD', 2, 288657986402540000], + ['2.8865798640254e-15', 'USD', 2, 0], + ['0.8865798640254e+15', 'USD', 2, 88657986402540000], + ['2.8865798640254e+15', 'JPY', 0, 2886579864025400], + ['2.8865798640254e-15', 'JPY', 0, 0], + ['0.8865798640254e+15', 'JPY', 0, 886579864025400], + ['-2.8865798640254e+15', 'USD', 2, -288657986402540000], + ['-2.8865798640254e-15', 'USD', 2, 0], + ['-0.8865798640254e+15', 'USD', 2, -88657986402540000], + ]; + } + + public static function invalidMoneyExamples() + { + return [ + ['INVALID'], + ['2.00'], + ['2'], + ['0.02'], + ['.'], + ]; + } +} From 378dc3c26c9195a7723841a50e5d736d11c04954 Mon Sep 17 00:00:00 2001 From: George Mponos Date: Sun, 2 Dec 2018 18:57:48 +0200 Subject: [PATCH 2/6] Fix style issues --- src/Parser/ExponentialMoneyParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/ExponentialMoneyParser.php b/src/Parser/ExponentialMoneyParser.php index 74969ce3e..5171e391e 100644 --- a/src/Parser/ExponentialMoneyParser.php +++ b/src/Parser/ExponentialMoneyParser.php @@ -20,6 +20,7 @@ final class ExponentialMoneyParser implements MoneyParser { const EXPO_DECIMAL_PATTERN = '/^(?P-)?(?P0|[1-9]\d*)?\.?(?P\d+)?[eE][-+]\d+$/'; + const DECIMAL_PATTERN = '/^(?P-)?(?P0|[1-9]\d*)?\.?(?P\d+)?$/'; /** @@ -82,7 +83,6 @@ public function parse($money, $forceCurrency = null) )); } - $negative = isset($matches['sign']) && $matches['sign'] === '-'; $decimal = $matches['digits']; From 5917181e98a8ec294a0de688ddb7fbc7a3dc4db8 Mon Sep 17 00:00:00 2001 From: George Mponos Date: Sun, 2 Dec 2018 19:04:19 +0200 Subject: [PATCH 3/6] Fix tests --- tests/Parser/ExponentialMoneyParserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Parser/ExponentialMoneyParserTest.php b/tests/Parser/ExponentialMoneyParserTest.php index 79bb8abc7..25638d6e2 100644 --- a/tests/Parser/ExponentialMoneyParserTest.php +++ b/tests/Parser/ExponentialMoneyParserTest.php @@ -66,7 +66,7 @@ public function it_accepts_only_a_currency_object() $parser = new ExponentialMoneyParser($currencies->reveal()); - $parser->parse('1.0', 'USD')->getAmount(); + $parser->parse('2.8865798640254e+15', 'USD')->getAmount(); } public function formattedMoneyExamples() From 619086b26ac4aad8db11e04262df6718d434249a Mon Sep 17 00:00:00 2001 From: George Mponos Date: Sun, 2 Dec 2018 19:24:56 +0200 Subject: [PATCH 4/6] Fix tests --- tests/Parser/ExponentialMoneyParserTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Parser/ExponentialMoneyParserTest.php b/tests/Parser/ExponentialMoneyParserTest.php index 25638d6e2..9bd6ab289 100644 --- a/tests/Parser/ExponentialMoneyParserTest.php +++ b/tests/Parser/ExponentialMoneyParserTest.php @@ -51,8 +51,7 @@ public function it_throws_an_exception_upon_invalid_inputs($input) /** * @group legacy - * @expectedDeprecation Passing a currency as string is deprecated since 3.1 and will be removed in 4.0. Please - * pass a Money\Currency instance instead. + * @expectedDeprecation Passing a currency as string is deprecated since 3.1 and will be removed in 4.0. Please pass a Money\Currency instance instead. * @test */ public function it_accepts_only_a_currency_object() From 9560b90b7a5173d6461761cba10e97cfdbcc543f Mon Sep 17 00:00:00 2001 From: Mponos George Date: Sat, 28 Dec 2019 12:29:40 +0200 Subject: [PATCH 5/6] Update src/Parser/ExponentialMoneyParser.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Márk Sági-Kazár --- src/Parser/ExponentialMoneyParser.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Parser/ExponentialMoneyParser.php b/src/Parser/ExponentialMoneyParser.php index 5171e391e..d1d42570b 100644 --- a/src/Parser/ExponentialMoneyParser.php +++ b/src/Parser/ExponentialMoneyParser.php @@ -15,7 +15,6 @@ * @example `2.8865798640254e+15` * * @author George Mponos - * @author Teoh Han Hui */ final class ExponentialMoneyParser implements MoneyParser { From b3a67ad229d13d5c6439361aace74da73e1aa356 Mon Sep 17 00:00:00 2001 From: Mponos George Date: Thu, 21 May 2020 20:57:27 +0300 Subject: [PATCH 6/6] Update ExponentialMoneyParser.php --- src/Parser/ExponentialMoneyParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/ExponentialMoneyParser.php b/src/Parser/ExponentialMoneyParser.php index d1d42570b..05b5d5251 100644 --- a/src/Parser/ExponentialMoneyParser.php +++ b/src/Parser/ExponentialMoneyParser.php @@ -46,7 +46,7 @@ public function parse($money, $forceCurrency = null) if (null === $forceCurrency) { throw new ParserException( - 'DecimalMoneyParser cannot parse currency symbols. Use forceCurrency argument' + 'ExponentialMoneyParser cannot parse currency symbols. Use forceCurrency argument' ); }