rector icon indicating copy to clipboard operation
rector copied to clipboard

[PHP 7.4] Replace deprecated money_format() with number_format()/NumberFormatter

Open TomasVotruba opened this issue 2 years ago • 11 comments

This function was deprecatedin PHP 7.4

  • https://github.com/php/php-src/commit/b1cdf06673789cf5c379e7af88c5af98bd06735c

This is tested and values are identical 1:1 in both version.

-            $value = money_format('%i', $value);
+            $roundedValue = round($value, 2, PHP_ROUND_HALF_ODD);
+            $value = number_format($roundedValue, 2, '.', '');

Another way is using NumberFormatter, but I could not figure it out:

  • https://benedikt.gr/posts/2019-12-01-php-money-format-to-number_formatter/

TomasVotruba avatar Aug 01 '22 22:08 TomasVotruba

@TomasVotruba it seems the result need to be checked against Locale value, eg:

setlocale(LC_MONETARY, 'en_US');
echo money_format('%i', 11);

// output :    USD 11.00

which we can't use only number_format() as it not prepended with USD for en_US.

samsonasik avatar Aug 02 '22 17:08 samsonasik

Oddly enough, I did this very change in someone's code yesterday using regex substitutions:

From:

setlocale(LC_MONETARY, 'en_US');
echo money_format( '%(n', ($rowm1['00001'])); // wrap output in () if negative

To:

$numFormatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY_ACCOUNTING);
echo $numFormatter->formatCurrency($rowm1['00001'], 'USD');

A lame, but working, test:

/**
 * @test
 */
public function numberFormatterReturnsCorrectValue(): void
{
    $this->assertSame(
        '($123.46)',
        formatIt(-123.45987887)
    );

    $this->assertSame(
        '$123.46',
        formatIt(123.45987887)
    );
}

Some work in mapping the money_format() format specifiers to the appropriateNumberFormatter class constants. (You're welcome, says Captain Obvious.) I haven't figured out the rule-based formats yet.

clarkphp avatar Aug 02 '22 21:08 clarkphp

@clarkphp could you write list of possible equivalent code from money_format() to NumberFormatter/number_format? Thank you.

samsonasik avatar Aug 03 '22 07:08 samsonasik

Since I've not used either money_format() or NumberFormatter class much before this client's code migration, I'm not intimately familiar with their behaviour. So, I looked here for inspiration:

https://github.com/php/php-src/tree/master/ext/intl/tests (formatter_*.phpt)

https://github.com/php/php-src/blob/php-7.4.30/ext/standard/tests/strings (money*.phpt)

Some of that will be more useful than other parts, but the above, the manual pages, and some experimentation should yield format mappings to aid in rectorphp/rector-src#2727

I can't spend a lot of time on it all at once, but I can add mappings each day until it's deemed sufficient. Will that do?

clarkphp avatar Aug 03 '22 12:08 clarkphp

@clarkphp thank you for the pointer, yes, that will do, we can improve with iteration PRs 👍 , not only in single PR for it

samsonasik avatar Aug 03 '22 12:08 samsonasik

@TomasVotruba @clarkphp it seems we can use money_format_polyfill library for it, ref

https://github.com/boenrobot/money_format_polyfill

The result seems equals with apply setlocale() usage https://3v4l.org/IGIr0

samsonasik avatar Aug 04 '22 15:08 samsonasik

@samsonasik The polyfill can help in the worst cases, but in the end the upgrade path to current PHP features is more maintainable choice.

TomasVotruba avatar Aug 04 '22 17:08 TomasVotruba

I'll have all the mappings I can do completed sometime this afternoon.

clarkphp avatar Aug 05 '22 13:08 clarkphp

I'm not done with the mapping, and certainly not sending you a PR this morning, but to prove I'm doing something: https://gist.github.com/clarkphp/b2643a4c4a16cafd02bd85eba18a72dd

clarkphp avatar Aug 08 '22 12:08 clarkphp

Thank you @clarkphp , that may need to be in invokable function to avoid out of scope variable make side effect.

samsonasik avatar Aug 08 '22 18:08 samsonasik

Don't worry; don't read that code as if I think it close to what the rector/fixer would look like. That's just a scratchpad for figuring out the behavior of money_format() and NumberFormatter.

clarkphp avatar Aug 08 '22 19:08 clarkphp

@samsonasik Just letting you know I'm still working on this. See experiments and output at https://gist.github.com/clarkphp/b2643a4c4a16cafd02bd85eba18a72dd

Modeling after your initial PR, I've got new code or modifications in:

  • rector-src/rules/Php74/Rector/FuncCall/MoneyFormatToNumberFormatterRector.php
  • rector-src/rules/Php74/Rector/FuncCall/MoneyFormatToNumberFormatterRector/config/configured_rule.php
  • rector-src/rules-tests/Php74/Rector/FuncCall/MoneyFormatToNumberFormatterRector/Fixture/fixture.php.inc
  • rector-src/rules-tests/Php74/Rector/FuncCall/MoneyFormatToNumberFormatterRector/MoneyFormatToNumberFormatterRectorTest.php
  • rector-src/src/ValueObject/PhpVersionFeature.php

After work today, I may open a draft PR for your thoughts. I'll give CONTRIBUTING.md another thorough read, as well.

clarkphp avatar Aug 12 '22 12:08 clarkphp

Thank you, code snippet with include in online code parser can be used to build closure for it, ref:

  • https://github.com/rectorphp/rector-downgrade-php/blob/main/rules/DowngradePhp81/snippet/array_is_list_closure.php.inc
  • https://github.com/rectorphp/rector-downgrade-php/blob/c5e04ca5255452f1f9fdab28b8a6bcb7676f24a0/rules/DowngradePhp81/Rector/FuncCall/DowngradeArrayIsListRector.php#L104

samsonasik avatar Aug 12 '22 12:08 samsonasik