Skip to content

[4.x] Use RationalMoney instead of Money #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,9 @@ $totalCustomTypeModifiers = $price->modifiers(false, 'custom');
$totalCustomTypeModifiersPerUnit = $price->modifiers(true, 'custom');
```

> [!WARNING]
> These values return instances of `RationalMoney` instead of regular `Money`, this is to allow your modifiers to return `RationalMoney` values as well. `RationalMoney` objects do not require rounding when doing operations on them. If you want to transform them to regular `Money` objects, you must specify the context and rounding options. [See the `brick/money` documentation](https://github.com/brick/money?tab=readme-ov-file#advanced-calculations) for more info.

## Output

By default, all handled monetary values are wrapped into a `Brick\Money\Money` object. This should be the only way to manipulate these values in order to [avoid decimal approximation errors](https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency).
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"prefer-stable": true,
"require": {
"php": "^8.0",
"brick/money": "0.8.*"
"brick/money": "^0.9.0"
},
"require-dev": {
"pestphp/pest": "^1.0"
Expand Down
233 changes: 119 additions & 114 deletions composer.lock

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions src/Calculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Whitecube\Price;

use Brick\Money\Money;
use Brick\Money\AbstractMoney;
use Brick\Money\RationalMoney;
use Whitecube\Price\Price;

class Calculator
Expand Down Expand Up @@ -38,7 +38,7 @@ public function exclusiveBeforeVat(bool $perUnit): array
/**
* Return the result for the "VAT" Money build
*/
public function vat(bool $perUnit): array|AbstractMoney
public function vat(bool $perUnit): array|RationalMoney
{
return $this->getCached('vat', $perUnit)
?? $this->setCached('vat', $perUnit, $this->getVatResult($perUnit));
Expand All @@ -65,15 +65,15 @@ public function inclusive(bool $perUnit): array
/**
* Retrieve a cached value for key and unit mode
*/
protected function getCached(string $key, bool $perUnit): null|array|AbstractMoney
protected function getCached(string $key, bool $perUnit): null|array|RationalMoney
{
return $this->cache[$key][$perUnit ? 'unit' : 'all'] ?? null;
}

/**
* Set a cached value for key and unit mode
*/
protected function setCached(string $key, bool $perUnit, array|AbstractMoney $result): array|AbstractMoney
protected function setCached(string $key, bool $perUnit, array|RationalMoney $result): array|RationalMoney
{
$this->cache[$key][$perUnit ? 'unit' : 'all'] = $result;

Expand Down Expand Up @@ -123,12 +123,12 @@ protected function getExclusiveAfterVatResult(bool $perUnit): array
/**
* Compute the VAT
*/
protected function getVatResult(bool $perUnit): AbstractMoney
protected function getVatResult(bool $perUnit): RationalMoney
{
$vat = $this->price->vat(true);

if(! $vat) {
return Money::zero($this->price->currency(), $this->price->context());
return Money::zero($this->price->currency(), $this->price->context())->toRational();
}

$exclusive = $this->exclusiveBeforeVat($perUnit)['amount'];
Expand All @@ -143,11 +143,11 @@ protected function getInclusiveResult(bool $perUnit): array
{
$result = $this->exclusiveBeforeVat($perUnit);

$result['amount'] = $result['amount']->plus($this->vat($perUnit), Price::getRounding('vat'));
$result['amount'] = $result['amount']->plus($this->vat($perUnit));

$supplement = $this->exclusiveAfterVat($perUnit);

$result['amount'] = $result['amount']->plus($supplement['amount'], Price::getRounding('exclusive'));
$result['amount'] = $result['amount']->plus($supplement['amount']);
$result['modifications'] = array_merge($result['modifications'], $supplement['modifications']);

return $result;
Expand All @@ -156,7 +156,7 @@ protected function getInclusiveResult(bool $perUnit): array
/**
* Compute the price for one unit before VAT is applied
*/
protected function applyModifier(PriceAmendable $modifier, array $result, bool $perUnit, bool $postVat = false, AbstractMoney $exclusive = null, Vat $vat = null): array
protected function applyModifier(PriceAmendable $modifier, array $result, bool $perUnit, bool $postVat = false, RationalMoney $exclusive = null, Vat $vat = null): array
{
$updated = $modifier->apply($result['amount'], $this->price->units(), $perUnit, $exclusive, $vat);

Expand Down
12 changes: 6 additions & 6 deletions src/Concerns/HasModifiers.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Whitecube\Price\Modifier;
use Whitecube\Price\PriceAmendable;
use Brick\Money\AbstractMoney;
use Brick\Money\Money;
use Brick\Money\RationalMoney;

trait HasModifiers
{
Expand Down Expand Up @@ -68,7 +68,7 @@ protected function makeModifier(string $type, string|callable|AbstractMoney|Pric
$modifier = $modifier->inclusive();
}

return $instance->add($modifier, Price::getRounding('exclusive'));
return $instance->add($modifier);
}

/**
Expand Down Expand Up @@ -101,25 +101,25 @@ public function modifications(bool $perUnit = false, ?string $type = null): arra
/**
* Return the modification total for all discounts
*/
public function discounts(bool $perUnit = false): Money
public function discounts(bool $perUnit = false): RationalMoney
{
return $this->modifiers($perUnit, Modifier::TYPE_DISCOUNT);
}

/**
* Return the modification total for all taxes
*/
public function taxes(bool $perUnit = false): Money
public function taxes(bool $perUnit = false): RationalMoney
{
return $this->modifiers($perUnit, Modifier::TYPE_TAX);
}

/**
* Return the modification total for a given type
*/
public function modifiers(bool $perUnit = false, ?string $type = null): Money
public function modifiers(bool $perUnit = false, ?string $type = null): RationalMoney
{
$amount = Money::zero($this->currency());
$amount = RationalMoney::of(0, $this->currency());

foreach ($this->modifications($perUnit, $type) as $modification) {
$amount = $amount->plus($modification['amount']);
Expand Down
49 changes: 17 additions & 32 deletions src/Concerns/OperatesOnBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Whitecube\Price\Price;
use Brick\Money\AbstractMoney;
use Brick\Money\RationalMoney;
use Brick\Money\Exception\MoneyMismatchException;
use Brick\Money\Money;
use Brick\Math\BigNumber;
Expand All @@ -21,7 +22,7 @@ public function __call(string $method, array $arguments): mixed

$result = call_user_func_array([$this->base, $method], $arguments);

if(! is_a($result, AbstractMoney::class)) {
if(! is_a($result, RationalMoney::class)) {
return $result;
}

Expand All @@ -33,64 +34,48 @@ public function __call(string $method, array $arguments): mixed
}

/**
* Check if given value equals the price's base value
* Check if the provided amount equals this price's total inclusive amount
*/
public function equals(BigNumber|int|float|string|AbstractMoney|Price $value): bool
public function equals(BigNumber|int|float|string|AbstractMoney|Price $that): bool
{
return $this->compareTo($value) === 0;
return $this->compareTo($that) === 0;
}

/**
* Compare a given value to the total inclusive value of this instance
* Compare the provided amount to this price's total inclusive amount
*/
public function compareTo(BigNumber|int|float|string|AbstractMoney|Price $value): int
public function compareTo(BigNumber|int|float|string|AbstractMoney|Price $that): int
{
return $this->compareMonies(
$this->inclusive(),
$this->valueToMoney($value)
$this->valueToRationalMoney($that, 'inclusive')
);
}

/**
* Compare a given value to the unitless base value of this instance
* Compare a provided amount to this price's unitless base amount
*/
public function compareBaseTo(BigNumber|int|float|string|AbstractMoney|Price $value): int
public function compareBaseTo(BigNumber|int|float|string|AbstractMoney|Price $that): int
{
return $this->compareMonies(
$this->base(),
$this->valueToMoney($value, 'base')
$this->valueToRationalMoney($that, 'base')
);
}

/**
* Compare the given "current" value to another value
* Compare the provided "current" amount to another amount
* @throws \Brick\Money\Exception\MoneyMismatchException
*/
protected function compareMonies(AbstractMoney $price, AbstractMoney $that): int
protected function compareMonies(RationalMoney $current, RationalMoney $that): int
{
$priceCurrency = $price->getCurrency();
$currentCurrency = $this->currency();
$thatCurrency = $that->getCurrency();

if($priceCurrency->getCurrencyCode() === $thatCurrency->getCurrencyCode()) {
return $price->getAmount()->compareTo($that->getAmount());
if($currentCurrency->getCurrencyCode() === $thatCurrency->getCurrencyCode()) {
return $current->getAmount()->compareTo($that->getAmount());
}

throw MoneyMismatchException::currencyMismatch($priceCurrency, $thatCurrency);
}

/**
* Transform a given value into a Money instance
*/
protected function valueToMoney(BigNumber|int|float|string|AbstractMoney|Price $value, string $method = 'inclusive'): AbstractMoney
{
if(is_a($value, Price::class)) {
$value = $value->$method();
}

if(is_a($value, AbstractMoney::class)) {
return $value;
}

return Money::ofMinor($value, $this->currency());
throw MoneyMismatchException::currencyMismatch($currentCurrency, $thatCurrency);
}
}
34 changes: 17 additions & 17 deletions src/Modifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Whitecube\Price;

use Brick\Money\Money;
use Brick\Money\AbstractMoney;
use Brick\Money\RationalMoney;

class Modifier implements PriceAmendable
{
Expand Down Expand Up @@ -155,11 +155,11 @@ public function appliesPerUnit(): bool
/**
* Add an addition modification to the stack
*/
public function add(...$arguments): static
public function add($argument): static
{
$this->stack[] = [
'method' => 'plus',
'arguments' => $arguments
'argument' => $argument
];

return $this;
Expand All @@ -168,11 +168,11 @@ public function add(...$arguments): static
/**
* Add a substraction modification to the stack
*/
public function subtract(...$arguments): static
public function subtract($argument): static
{
$this->stack[] = [
'method' => 'minus',
'arguments' => $arguments
'argument' => $argument
];

return $this;
Expand All @@ -181,11 +181,11 @@ public function subtract(...$arguments): static
/**
* Add a multiplication modification to the stack
*/
public function multiply(...$arguments): static
public function multiply($argument): static
{
$this->stack[] = [
'method' => 'multipliedBy',
'arguments' => $arguments
'argument' => $argument
];

return $this;
Expand All @@ -194,11 +194,11 @@ public function multiply(...$arguments): static
/**
* Add a division modification to the stack
*/
public function divide(...$arguments): static
public function divide($argument): static
{
$this->stack[] = [
'method' => 'dividedBy',
'arguments' => $arguments
'argument' => $argument
];

return $this;
Expand All @@ -219,7 +219,7 @@ public function abs(): static
/**
* Apply the modifier on the given Money instance
*/
public function apply(AbstractMoney $build, $units, $perUnit, AbstractMoney $exclusive = null, Vat $vat = null) : ?AbstractMoney
public function apply(RationalMoney $build, $units, $perUnit, RationalMoney $exclusive = null, Vat $vat = null) : ?RationalMoney
{
if(! $this->stack) {
return null;
Expand All @@ -230,15 +230,15 @@ public function apply(AbstractMoney $build, $units, $perUnit, AbstractMoney $exc
return $this->applyStackAction($action, $build);
}

$argument = is_a($action['arguments'][0] ?? null, AbstractMoney::class)
? $action['arguments'][0]
: Money::ofMinor($action['arguments'][0] ?? 0, $build->getCurrency());
$argument = is_a($action['argument'] ?? null, RationalMoney::class)
? $action['argument']
: Money::ofMinor($action['argument'] ?? 0, $build->getCurrency())->toRational();

if($this->appliesPerUnit() && (! $perUnit) && $units > 1) {
$argument = $argument->multipliedBy($units, Price::getRounding('exclusive'));
$argument = $argument->multipliedBy($units);
}

$action['arguments'][0] = $argument;
$action['argument'] = $argument;

return $this->applyStackAction($action, $build);
}, $build);
Expand All @@ -247,8 +247,8 @@ public function apply(AbstractMoney $build, $units, $perUnit, AbstractMoney $exc
/**
* Apply given stack action on the price being build
*/
protected function applyStackAction(array $action, AbstractMoney $build): AbstractMoney
protected function applyStackAction(array $action, RationalMoney $build): RationalMoney
{
return call_user_func_array([$build, $action['method']], $action['arguments'] ?? []);
return call_user_func([$build, $action['method']], $action['argument'] ?? null);
}
}
Loading
Loading