Skip to content

Latest commit

 

History

History
558 lines (379 loc) · 15.8 KB

3.x_to_4.x.md

File metadata and controls

558 lines (379 loc) · 15.8 KB

🧰 Upgrade guide from 3.x to 4.x

PHP and Laravel versions

  • PHP: ^8.1
  • Laravel: 9

This version drops support of PHP < 8.1 and now supports Laravel 9. This was made in order to keep pace with new versions and use new features.

Extensions

  • bcmath Now used for all calculations to be as accurate as possible.

📈 Raising precision

The main purpose of this update was to raise the precision of monetary objects.

It was decided to use only string as a representation of money due to large numbers are being stored, therefore these amounts were raised up to 4 decimal places.

For example, the following 102500 stands for 10.25.

📦 Migrations

⚠️ WARN: before running any migration, make sure you installed the doctrine/dbal package.

If you've used integer origin

That's good if you heeded Uncle Bob's advice: don't use floating point numbers to represent monetary amounts.

View a migration
// Adjust these constants for your needs.
// DECIMALS is the value that is set in the config file (the value that was before).
// TABLES is an array of tables you need to change in.
// A key represents a table name and a value (array) represents needed columns for the change.
private const DECIMALS = 1;
private const TABLES = [
    'users' => ['balance'],
    'products' => ['min_price', 'price'],
];

public function up(): void
{
    foreach (self::TABLES as $tableName => $columns) {    
        Schema::table($tableName, function (Blueprint $table) use ($columns) {
            foreach ($columns as $column) {
                $table->bigInteger($column)->default(0)->change();
            }
        });
    
        $lackingDecimalPlaces = 4 - self::DECIMALS;
        if ($lackingDecimalPlaces > 0) {
            $multiplier = 10 ** $lackingDecimalPlaces;
            DB::table($tableName)
                ->update(
                    collect($columns)
                        ->mapWithKeys(function (string $column) use ($multiplier) {
                            return [$column => DB::raw("$column * $multiplier")];
                        })
                        ->toArray()
                );
        }
    }
}

If you've used float origin

Unfortunately, there is a lot of work to do here. However, I've spent some time to write a migration for you, but you still need to do a manual stuff in the code.

View a migration
// Adjust this constant for your needs.
// TABLES is an array of tables you need to change in.
// A key represents a table name and a value (array) represents needed columns for the change.
private const TABLES = [
    'users' => ['balance'],
    'products' => ['min_price', 'price'],
];

public function up(): void
{
    foreach (self::TABLES as $tableName => $columns) {
        Schema::table($tableName, function (Blueprint $table) use ($columns) {
            foreach ($columns as $column) {
                // WARNING: make sure the largest amount (in one of the columns) is not greater than 16 digits,
                // otherwise increase the decimal parameters (20 and 4)
                $table->decimal($column, 20, 4)->default(0)->change();
            }
        });

        $multiplier = 10 ** 4;
        DB::table($tableName)
            ->update(
                collect($columns)
                    ->mapWithKeys(function (string $column) use ($multiplier) {
                        return [$column => DB::raw("$column * $multiplier")];
                    })
                    ->toArray(),
            );

        Schema::table($tableName, function (Blueprint $table) use ($columns) {
            foreach ($columns as $column) {
                $table->bigInteger($column)->default(0)->change();
            }
        });
    }
}

🛡 Responsibility

Please note that you are responsible for the correctness of the migration. Check several times yourself locally.

If you find any vulnerability or idea for improvement in any migration, please open a new issue.

Origins

Origins were completely removed from this library, so if you use them anywhere, you must clean up every single place where it's been used.

❌ The property origin was removed from the config file.

❌ The following methods were removed from MoneySettings:

  • setOrigin()
  • getOrigin()

❌ The ORIGIN constants were removed:

  • ORIGIN_INT
  • ORIGIN_FLOAT

❌ The exception UndefinedOriginException was removed.

❌ The following method was removed from Money:

  • upload()

🟧 The following methods no longer accept number and origin as their first and second arguments, respectively.

Now they have only one argument (another monetary object).

NO LONGER:

use PostScripton\Money\MoneySettings;

$m1 = money('100000');

$m1->add(200, MoneySettings::ORIGIN_INT)

ONLY WAY:

$m1 = money('1000000');

$m1->add(money('2000000'));

😔 Unfortunately, you have to check all usages manually and adjust them so that they can accept only a monetary object as their first argument, and don't use origin word.

Money constructors

All money construct methods:

  • new Money()
  • Money::of()
  • money()

now accept only string as their first argument.

