Server IP : 66.29.132.124 / Your IP : 3.129.216.248 Web Server : LiteSpeed System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64 User : wavevlvu ( 1524) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/wavevlvu/book24.ng/vendor/moneyphp/money/src/ |
Upload File : |
<?php namespace Money; use Money\Calculator\BcMathCalculator; use Money\Calculator\GmpCalculator; use Money\Calculator\PhpCalculator; /** * Money Value Object. * * @author Mathias Verraes * * @psalm-immutable */ final class Money implements \JsonSerializable { use MoneyFactory; const ROUND_HALF_UP = PHP_ROUND_HALF_UP; const ROUND_HALF_DOWN = PHP_ROUND_HALF_DOWN; const ROUND_HALF_EVEN = PHP_ROUND_HALF_EVEN; const ROUND_HALF_ODD = PHP_ROUND_HALF_ODD; const ROUND_UP = 5; const ROUND_DOWN = 6; const ROUND_HALF_POSITIVE_INFINITY = 7; const ROUND_HALF_NEGATIVE_INFINITY = 8; /** * Internal value. * * @var string */ private $amount; /** * @var Currency */ private $currency; /** * @var Calculator */ private static $calculator; /** * @var array */ private static $calculators = [ BcMathCalculator::class, GmpCalculator::class, PhpCalculator::class, ]; /** * @param int|string $amount Amount, expressed in the smallest units of $currency (eg cents) * @param Currency $currency * * @throws \InvalidArgumentException If amount is not integer */ public function __construct($amount, Currency $currency) { if (filter_var($amount, FILTER_VALIDATE_INT) === false) { $numberFromString = Number::fromString($amount); if (!$numberFromString->isInteger()) { throw new \InvalidArgumentException('Amount must be an integer(ish) value'); } $amount = $numberFromString->getIntegerPart(); } $this->amount = (string) $amount; $this->currency = $currency; } /** * Returns a new Money instance based on the current one using the Currency. * * @param int|string $amount * * @return Money * * @throws \InvalidArgumentException If amount is not integer */ private function newInstance($amount) { return new self($amount, $this->currency); } /** * Checks whether a Money has the same Currency as this. * * @param Money $other * * @return bool */ public function isSameCurrency(Money $other) { return $this->currency->equals($other->currency); } /** * Asserts that a Money has the same currency as this. * * @param Money $other * * @throws \InvalidArgumentException If $other has a different currency */ private function assertSameCurrency(Money $other) { if (!$this->isSameCurrency($other)) { throw new \InvalidArgumentException('Currencies must be identical'); } } /** * Checks whether the value represented by this object equals to the other. * * @param Money $other * * @return bool */ public function equals(Money $other) { return $this->isSameCurrency($other) && $this->amount === $other->amount; } /** * Returns an integer less than, equal to, or greater than zero * if the value of this object is considered to be respectively * less than, equal to, or greater than the other. * * @param Money $other * * @return int */ public function compare(Money $other) { $this->assertSameCurrency($other); return $this->getCalculator()->compare($this->amount, $other->amount); } /** * Checks whether the value represented by this object is greater than the other. * * @param Money $other * * @return bool */ public function greaterThan(Money $other) { return $this->compare($other) > 0; } /** * @param \Money\Money $other * * @return bool */ public function greaterThanOrEqual(Money $other) { return $this->compare($other) >= 0; } /** * Checks whether the value represented by this object is less than the other. * * @param Money $other * * @return bool */ public function lessThan(Money $other) { return $this->compare($other) < 0; } /** * @param \Money\Money $other * * @return bool */ public function lessThanOrEqual(Money $other) { return $this->compare($other) <= 0; } /** * Returns the value represented by this object. * * @return string */ public function getAmount() { return $this->amount; } /** * Returns the currency of this object. * * @return Currency */ public function getCurrency() { return $this->currency; } /** * Returns a new Money object that represents * the sum of this and an other Money object. * * @param Money[] $addends * * @return Money */ public function add(Money ...$addends) { $amount = $this->amount; $calculator = $this->getCalculator(); foreach ($addends as $addend) { $this->assertSameCurrency($addend); $amount = $calculator->add($amount, $addend->amount); } return new self($amount, $this->currency); } /** * Returns a new Money object that represents * the difference of this and an other Money object. * * @param Money[] $subtrahends * * @return Money * * @psalm-pure */ public function subtract(Money ...$subtrahends) { $amount = $this->amount; $calculator = $this->getCalculator(); foreach ($subtrahends as $subtrahend) { $this->assertSameCurrency($subtrahend); $amount = $calculator->subtract($amount, $subtrahend->amount); } return new self($amount, $this->currency); } /** * Asserts that the operand is integer or float. * * @param float|int|string $operand * * @throws \InvalidArgumentException If $operand is neither integer nor float */ private function assertOperand($operand) { if (!is_numeric($operand)) { throw new \InvalidArgumentException(sprintf( 'Operand should be a numeric value, "%s" given.', is_object($operand) ? get_class($operand) : gettype($operand) )); } } /** * Asserts that rounding mode is a valid integer value. * * @param int $roundingMode * * @throws \InvalidArgumentException If $roundingMode is not valid */ private function assertRoundingMode($roundingMode) { if (!in_array( $roundingMode, [ self::ROUND_HALF_DOWN, self::ROUND_HALF_EVEN, self::ROUND_HALF_ODD, self::ROUND_HALF_UP, self::ROUND_UP, self::ROUND_DOWN, self::ROUND_HALF_POSITIVE_INFINITY, self::ROUND_HALF_NEGATIVE_INFINITY, ], true )) { throw new \InvalidArgumentException( 'Rounding mode should be Money::ROUND_HALF_DOWN | '. 'Money::ROUND_HALF_EVEN | Money::ROUND_HALF_ODD | '. 'Money::ROUND_HALF_UP | Money::ROUND_UP | Money::ROUND_DOWN'. 'Money::ROUND_HALF_POSITIVE_INFINITY | Money::ROUND_HALF_NEGATIVE_INFINITY' ); } } /** * Returns a new Money object that represents * the multiplied value by the given factor. * * @param float|int|string $multiplier * @param int $roundingMode * * @return Money */ public function multiply($multiplier, $roundingMode = self::ROUND_HALF_UP) { $this->assertOperand($multiplier); $this->assertRoundingMode($roundingMode); $product = $this->round($this->getCalculator()->multiply($this->amount, $multiplier), $roundingMode); return $this->newInstance($product); } /** * Returns a new Money object that represents * the divided value by the given factor. * * @param float|int|string $divisor * @param int $roundingMode * * @return Money */ public function divide($divisor, $roundingMode = self::ROUND_HALF_UP) { $this->assertOperand($divisor); $this->assertRoundingMode($roundingMode); $divisor = (string) Number::fromNumber($divisor); if ($this->getCalculator()->compare($divisor, '0') === 0) { throw new \InvalidArgumentException('Division by zero'); } $quotient = $this->round($this->getCalculator()->divide($this->amount, $divisor), $roundingMode); return $this->newInstance($quotient); } /** * Returns a new Money object that represents * the remainder after dividing the value by * the given factor. * * @param Money $divisor * * @return Money */ public function mod(Money $divisor) { $this->assertSameCurrency($divisor); return new self($this->getCalculator()->mod($this->amount, $divisor->amount), $this->currency); } /** * Allocate the money according to a list of ratios. * * @param array $ratios * * @return Money[] */ public function allocate(array $ratios) { if (count($ratios) === 0) { throw new \InvalidArgumentException('Cannot allocate to none, ratios cannot be an empty array'); } $remainder = $this->amount; $results = []; $total = array_sum($ratios); if ($total <= 0) { throw new \InvalidArgumentException('Cannot allocate to none, sum of ratios must be greater than zero'); } foreach ($ratios as $key => $ratio) { if ($ratio < 0) { throw new \InvalidArgumentException('Cannot allocate to none, ratio must be zero or positive'); } $share = $this->getCalculator()->share($this->amount, $ratio, $total); $results[$key] = $this->newInstance($share); $remainder = $this->getCalculator()->subtract($remainder, $share); } if ($this->getCalculator()->compare($remainder, '0') === 0) { return $results; } $fractions = array_map(function ($ratio) use ($total) { $share = ($ratio / $total) * $this->amount; return $share - floor($share); }, $ratios); while ($this->getCalculator()->compare($remainder, '0') > 0) { $index = !empty($fractions) ? array_keys($fractions, max($fractions))[0] : 0; $results[$index]->amount = $this->getCalculator()->add($results[$index]->amount, '1'); $remainder = $this->getCalculator()->subtract($remainder, '1'); unset($fractions[$index]); } return $results; } /** * Allocate the money among N targets. * * @param int $n * * @return Money[] * * @throws \InvalidArgumentException If number of targets is not an integer */ public function allocateTo($n) { if (!is_int($n)) { throw new \InvalidArgumentException('Number of targets must be an integer'); } if ($n <= 0) { throw new \InvalidArgumentException('Cannot allocate to none, target must be greater than zero'); } return $this->allocate(array_fill(0, $n, 1)); } /** * @param Money $money * * @return string */ public function ratioOf(Money $money) { if ($money->isZero()) { throw new \InvalidArgumentException('Cannot calculate a ratio of zero'); } return $this->getCalculator()->divide($this->amount, $money->amount); } /** * @param string $amount * @param int $rounding_mode * * @return string */ private function round($amount, $rounding_mode) { $this->assertRoundingMode($rounding_mode); if ($rounding_mode === self::ROUND_UP) { return $this->getCalculator()->ceil($amount); } if ($rounding_mode === self::ROUND_DOWN) { return $this->getCalculator()->floor($amount); } return $this->getCalculator()->round($amount, $rounding_mode); } /** * @return Money */ public function absolute() { return $this->newInstance($this->getCalculator()->absolute($this->amount)); } /** * @return Money */ public function negative() { return $this->newInstance(0)->subtract($this); } /** * Checks if the value represented by this object is zero. * * @return bool */ public function isZero() { return $this->getCalculator()->compare($this->amount, 0) === 0; } /** * Checks if the value represented by this object is positive. * * @return bool */ public function isPositive() { return $this->getCalculator()->compare($this->amount, 0) > 0; } /** * Checks if the value represented by this object is negative. * * @return bool */ public function isNegative() { return $this->getCalculator()->compare($this->amount, 0) < 0; } /** * {@inheritdoc} * * @return array */ public function jsonSerialize() { return [ 'amount' => $this->amount, 'currency' => $this->currency->jsonSerialize(), ]; } /** * @param Money $first * @param Money ...$collection * * @return Money * * @psalm-pure */ public static function min(self $first, self ...$collection) { $min = $first; foreach ($collection as $money) { if ($money->lessThan($min)) { $min = $money; } } return $min; } /** * @param Money $first * @param Money ...$collection * * @return Money * * @psalm-pure */ public static function max(self $first, self ...$collection) { $max = $first; foreach ($collection as $money) { if ($money->greaterThan($max)) { $max = $money; } } return $max; } /** * @param Money $first * @param Money ...$collection * * @return Money * * @psalm-pure */ public static function sum(self $first, self ...$collection) { return $first->add(...$collection); } /** * @param Money $first * @param Money ...$collection * * @return Money * * @psalm-pure */ public static function avg(self $first, self ...$collection) { return $first->add(...$collection)->divide(func_num_args()); } /** * @param string $calculator */ public static function registerCalculator($calculator) { if (is_a($calculator, Calculator::class, true) === false) { throw new \InvalidArgumentException('Calculator must implement '.Calculator::class); } array_unshift(self::$calculators, $calculator); } /** * @return Calculator * * @throws \RuntimeException If cannot find calculator for money calculations */ private static function initializeCalculator() { $calculators = self::$calculators; foreach ($calculators as $calculator) { /** @var Calculator $calculator */ if ($calculator::supported()) { return new $calculator(); } } throw new \RuntimeException('Cannot find calculator for money calculations'); } /** * @return Calculator */ private function getCalculator() { if (null === self::$calculator) { self::$calculator = self::initializeCalculator(); } return self::$calculator; } }