PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.19.1
GiveWP – Donation Plugin and Fundraising Platform v3.19.1
4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / vendor / moneyphp / money / src / Money.php
give / vendor / moneyphp / money / src Last commit date
Calculator 4 years ago Currencies 4 years ago Exception 4 years ago Exchange 4 years ago Formatter 4 years ago PHPUnit 4 years ago Parser 4 years ago Calculator.php 4 years ago Converter.php 4 years ago Currencies.php 4 years ago Currency.php 4 years ago CurrencyPair.php 4 years ago Exception.php 4 years ago Exchange.php 4 years ago Money.php 4 years ago MoneyFactory.php 4 years ago MoneyFormatter.php 4 years ago MoneyParser.php 4 years ago Number.php 4 years ago
Money.php
640 lines
1 <?php
2
3 namespace Money;
4
5 use Money\Calculator\BcMathCalculator;
6 use Money\Calculator\GmpCalculator;
7 use Money\Calculator\PhpCalculator;
8
9 /**
10 * Money Value Object.
11 *
12 * @author Mathias Verraes
13 *
14 * @psalm-immutable
15 */
16 final class Money implements \JsonSerializable
17 {
18 use MoneyFactory;
19
20 const ROUND_HALF_UP = PHP_ROUND_HALF_UP;
21
22 const ROUND_HALF_DOWN = PHP_ROUND_HALF_DOWN;
23
24 const ROUND_HALF_EVEN = PHP_ROUND_HALF_EVEN;
25
26 const ROUND_HALF_ODD = PHP_ROUND_HALF_ODD;
27
28 const ROUND_UP = 5;
29
30 const ROUND_DOWN = 6;
31
32 const ROUND_HALF_POSITIVE_INFINITY = 7;
33
34 const ROUND_HALF_NEGATIVE_INFINITY = 8;
35
36 /**
37 * Internal value.
38 *
39 * @var string
40 */
41 private $amount;
42
43 /**
44 * @var Currency
45 */
46 private $currency;
47
48 /**
49 * @var Calculator
50 */
51 private static $calculator;
52
53 /**
54 * @var array
55 */
56 private static $calculators = [
57 BcMathCalculator::class,
58 GmpCalculator::class,
59 PhpCalculator::class,
60 ];
61
62 /**
63 * @param int|string $amount Amount, expressed in the smallest units of $currency (eg cents)
64 * @param Currency $currency
65 *
66 * @throws \InvalidArgumentException If amount is not integer
67 */
68 public function __construct($amount, Currency $currency)
69 {
70 if (filter_var($amount, FILTER_VALIDATE_INT) === false) {
71 $numberFromString = Number::fromString($amount);
72 if (!$numberFromString->isInteger()) {
73 throw new \InvalidArgumentException('Amount must be an integer(ish) value');
74 }
75
76 $amount = $numberFromString->getIntegerPart();
77 }
78
79 $this->amount = (string) $amount;
80 $this->currency = $currency;
81 }
82
83 /**
84 * Returns a new Money instance based on the current one using the Currency.
85 *
86 * @param int|string $amount
87 *
88 * @return Money
89 *
90 * @throws \InvalidArgumentException If amount is not integer
91 */
92 private function newInstance($amount)
93 {
94 return new self($amount, $this->currency);
95 }
96
97 /**
98 * Checks whether a Money has the same Currency as this.
99 *
100 * @param Money $other
101 *
102 * @return bool
103 */
104 public function isSameCurrency(Money $other)
105 {
106 return $this->currency->equals($other->currency);
107 }
108
109 /**
110 * Asserts that a Money has the same currency as this.
111 *
112 * @param Money $other
113 *
114 * @throws \InvalidArgumentException If $other has a different currency
115 */
116 private function assertSameCurrency(Money $other)
117 {
118 if (!$this->isSameCurrency($other)) {
119 throw new \InvalidArgumentException('Currencies must be identical');
120 }
121 }
122
123 /**
124 * Checks whether the value represented by this object equals to the other.
125 *
126 * @param Money $other
127 *
128 * @return bool
129 */
130 public function equals(Money $other)
131 {
132 return $this->isSameCurrency($other) && $this->amount === $other->amount;
133 }
134
135 /**
136 * Returns an integer less than, equal to, or greater than zero
137 * if the value of this object is considered to be respectively
138 * less than, equal to, or greater than the other.
139 *
140 * @param Money $other
141 *
142 * @return int
143 */
144 public function compare(Money $other)
145 {
146 $this->assertSameCurrency($other);
147
148 return $this->getCalculator()->compare($this->amount, $other->amount);
149 }
150
151 /**
152 * Checks whether the value represented by this object is greater than the other.
153 *
154 * @param Money $other
155 *
156 * @return bool
157 */
158 public function greaterThan(Money $other)
159 {
160 return $this->compare($other) > 0;
161 }
162
163 /**
164 * @param \Money\Money $other
165 *
166 * @return bool
167 */
168 public function greaterThanOrEqual(Money $other)
169 {
170 return $this->compare($other) >= 0;
171 }
172
173 /**
174 * Checks whether the value represented by this object is less than the other.
175 *
176 * @param Money $other
177 *
178 * @return bool
179 */
180 public function lessThan(Money $other)
181 {
182 return $this->compare($other) < 0;
183 }
184
185 /**
186 * @param \Money\Money $other
187 *
188 * @return bool
189 */
190 public function lessThanOrEqual(Money $other)
191 {
192 return $this->compare($other) <= 0;
193 }
194
195 /**
196 * Returns the value represented by this object.
197 *
198 * @return string
199 */
200 public function getAmount()
201 {
202 return $this->amount;
203 }
204
205 /**
206 * Returns the currency of this object.
207 *
208 * @return Currency
209 */
210 public function getCurrency()
211 {
212 return $this->currency;
213 }
214
215 /**
216 * Returns a new Money object that represents
217 * the sum of this and an other Money object.
218 *
219 * @param Money[] $addends
220 *
221 * @return Money
222 */
223 public function add(Money ...$addends)
224 {
225 $amount = $this->amount;
226 $calculator = $this->getCalculator();
227
228 foreach ($addends as $addend) {
229 $this->assertSameCurrency($addend);
230
231 $amount = $calculator->add($amount, $addend->amount);
232 }
233
234 return new self($amount, $this->currency);
235 }
236
237 /**
238 * Returns a new Money object that represents
239 * the difference of this and an other Money object.
240 *
241 * @param Money[] $subtrahends
242 *
243 * @return Money
244 *
245 * @psalm-pure
246 */
247 public function subtract(Money ...$subtrahends)
248 {
249 $amount = $this->amount;
250 $calculator = $this->getCalculator();
251
252 foreach ($subtrahends as $subtrahend) {
253 $this->assertSameCurrency($subtrahend);
254
255 $amount = $calculator->subtract($amount, $subtrahend->amount);
256 }
257
258 return new self($amount, $this->currency);
259 }
260
261 /**
262 * Asserts that the operand is integer or float.
263 *
264 * @param float|int|string $operand
265 *
266 * @throws \InvalidArgumentException If $operand is neither integer nor float
267 */
268 private function assertOperand($operand)
269 {
270 if (!is_numeric($operand)) {
271 throw new \InvalidArgumentException(sprintf(
272 'Operand should be a numeric value, "%s" given.',
273 is_object($operand) ? get_class($operand) : gettype($operand)
274 ));
275 }
276 }
277
278 /**
279 * Asserts that rounding mode is a valid integer value.
280 *
281 * @param int $roundingMode
282 *
283 * @throws \InvalidArgumentException If $roundingMode is not valid
284 */
285 private function assertRoundingMode($roundingMode)
286 {
287 if (!in_array(
288 $roundingMode, [
289 self::ROUND_HALF_DOWN, self::ROUND_HALF_EVEN, self::ROUND_HALF_ODD,
290 self::ROUND_HALF_UP, self::ROUND_UP, self::ROUND_DOWN,
291 self::ROUND_HALF_POSITIVE_INFINITY, self::ROUND_HALF_NEGATIVE_INFINITY,
292 ], true
293 )) {
294 throw new \InvalidArgumentException(
295 'Rounding mode should be Money::ROUND_HALF_DOWN | '.
296 'Money::ROUND_HALF_EVEN | Money::ROUND_HALF_ODD | '.
297 'Money::ROUND_HALF_UP | Money::ROUND_UP | Money::ROUND_DOWN'.
298 'Money::ROUND_HALF_POSITIVE_INFINITY | Money::ROUND_HALF_NEGATIVE_INFINITY'
299 );
300 }
301 }
302
303 /**
304 * Returns a new Money object that represents
305 * the multiplied value by the given factor.
306 *
307 * @param float|int|string $multiplier
308 * @param int $roundingMode
309 *
310 * @return Money
311 */
312 public function multiply($multiplier, $roundingMode = self::ROUND_HALF_UP)
313 {
314 $this->assertOperand($multiplier);
315 $this->assertRoundingMode($roundingMode);
316
317 $product = $this->round($this->getCalculator()->multiply($this->amount, $multiplier), $roundingMode);
318
319 return $this->newInstance($product);
320 }
321
322 /**
323 * Returns a new Money object that represents
324 * the divided value by the given factor.
325 *
326 * @param float|int|string $divisor
327 * @param int $roundingMode
328 *
329 * @return Money
330 */
331 public function divide($divisor, $roundingMode = self::ROUND_HALF_UP)
332 {
333 $this->assertOperand($divisor);
334 $this->assertRoundingMode($roundingMode);
335
336 $divisor = (string) Number::fromNumber($divisor);
337
338 if ($this->getCalculator()->compare($divisor, '0') === 0) {
339 throw new \InvalidArgumentException('Division by zero');
340 }
341
342 $quotient = $this->round($this->getCalculator()->divide($this->amount, $divisor), $roundingMode);
343
344 return $this->newInstance($quotient);
345 }
346
347 /**
348 * Returns a new Money object that represents
349 * the remainder after dividing the value by
350 * the given factor.
351 *
352 * @param Money $divisor
353 *
354 * @return Money
355 */
356 public function mod(Money $divisor)
357 {
358 $this->assertSameCurrency($divisor);
359
360 return new self($this->getCalculator()->mod($this->amount, $divisor->amount), $this->currency);
361 }
362
363 /**
364 * Allocate the money according to a list of ratios.
365 *
366 * @param array $ratios
367 *
368 * @return Money[]
369 */
370 public function allocate(array $ratios)
371 {
372 if (count($ratios) === 0) {
373 throw new \InvalidArgumentException('Cannot allocate to none, ratios cannot be an empty array');
374 }
375
376 $remainder = $this->amount;
377 $results = [];
378 $total = array_sum($ratios);
379
380 if ($total <= 0) {
381 throw new \InvalidArgumentException('Cannot allocate to none, sum of ratios must be greater than zero');
382 }
383
384 foreach ($ratios as $key => $ratio) {
385 if ($ratio < 0) {
386 throw new \InvalidArgumentException('Cannot allocate to none, ratio must be zero or positive');
387 }
388 $share = $this->getCalculator()->share($this->amount, $ratio, $total);
389 $results[$key] = $this->newInstance($share);
390 $remainder = $this->getCalculator()->subtract($remainder, $share);
391 }
392
393 if ($this->getCalculator()->compare($remainder, '0') === 0) {
394 return $results;
395 }
396
397 $fractions = array_map(function ($ratio) use ($total) {
398 $share = ($ratio / $total) * $this->amount;
399
400 return $share - floor($share);
401 }, $ratios);
402
403 while ($this->getCalculator()->compare($remainder, '0') > 0) {
404 $index = !empty($fractions) ? array_keys($fractions, max($fractions))[0] : 0;
405 $results[$index]->amount = $this->getCalculator()->add($results[$index]->amount, '1');
406 $remainder = $this->getCalculator()->subtract($remainder, '1');
407 unset($fractions[$index]);
408 }
409
410 return $results;
411 }
412
413 /**
414 * Allocate the money among N targets.
415 *
416 * @param int $n
417 *
418 * @return Money[]
419 *
420 * @throws \InvalidArgumentException If number of targets is not an integer
421 */
422 public function allocateTo($n)
423 {
424 if (!is_int($n)) {
425 throw new \InvalidArgumentException('Number of targets must be an integer');
426 }
427
428 if ($n <= 0) {
429 throw new \InvalidArgumentException('Cannot allocate to none, target must be greater than zero');
430 }
431
432 return $this->allocate(array_fill(0, $n, 1));
433 }
434
435 /**
436 * @param Money $money
437 *
438 * @return string
439 */
440 public function ratioOf(Money $money)
441 {
442 if ($money->isZero()) {
443 throw new \InvalidArgumentException('Cannot calculate a ratio of zero');
444 }
445
446 return $this->getCalculator()->divide($this->amount, $money->amount);
447 }
448
449 /**
450 * @param string $amount
451 * @param int $rounding_mode
452 *
453 * @return string
454 */
455 private function round($amount, $rounding_mode)
456 {
457 $this->assertRoundingMode($rounding_mode);
458
459 if ($rounding_mode === self::ROUND_UP) {
460 return $this->getCalculator()->ceil($amount);
461 }
462
463 if ($rounding_mode === self::ROUND_DOWN) {
464 return $this->getCalculator()->floor($amount);
465 }
466
467 return $this->getCalculator()->round($amount, $rounding_mode);
468 }
469
470 /**
471 * @return Money
472 */
473 public function absolute()
474 {
475 return $this->newInstance($this->getCalculator()->absolute($this->amount));
476 }
477
478 /**
479 * @return Money
480 */
481 public function negative()
482 {
483 return $this->newInstance(0)->subtract($this);
484 }
485
486 /**
487 * Checks if the value represented by this object is zero.
488 *
489 * @return bool
490 */
491 public function isZero()
492 {
493 return $this->getCalculator()->compare($this->amount, 0) === 0;
494 }
495
496 /**
497 * Checks if the value represented by this object is positive.
498 *
499 * @return bool
500 */
501 public function isPositive()
502 {
503 return $this->getCalculator()->compare($this->amount, 0) > 0;
504 }
505
506 /**
507 * Checks if the value represented by this object is negative.
508 *
509 * @return bool
510 */
511 public function isNegative()
512 {
513 return $this->getCalculator()->compare($this->amount, 0) < 0;
514 }
515
516 /**
517 * {@inheritdoc}
518 *
519 * @return array
520 */
521 public function jsonSerialize()
522 {
523 return [
524 'amount' => $this->amount,
525 'currency' => $this->currency->jsonSerialize(),
526 ];
527 }
528
529 /**
530 * @param Money $first
531 * @param Money ...$collection
532 *
533 * @return Money
534 *
535 * @psalm-pure
536 */
537 public static function min(self $first, self ...$collection)
538 {
539 $min = $first;
540
541 foreach ($collection as $money) {
542 if ($money->lessThan($min)) {
543 $min = $money;
544 }
545 }
546
547 return $min;
548 }
549
550 /**
551 * @param Money $first
552 * @param Money ...$collection
553 *
554 * @return Money
555 *
556 * @psalm-pure
557 */
558 public static function max(self $first, self ...$collection)
559 {
560 $max = $first;
561
562 foreach ($collection as $money) {
563 if ($money->greaterThan($max)) {
564 $max = $money;
565 }
566 }
567
568 return $max;
569 }
570
571 /**
572 * @param Money $first
573 * @param Money ...$collection
574 *
575 * @return Money
576 *
577 * @psalm-pure
578 */
579 public static function sum(self $first, self ...$collection)
580 {
581 return $first->add(...$collection);
582 }
583
584 /**
585 * @param Money $first
586 * @param Money ...$collection
587 *
588 * @return Money
589 *
590 * @psalm-pure
591 */
592 public static function avg(self $first, self ...$collection)
593 {
594 return $first->add(...$collection)->divide(func_num_args());
595 }
596
597 /**
598 * @param string $calculator
599 */
600 public static function registerCalculator($calculator)
601 {
602 if (is_a($calculator, Calculator::class, true) === false) {
603 throw new \InvalidArgumentException('Calculator must implement '.Calculator::class);
604 }
605
606 array_unshift(self::$calculators, $calculator);
607 }
608
609 /**
610 * @return Calculator
611 *
612 * @throws \RuntimeException If cannot find calculator for money calculations
613 */
614 private static function initializeCalculator()
615 {
616 $calculators = self::$calculators;
617
618 foreach ($calculators as $calculator) {
619 /** @var Calculator $calculator */
620 if ($calculator::supported()) {
621 return new $calculator();
622 }
623 }
624
625 throw new \RuntimeException('Cannot find calculator for money calculations');
626 }
627
628 /**
629 * @return Calculator
630 */
631 private function getCalculator()
632 {
633 if (null === self::$calculator) {
634 self::$calculator = self::initializeCalculator();
635 }
636
637 return self::$calculator;
638 }
639 }
640