Enums

When the package migrated to PHP 8.1, the enums became available.

🟩 CurrencyDisplay

  • Currency::DISPLAY_SYMBOL => 🟩 CurrencyDisplay::Symbol
  • Currency::DISPLAY_CODE => 🟩 CurrencyDisplay::Code

🟩 CurrencyList

  • CurrencyList::All
  • CurrencyList::Popular
  • CurrencyList::Custom

🟩 CurrencyPosition

  • Currency::POSITION_START => 🟩 CurrencyPosition::Start
  • Currency::POSITION_END => 🟩 CurrencyPosition::End

Config

Enums instead of constants

  1. In the currency_list setting change a string on its enum equivalent CurrencyList
  2. In the custom_currencies setting change a position-constant on its enum equivalent CurrencyPosition

Formatting

Put your formatting settings inside of formatting key.

Before

'thousands_separator' => ' ',
'decimal_separator' => '.',
'decimals' => 1,
'ends_with_0' => false,
'space_between' => true,
config('money.thousands_separator');

After

'formatting' => [
    'thousands_separator' => ' ',
    'decimal_separator' => '.',
    'decimals' => 1,
    'ends_with_0' => false,
    'space_between' => true,
],
config('money.formatting.thousands_separator');

Rate exchangers

Replace service and services with rate_exchanger and rate_exchangers correspondingly.

⚠️ exchangeratesapi is replaced with fixer.

Before

'service' => 'exchangerate',

'services' => [
    'exchangeratesapi' => [
        // https://exchangeratesapi.io/
        'class' => \PostScripton\Money\Services\ExchangeRatesAPIService::class,
        'key' => env('EXCHANGERATESAPI_API_KEY'),
        'secure' => env('EXCHANGERATESAPI_SECURE', false),
        'base_restriction' => env('EXCHANGERATESAPI_BASE_RESTRICTION', true),
    ],
    'openexchangerates' => [
        // https://openexchangerates.org/
        'class' => \PostScripton\Money\Services\OpenExchangeRatesService::class,
        'key' => env('OPENEXCHANGERATES_API_KEY'),
        'base_restriction' => env('OPENEXCHANGERATES_BASE_RESTRICTION', true),
    ],
    'exchangerate' => [
        // https://exchangerate.host/
        'class' => \PostScripton\Money\Services\ExchangeRateService::class,
    ],
],

After

'rate_exchanger' => 'exchangerate',

'rate_exchangers' => [

    'fixer' => [
        // https://fixer.io/
        'class' => \PostScripton\Money\Clients\RateExchangers\Fixer::class,
        'key' => env('FIXER_API_KEY'),
        'free_plan' => env('FIXER_FREE_PLAN', true),
    ],

    'openexchangerates' => [
        // https://openexchangerates.org/
        'class' => \PostScripton\Money\Clients\RateExchangers\OpenExchangeRates::class,
        'key' => env('OPENEXCHANGERATES_API_KEY'),
    ],

    'exchangerate' => [
        // https://exchangerate.host/
        'class' => \PostScripton\Money\Clients\RateExchangers\ExchangeRate::class,
    ],

],

Cache

There's new settings for caching:

'cache' => [
    'enabled' => env('MONEY_CACHE_ENABLED', true),    
    
    'store' => 'default',
    
    'rate_exchanger' => [
        // Request to get all supported currencies by rate exchanger.
        'supports' => [
            'ttl' => \DateInterval::createFromDateString('7 days'), // or `null` to store forever
        ],

        // Request to get a real-time exchange rates. Historical rates are stored forever.
        'rate' => [
            'ttl' => \DateInterval::createFromDateString('1 hour'), // or `null` to store forever
        ],
    ],
],

Money

🟧 Constructor

There are several breaking changes in the constructor behavior:

  1. No settings in the constructor as third argument
  2. Accepts only string-integers, otherwise throws exception InvalidArgumentException
  3. Trims all decimals. Only integers (as strings) are used to represent amount

getPureAmount(), 🟧 getAmount()

The getPureAmount() method has been removed, and getAmount() now works as the previous one worked.

// 3.x =========================

$money = money('12345000');

$money->getPureAmount();    // "12345000"
$money->getAmount();        // "1 234.5"

// 4.x =========================

$money = money('12345000');

$money->getAmount();            // "12345000"
$money->toAmountOnlyString();   // "1 234.5"

Money::getDefaultCurrency(), Money::setDefaultCurrency()

These methods were moved to the Currency class.

use PostScripton\Money\Currency;

