PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.3
GiveWP – Donation Plugin and Fundraising Platform v4.16.3
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 1 year ago Currencies 1 year ago Exception 1 year ago Exchange 1 year ago Formatter 1 year ago PHPUnit 1 year ago Parser 1 year ago Calculator.php 4 years ago Converter.php 1 year ago Currencies.php 1 year ago Currency.php 1 year ago CurrencyPair.php 1 year ago Exception.php 4 years ago Exchange.php 1 year ago Money.php 1 year ago MoneyFactory.php 4 years ago MoneyFormatter.php 1 year ago MoneyParser.php 4 years ago Number.php 1 year ago
Money.php
614 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 *
65 * @throws \InvalidArgumentException If amount is not integer
66 */
67 public function __construct($amount, Currency $currency)
68 {
69 if (filter_var($amount, FILTER_VALIDATE_INT) === false) {
70 $numberFromString = Number::fromString($amount);
71 if (!$numberFromString->isInteger()) {
72 throw new \InvalidArgumentException('Amount must be an integer(ish) value');
73 }
74
75 $amount = $numberFromString->getIntegerPart();
76 }
77
78 $this->amount = (string) $amount;
79 $this->currency = $currency;
80 }
81
82 /**
83 * Returns a new Money instance based on the current one using the Currency.
84 *
85 * @param int|string $amount
86 *
87 * @return Money
88 *
89 * @throws \InvalidArgumentException If amount is not integer
90 */
91 private function newInstance($amount)
92 {
93 return new self($amount, $this->currency);
94 }
95
96 /**
97 * Checks whether a Money has the same Currency as this.
98 *
99 * @return bool
100 */
101 public function isSameCurrency(Money $other)
102 {
103 return $this->currency->equals($other->currency);
104 }
105
106 /**
107 * Asserts that a Money has the same currency as this.
108 *
109 * @throws \InvalidArgumentException If $other has a different currency
110 */
111 private function assertSameCurrency(Money $other)
112 {
113 if (!$this->isSameCurrency($other)) {
114 throw new \InvalidArgumentException('Currencies must be identical');
115 }
116 }
117
118 /**
119 * Checks whether the value represented by this object equals to the other.
120 *
121 * @return bool
122 */
123 public function equals(Money $other)
124 {
125 return $this->isSameCurrency($other) && $this->amount === $other->amount;
126 }
127
128 /**
129 * Returns an integer less than, equal to, or greater than zero
130 * if the value of this object is considered to be respectively
131 * less than, equal to, or greater than the other.
132 *
133 * @return int
134 */
135 public function compare(Money $other)
136 {
137 $this->assertSameCurrency($other);
138
139 return $this->getCalculator()->compare($this->amount, $other->amount);
140 }
141
142 /**
143 * Checks whether the value represented by this object is greater than the other.
144 *
145 * @return bool
146 */
147 public function greaterThan(Money $other)
148 {
149 return $this->compare($other) > 0;
150 }
151
152 /**
153 * @param \Money\Money $other
154 *
155 * @return bool
156 */
157 public function greaterThanOrEqual(Money $other)
158 {
159 return $this->compare($other) >= 0;
160 }
161
162 /**
163 * Checks whether the value represented by this object is less than the other.
164 *
165 * @return bool
166 */
167 public function lessThan(Money $other)
168 {
169 return $this->compare($other) < 0;
170 }
171
172 /**
173 * @param \Money\Money $other
174 *
175 * @return bool
176 */
177 public function lessThanOrEqual(Money $other)
178 {
179 return $this->compare($other) <= 0;
180 }
181
182 /**
183 * Returns the value represented by this object.
184 *
185 * @return string
186 */
187 public function getAmount()
188 {
189 return $this->amount;
190 }
191
192 /**
193 * Returns the currency of this object.
194 *
195 * @return Currency
196 */
197 public function getCurrency()
198 {
199 return $this->currency;
200 }
201
202 /**
203 * Returns a new Money object that represents
204 * the sum of this and an other Money object.
205 *
206 * @param Money[] $addends
207 *
208 * @return Money
209 */
210 public function add(Money ...$addends)
211 {
212 $amount = $this->amount;
213 $calculator = $this->getCalculator();
214
215 foreach ($addends as $addend) {
216 $this->assertSameCurrency($addend);
217
218 $amount = $calculator->add($amount, $addend->amount);
219 }
220
221 return new self($amount, $this->currency);
222 }
223
224 /**
225 * Returns a new Money object that represents
226 * the difference of this and an other Money object.
227 *
228 * @param Money[] $subtrahends
229 *
230 * @return Money
231 *
232 * @psalm-pure
233 */
234 public function subtract(Money ...$subtrahends)
235 {
236 $amount = $this->amount;
237 $calculator = $this->getCalculator();
238
239 foreach ($subtrahends as $subtrahend) {
240 $this->assertSameCurrency($subtrahend);
241
242 $amount = $calculator->subtract($amount, $subtrahend->amount);
243 }
244
245 return new self($amount, $this->currency);
246 }
247
248 /**
249 * Asserts that the operand is integer or float.
250 *
251 * @param float|int|string $operand
252 *
253 * @throws \InvalidArgumentException If $operand is neither integer nor float
254 */
255 private function assertOperand($operand)
256 {
257 if (!is_numeric($operand)) {
258 throw new \InvalidArgumentException(sprintf('Operand should be a numeric value, "%s" given.', is_object($operand) ? get_class($operand) : gettype($operand)));
259 }
260 }
261
262 /**
263 * Asserts that rounding mode is a valid integer value.
264 *
265 * @param int $roundingMode
266 *
267 * @throws \InvalidArgumentException If $roundingMode is not valid
268 */
269 private function assertRoundingMode($roundingMode)
270 {
271 if (!in_array(
272 $roundingMode, [
273 self::ROUND_HALF_DOWN, self::ROUND_HALF_EVEN, self::ROUND_HALF_ODD,
274 self::ROUND_HALF_UP, self::ROUND_UP, self::ROUND_DOWN,
275 self::ROUND_HALF_POSITIVE_INFINITY, self::ROUND_HALF_NEGATIVE_INFINITY,
276 ], true
277 )) {
278 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');
279 }
280 }
281
282 /**
283 * Returns a new Money object that represents
284 * the multiplied value by the given factor.
285 *
286 * @param float|int|string $multiplier
287 * @param int $roundingMode
288 *
289 * @return Money
290 */
291 public function multiply($multiplier, $roundingMode = self::ROUND_HALF_UP)
292 {
293 $this->assertOperand($multiplier);
294 $this->assertRoundingMode($roundingMode);
295
296 $product = $this->round($this->getCalculator()->multiply($this->amount, $multiplier), $roundingMode);
297
298 return $this->newInstance($product);
299 }
300
301 /**
302 * Returns a new Money object that represents
303 * the divided value by the given factor.
304 *
305 * @param float|int|string $divisor
306 * @param int $roundingMode
307 *
308 * @return Money
309 */
310 public function divide($divisor, $roundingMode = self::ROUND_HALF_UP)
311 {
312 $this->assertOperand($divisor);
313 $this->assertRoundingMode($roundingMode);
314
315 $divisor = (string) Number::fromNumber($divisor);
316
317 if ($this->getCalculator()->compare($divisor, '0') === 0) {
318 throw new \InvalidArgumentException('Division by zero');
319 }
320
321 $quotient = $this->round($this->getCalculator()->divide($this->amount, $divisor), $roundingMode);
322
323 return $this->newInstance($quotient);
324 }
325
326 /**
327 * Returns a new Money object that represents
328 * the remainder after dividing the value by
329 * the given factor.
330 *
331 * @return Money
332 */
333 public function mod(Money $divisor)
334 {
335 $this->assertSameCurrency($divisor);
336
337 return new self($this->getCalculator()->mod($this->amount, $divisor->amount), $this->currency);
338 }
339
340 /**
341 * Allocate the money according to a list of ratios.
342 *
343 * @return Money[]
344 */
345 public function allocate(array $ratios)
346 {
347 if (count($ratios) === 0) {
348 throw new \InvalidArgumentException('Cannot allocate to none, ratios cannot be an empty array');
349 }
350
351 $remainder = $this->amount;
352 $results = [];
353 $total = array_sum($ratios);
354
355 if ($total <= 0) {
356 throw new \InvalidArgumentException('Cannot allocate to none, sum of ratios must be greater than zero');
357 }
358
359 foreach ($ratios as $key => $ratio) {
360 if ($ratio < 0) {
361 throw new \InvalidArgumentException('Cannot allocate to none, ratio must be zero or positive');
362 }
363 $share = $this->getCalculator()->share($this->amount, $ratio, $total);
364 $results[$key] = $this->newInstance($share);
365 $remainder = $this->getCalculator()->subtract($remainder, $share);
366 }
367
368 if ($this->getCalculator()->compare($remainder, '0') === 0) {
369 return $results;
370 }
371
372 $fractions = array_map(function ($ratio) use ($total) {
373 $share = ($ratio / $total) * $this->amount;
374
375 return $share - floor($share);
376 }, $ratios);
377
378 while ($this->getCalculator()->compare($remainder, '0') > 0) {
379 $index = !empty($fractions) ? array_keys($fractions, max($fractions))[0] : 0;
380 $results[$index]->amount = $this->getCalculator()->add($results[$index]->amount, '1');
381 $remainder = $this->getCalculator()->subtract($remainder, '1');
382 unset($fractions[$index]);
383 }
384
385 return $results;
386 }
387
388 /**
389 * Allocate the money among N targets.
390 *
391 * @param int $n
392 *
393 * @return Money[]
394 *
395 * @throws \InvalidArgumentException If number of targets is not an integer
396 */
397 public function allocateTo($n)
398 {
399 if (!is_int($n)) {
400 throw new \InvalidArgumentException('Number of targets must be an integer');
401 }
402
403 if ($n <= 0) {
404 throw new \InvalidArgumentException('Cannot allocate to none, target must be greater than zero');
405 }
406
407 return $this->allocate(array_fill(0, $n, 1));
408 }
409
410 /**
411 * @return string
412 */
413 public function ratioOf(Money $money)
414 {
415 if ($money->isZero()) {
416 throw new \InvalidArgumentException('Cannot calculate a ratio of zero');
417 }
418
419 return $this->getCalculator()->divide($this->amount, $money->amount);
420 }
421
422 /**
423 * @param string $amount
424 * @param int $rounding_mode
425 *
426 * @return string
427 */
428 private function round($amount, $rounding_mode)
429 {
430 $this->assertRoundingMode($rounding_mode);
431
432 if ($rounding_mode === self::ROUND_UP) {
433 return $this->getCalculator()->ceil($amount);
434 }
435
436 if ($rounding_mode === self::ROUND_DOWN) {
437 return $this->getCalculator()->floor($amount);
438 }
439
440 return $this->getCalculator()->round($amount, $rounding_mode);
441 }
442
443 /**
444 * @return Money
445 */
446 public function absolute()
447 {
448 return $this->newInstance($this->getCalculator()->absolute($this->amount));
449 }
450
451 /**
452 * @return Money
453 */
454 public function negative()
455 {
456 return $this->newInstance(0)->subtract($this);
457 }
458
459 /**
460 * Checks if the value represented by this object is zero.
461 *
462 * @return bool
463 */
464 public function isZero()
465 {
466 return $this->getCalculator()->compare($this->amount, 0) === 0;
467 }
468
469 /**
470 * Checks if the value represented by this object is positive.
471 *
472 * @return bool
473 */
474 public function isPositive()
475 {
476 return $this->getCalculator()->compare($this->amount, 0) > 0;
477 }
478
479 /**
480 * Checks if the value represented by this object is negative.
481 *
482 * @return bool
483 */
484 public function isNegative()
485 {
486 return $this->getCalculator()->compare($this->amount, 0) < 0;
487 }
488
489 /**
490 * {@inheritdoc}
491 *
492 * @return array
493 */
494 #[\ReturnTypeWillChange]
495 public function jsonSerialize()
496 {
497 return [
498 'amount' => $this->amount,
499 'currency' => $this->currency->jsonSerialize(),
500 ];
501 }
502
503 /**
504 * @param Money $first
505 * @param Money ...$collection
506 *
507 * @return Money
508 *
509 * @psalm-pure
510 */
511 public static function min(self $first, self ...$collection)
512 {
513 $min = $first;
514
515 foreach ($collection as $money) {
516 if ($money->lessThan($min)) {
517 $min = $money;
518 }
519 }
520
521 return $min;
522 }
523
524 /**
525 * @param Money $first
526 * @param Money ...$collection
527 *
528 * @return Money
529 *
530 * @psalm-pure
531 */
532 public static function max(self $first, self ...$collection)
533 {
534 $max = $first;
535
536 foreach ($collection as $money) {
537 if ($money->greaterThan($max)) {
538 $max = $money;
539 }
540 }
541
542 return $max;
543 }
544
545 /**
546 * @param Money $first
547 * @param Money ...$collection
548 *
549 * @return Money
550 *
551 * @psalm-pure
552 */
553 public static function sum(self $first, self ...$collection)
554 {
555 return $first->add(...$collection);
556 }
557
558 /**
559 * @param Money $first
560 * @param Money ...$collection
561 *
562 * @return Money
563 *
564 * @psalm-pure
565 */
566 public static function avg(self $first, self ...$collection)
567 {
568 return $first->add(...$collection)->divide(func_num_args());
569 }
570
571 /**
572 * @param string $calculator
573 */
574 public static function registerCalculator($calculator)
575 {
576 if (is_a($calculator, Calculator::class, true) === false) {
577 throw new \InvalidArgumentException('Calculator must implement '.Calculator::class);
578 }
579
580 array_unshift(self::$calculators, $calculator);
581 }
582
583 /**
584 * @return Calculator
585 *
586 * @throws \RuntimeException If cannot find calculator for money calculations
587 */
588 private static function initializeCalculator()
589 {
590 $calculators = self::$calculators;
591
592 foreach ($calculators as $calculator) {
593 /** @var Calculator $calculator */
594 if ($calculator::supported()) {
595 return new $calculator();
596 }
597 }
598
599 throw new \RuntimeException('Cannot find calculator for money calculations');
600 }
601
602 /**
603 * @return Calculator
604 */
605 private function getCalculator()
606 {
607 if (null === self::$calculator) {
608 self::$calculator = self::initializeCalculator();
609 }
610
611 return self::$calculator;
612 }
613 }
614