Currency::getDefault();
Currency::setDefault(currency('RUB'));

Money::correctInput()

The method has been removed due to vote results on GitHub because no one voted.

🟧 multiple()

This method has been renamed to multiply() in order to be grammatically correct.

Whenever you use it, you can take advantage of your IDE to replace this everywhere. Press Ctrl+Shift+R, select Match case and Words options, and replace all multiple( with multiply(. But first, check all found occurrences out.

🟧 multiply()

In order not to stray from the previous change, this method now accepts string instead of float.

It's very important because unexpected behavior may occur.

$money->multiply('1.5');    // use this
$money->multiply(1.5);      // instead of this

🟧 divide()

This method now accepts string instead of float.

It's very important because unexpected behavior may occur.

$money->divide('1.5');  // use this
$money->divide(1.5);    // instead of this

🟧 isEmpty()

This method has been renamed to isZero().

🟧 lessThan()

Now throws MoneyHasDifferentCurrenciesException when $money argument has a different currency.

🟧 lessThanOrEqual()

Now throws MoneyHasDifferentCurrenciesException when $money argument has a different currency.

🟧 greaterThan()

Now throws MoneyHasDifferentCurrenciesException when $money argument has a different currency.

🟧 greaterThanOrEqual()

Now throws MoneyHasDifferentCurrenciesException when $money argument has a different currency.

🟧 equals()

The method was reworked. It no longer compares objects via === and == operators.

Now, with strict flag it compares not only amounts, but also currencies of monetary objects.

🟧 difference()

This method no longer returns string, instead it returns a new monetary object. The doesn't accept settings as its second parameter as well.

The important thing is, the difference() method returns an absolute amount.

use PostScripton\Money\Money;

$m1 = Money::parse('$ 25');
$m2 = Money::parse('$ 100');

// Before
$m3 = $m1->difference($m2); // "$ -75" (string)

// After
$m3 = $m1->difference($m2); // $ 75 (Money)

🟧 convertInto()

The signature of this method changed as following:

  1. convertTo(Currency|string $to, ?Carbon $date = null): Money
  2. offlineConvertTo(Currency|string $currency, string $rate): Money

🟧 Money::make()

The method was renamed to Money::of(), the rest is the same.

🟧 Money::parse()

Now you need to help a parser to recognize a currency by providing a code (USD/840) as the second parameter. However, if only first parameter is passed, it will take the default_currency from the config file.

🟧 Money::getDefaultDivisor()

This method now always returns 10.000 (10 powered by 4 decimal places)

🟩 setCurrency()

The method was extracted from settings and moved here.

❌ MoneySettings

One of the most important things of this update is replacement MoneySettings with MoneyFormatter.

Now formatters are used in order to format (display) monetary objects.

👀 See here for full details about new formatters.

settings()

The helper for creating new settings instance was removed, because of the reason that has been mentioned above.

bind(), unbind(), bound()

These methods were removed completely because of Garbage Collection.

getCurrency(), setCurrency()

These methods were extracted directly from settings into Money.

Exceptions

Most exceptions were reworked completely and some of them were removed.

  • CustomCurrencyWrongFieldTypeException
  • CustomCurrencyDoesNotHaveFieldException

Currency

Currency list

It was decided to get rid of methods for setting a currency list from the code. From now on, it is still available for setting only from the config file.

  • Currency::currentList()
  • Currency::setCurrencyList()
  • Currency::isIncorrectList()

❌ Constants

All constants were replaced with enum equivalents.

See above in the Enums section.

Currency::getConfigCurrencyCode()

Use Currency::getDefault() instead only if you don't override it with Currency::setDefault().

If you need to use exactly default currency from the config file, then use config('money.default_currency')

🟧 Currency::getCurrencies()

This method has been moved to Currencies class:

  • Currencies::get()
  • Currencies::getCodesArray()

❌ Services

Services have been refactored to RateExchangers. Those are used inside of convertTo() method.

👀 See here for full details.

Exceptions

Most exceptions were refactored, some united and some removed. Several ones adjusted messages a little.

  • 🟩 RateExchangerException - this one united several exceptions into itself:
    • ServiceException
    • ServiceClassDoesNotExistException
    • ServiceDoesNotExistException
    • ServiceDoesNotHaveClassException
    • ServiceDoesNotInheritServiceException
    • ServiceDoesNotSupportCurrencyException
  • 🟧 These are replaced with the standard Exception:
    • ShouldPublishConfigFileException
    • WrongParserStringException
  • 🟧 These are replaced with the standard InvalidArgumentException:
    • CustomCurrencyValidationException