• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / money / 21968458739

12 Feb 2026 11:31PM UTC coverage: 98.05% (-1.7%) from 99.796%
21968458739

push

github

BenMorel
Rename AbstractMoney::to() -> toContext()

6 of 6 new or added lines in 2 files covered. (100.0%)

9 existing lines in 1 file now uncovered.

553 of 564 relevant lines covered (98.05%)

28.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.74
/src/Money.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Money;
6

7
use Brick\Math\BigDecimal;
8
use Brick\Math\BigInteger;
9
use Brick\Math\BigNumber;
10
use Brick\Math\BigRational;
11
use Brick\Math\Exception\MathException;
12
use Brick\Math\Exception\NumberFormatException;
13
use Brick\Math\Exception\RoundingNecessaryException;
14
use Brick\Math\RoundingMode;
15
use Brick\Money\Context\DefaultContext;
16
use Brick\Money\Exception\MoneyMismatchException;
17
use Brick\Money\Exception\UnknownCurrencyException;
18
use Brick\Money\Formatter\MoneyLocaleFormatter;
19
use InvalidArgumentException;
20
use Override;
21

22
use function array_fill;
23
use function array_map;
24
use function array_sum;
25
use function array_values;
26
use function intdiv;
27
use function trigger_error;
28

29
use const E_USER_DEPRECATED;
30

31
/**
32
 * A monetary value in a given currency. This class is immutable.
33
 *
34
 * Money has an amount, a currency, and a context. The context defines the scale of the amount, and an optional cash
35
 * rounding step, for monies that do not have coins or notes for their smallest units.
36
 *
37
 * All operations on a Money return another Money with the same context. The available contexts are:
38
 *
39
 * - DefaultContext handles monies with the default scale for the currency.
40
 * - CashContext is similar to DefaultContext, but supports a cash rounding step.
41
 * - CustomContext handles monies with a custom scale and optionally step.
42
 * - AutoContext automatically adjusts the scale of the money to the minimum required.
43
 */
44
final readonly class Money extends AbstractMoney
45
{
46
    /**
47
     * @param BigDecimal $amount   The amount.
48
     * @param Currency   $currency The currency.
49
     * @param Context    $context  The context that defines the capability of this Money.
50
     */
51
    private function __construct(
52
        private BigDecimal $amount,
53
        private Currency $currency,
54
        private Context $context,
55
    ) {
56
    }
420✔
57

58
    /**
59
     * Returns the minimum of the given monies.
60
     *
61
     * If several monies are equal to the minimum value, the first one is returned.
62
     *
63
     * @param Money $money     The first money.
64
     * @param Money ...$monies The subsequent monies.
65
     *
66
     * @throws MoneyMismatchException If all the monies are not in the same currency.
67
     */
68
    public static function min(Money $money, Money ...$monies): Money
69
    {
70
        $min = $money;
4✔
71

72
        foreach ($monies as $money) {
4✔
73
            if ($money->isLessThan($min)) {
4✔
74
                $min = $money;
2✔
75
            }
76
        }
77

78
        return $min;
3✔
79
    }
80

81
    /**
82
     * Returns the maximum of the given monies.
83
     *
84
     * If several monies are equal to the maximum value, the first one is returned.
85
     *
86
     * @param Money $money     The first money.
87
     * @param Money ...$monies The subsequent monies.
88
     *
89
     * @throws MoneyMismatchException If all the monies are not in the same currency.
90
     */
91
    public static function max(Money $money, Money ...$monies): Money
92
    {
93
        $max = $money;
4✔
94

95
        foreach ($monies as $money) {
4✔
96
            if ($money->isGreaterThan($max)) {
4✔
97
                $max = $money;
2✔
98
            }
99
        }
100

101
        return $max;
3✔
102
    }
103

104
    /**
105
     * Returns the sum of the given monies.
106
     *
107
     * The monies must share the same currency and context.
108
     *
109
     * @param Money $money     The first money.
110
     * @param Money ...$monies The subsequent monies.
111
     *
112
     * @throws MoneyMismatchException If all the monies are not in the same currency and context.
113
     */
114
    public static function sum(Money $money, Money ...$monies): Money
115
    {
UNCOV
116
        $sum = $money;
×
117

UNCOV
118
        foreach ($monies as $money) {
×
UNCOV
119
            $sum = $sum->plus($money);
×
120
        }
121

UNCOV
122
        return $sum;
×
123
    }
124

125
    /**
126
     * Returns the total of the given monies.
127
     *
128
     * The monies must share the same currency and context.
129
     *
130
     * @deprecated Use Money::sum() instead.
131
     *
132
     * @param Money $money     The first money.
133
     * @param Money ...$monies The subsequent monies.
134
     *
135
     * @throws MoneyMismatchException If all the monies are not in the same currency and context.
136
     */
137
    public static function total(Money $money, Money ...$monies): Money
138
    {
139
        trigger_error(
2✔
140
            'Money::total() is deprecated, and will be removed in a future version. Use Money::sum() instead.',
2✔
141
            E_USER_DEPRECATED,
2✔
142
        );
2✔
143

144
        $total = $money;
2✔
145

146
        foreach ($monies as $money) {
2✔
147
            $total = $total->plus($money);
2✔
148
        }
149

150
        return $total;
1✔
151
    }
152

153
    /**
154
     * Creates a Money from a rational amount, a currency, and a context.
155
     *
156
     * @param BigNumber    $amount       The amount.
157
     * @param Currency     $currency     The currency.
158
     * @param Context      $context      The context.
159
     * @param RoundingMode $roundingMode An optional rounding mode if the amount does not fit the context.
160
     *
161
     * @throws RoundingNecessaryException If RoundingMode::Unnecessary is used but rounding is necessary.
162
     */
163
    public static function create(BigNumber $amount, Currency $currency, Context $context, RoundingMode $roundingMode = RoundingMode::Unnecessary): Money
164
    {
165
        $amount = $context->applyTo($amount, $currency, $roundingMode);
424✔
166

167
        return new Money($amount, $currency, $context);
420✔
168
    }
169

170
    /**
171
     * Returns a Money of the given amount and currency.
172
     *
173
     * By default, the money is created with a DefaultContext. This means that the amount is scaled to match the
174
     * currency's default fraction digits. For example, `Money::of('2.5', 'USD')` will yield `USD 2.50`.
175
     * If the amount cannot be safely converted to this scale, an exception is thrown.
176
     *
177
     * To override this behaviour, a Context instance can be provided.
178
     * Operations on this Money return a Money with the same context.
179
     *
180
     * @param BigNumber|int|string $amount       The monetary amount.
181
     * @param Currency|string      $currency     The Currency instance or ISO currency code.
182
     * @param Context|null         $context      An optional Context, defaults to DefaultContext.
183
     * @param RoundingMode         $roundingMode An optional RoundingMode, if the amount does not fit the context.
184
     *
185
     * @throws NumberFormatException      If the amount is a string in a non-supported format.
186
     * @throws UnknownCurrencyException   If the currency is an unknown currency code.
187
     * @throws RoundingNecessaryException If the rounding mode is RoundingMode::Unnecessary, and rounding is necessary
188
     *                                    to represent the amount at the requested scale.
189
     */
190
    public static function of(
191
        BigNumber|int|string $amount,
192
        Currency|string $currency,
193
        ?Context $context = new DefaultContext(),
194
        RoundingMode $roundingMode = RoundingMode::Unnecessary,
195
    ): Money {
196
        if (! $currency instanceof Currency) {
399✔
197
            $currency = Currency::of($currency);
395✔
198
        }
199

200
        if ($context === null) {
399✔
201
            trigger_error(
3✔
202
                'Passing null for the $context parameter to Money::of() is deprecated, use named arguments to skip to rounding mode.',
3✔
203
                E_USER_DEPRECATED,
3✔
204
            );
3✔
205

206
            $context = new DefaultContext();
3✔
207
        }
208

209
        $amount = BigNumber::of($amount);
399✔
210

211
        return self::create($amount, $currency, $context, $roundingMode);
398✔
212
    }
213

214
    /**
215
     * Returns a Money from a number of minor units.
216
     *
217
     * By default, the money is created with a DefaultContext. This means that the amount is scaled to match the
218
     * currency's default fraction digits. For example, `Money::ofMinor(1234, 'USD')` will yield `USD 12.34`.
219
     * If the amount cannot be safely converted to this scale, an exception is thrown.
220
     *
221
     * @param BigNumber|int|string $minorAmount  The amount, in minor currency units.
222
     * @param Currency|string      $currency     The Currency instance or ISO currency code.
223
     * @param Context|null         $context      An optional Context, defaults to DefaultContext.
224
     * @param RoundingMode         $roundingMode An optional RoundingMode, if the amount does not fit the context.
225
     *
226
     * @throws NumberFormatException      If the amount is a string in a non-supported format.
227
     * @throws UnknownCurrencyException   If the currency is an unknown currency code.
228
     * @throws RoundingNecessaryException If the rounding mode is RoundingMode::Unnecessary, and rounding is necessary
229
     *                                    to represent the amount at the requested scale.
230
     */
231
    public static function ofMinor(
232
        BigNumber|int|string $minorAmount,
233
        Currency|string $currency,
234
        ?Context $context = new DefaultContext(),
235
        RoundingMode $roundingMode = RoundingMode::Unnecessary,
236
    ): Money {
237
        if (! $currency instanceof Currency) {
6✔
238
            $currency = Currency::of($currency);
6✔
239
        }
240

241
        if ($context === null) {
6✔
UNCOV
242
            trigger_error(
×
UNCOV
243
                'Passing null for the $context parameter to Money::ofMinor() is deprecated, use named arguments to skip to rounding mode.',
×
UNCOV
244
                E_USER_DEPRECATED,
×
UNCOV
245
            );
×
246

UNCOV
247
            $context = new DefaultContext();
×
248
        }
249

250
        $amount = BigRational::of($minorAmount)->dividedBy(10 ** $currency->getDefaultFractionDigits());
6✔
251

252
        return self::create($amount, $currency, $context, $roundingMode);
5✔
253
    }
254

255
    /**
256
     * Returns a Money with zero value, in the given currency.
257
     *
258
     * By default, the money is created with a DefaultContext: it has the default scale for the currency.
259
     * A Context instance can be provided to override the default.
260
     *
261
     * @param Currency|string $currency The Currency instance or ISO currency code.
262
     * @param Context|null    $context  An optional context, defaults to DefaultContext.
263
     */
264
    public static function zero(Currency|string $currency, ?Context $context = new DefaultContext()): Money
265
    {
266
        if (! $currency instanceof Currency) {
5✔
267
            $currency = Currency::of($currency);
5✔
268
        }
269

270
        if ($context === null) {
5✔
271
            trigger_error(
3✔
272
                'Passing null for the $context parameter to Money::zero() is deprecated, use named arguments to skip to rounding mode.',
3✔
273
                E_USER_DEPRECATED,
3✔
274
            );
3✔
275

276
            $context = new DefaultContext();
3✔
277
        }
278

279
        $amount = BigDecimal::zero();
5✔
280

281
        return self::create($amount, $currency, $context);
5✔
282
    }
283

284
    /**
285
     * Returns the amount of this Money, as a BigDecimal.
286
     */
287
    #[Override]
288
    public function getAmount(): BigDecimal
289
    {
290
        return $this->amount;
297✔
291
    }
292

293
    /**
294
     * Returns the amount of this Money in minor units (cents) for the currency.
295
     *
296
     * The value is returned as a BigDecimal. If this Money has a scale greater than that of the currency, the result
297
     * will have a non-zero scale.
298
     *
299
     * For example, `USD 1.23` will return a BigDecimal of `123`, while `USD 1.2345` will return `123.45`.
300
     */
301
    public function getMinorAmount(): BigDecimal
302
    {
303
        return $this->amount->withPointMovedRight($this->currency->getDefaultFractionDigits());
5✔
304
    }
305

306
    /**
307
     * Returns a BigInteger containing the unscaled value (all digits) of this money.
308
     *
309
     * For example, `123.4567 USD` will return a BigInteger of `1234567`.
310
     *
311
     * @deprecated Use getAmount()->getUnscaledValue() instead.
312
     */
313
    public function getUnscaledAmount(): BigInteger
314
    {
315
        trigger_error(
1✔
316
            'Money::getUnscaledAmount() is deprecated, and will be removed in a future version. ' .
1✔
317
            'Use getAmount()->getUnscaledValue() instead.',
1✔
318
            E_USER_DEPRECATED,
1✔
319
        );
1✔
320

321
        return $this->amount->getUnscaledValue();
1✔
322
    }
323

324
    /**
325
     * Returns the Currency of this Money.
326
     */
327
    #[Override]
328
    public function getCurrency(): Currency
329
    {
330
        return $this->currency;
200✔
331
    }
332

333
    /**
334
     * Returns the Context of this Money.
335
     */
336
    public function getContext(): Context
337
    {
338
        return $this->context;
48✔
339
    }
340

341
    /**
342
     * Returns the sum of this Money and the given amount.
343
     *
344
     * If the operand is a Money, it must have the same context as this Money, or an exception is thrown.
345
     * This is by design, to ensure that contexts are not mixed accidentally.
346
     * If you do need to add a Money in a different context, you can use `plus($money->toRational())`.
347
     *
348
     * The resulting Money has the same context as this Money. If the result needs rounding to fit this context, a
349
     * rounding mode can be provided. If a rounding mode is not provided and rounding is necessary, an exception is
350
     * thrown.
351
     *
352
     * @param AbstractMoney|BigNumber|int|string $that         The money or amount to add.
353
     * @param RoundingMode                       $roundingMode An optional RoundingMode constant.
354
     *
355
     * @throws MathException          If the argument is an invalid number or rounding is necessary.
356
     * @throws MoneyMismatchException If the argument is a money in a different currency or in a different context.
357
     */
358
    public function plus(AbstractMoney|BigNumber|int|string $that, RoundingMode $roundingMode = RoundingMode::Unnecessary): Money
359
    {
360
        $amount = $this->getAmountOf($that);
35✔
361

362
        if ($that instanceof Money) {
33✔
363
            $this->checkContext($that->getContext(), __FUNCTION__);
20✔
364

365
            if ($this->context->isFixedScale()) {
19✔
366
                return new Money($this->amount->plus($that->amount), $this->currency, $this->context);
17✔
367
            }
368
        }
369

370
        $amount = $this->amount->toBigRational()->plus($amount);
15✔
371

372
        return self::create($amount, $this->currency, $this->context, $roundingMode);
15✔
373
    }
374

375
    /**
376
     * Returns the difference of this Money and the given amount.
377
     *
378
     * If the operand is a Money, it must have the same context as this Money, or an exception is thrown.
379
     * This is by design, to ensure that contexts are not mixed accidentally.
380
     * If you do need to subtract a Money in a different context, you can use `minus($money->toRational())`.
381
     *
382
     * The resulting Money has the same context as this Money. If the result needs rounding to fit this context, a
383
     * rounding mode can be provided. If a rounding mode is not provided and rounding is necessary, an exception is
384
     * thrown.
385
     *
386
     * @param AbstractMoney|BigNumber|int|string $that         The money or amount to subtract.
387
     * @param RoundingMode                       $roundingMode An optional RoundingMode constant.
388
     *
389
     * @throws MathException          If the argument is an invalid number or rounding is necessary.
390
     * @throws MoneyMismatchException If the argument is a money in a different currency or in a different context.
391
     */
392
    public function minus(AbstractMoney|BigNumber|int|string $that, RoundingMode $roundingMode = RoundingMode::Unnecessary): Money
393
    {
394
        $amount = $this->getAmountOf($that);
49✔
395

396
        if ($that instanceof Money) {
48✔
397
            $this->checkContext($that->getContext(), __FUNCTION__);
39✔
398

399
            if ($this->context->isFixedScale()) {
39✔
400
                return new Money($this->amount->minus($that->amount), $this->currency, $this->context);
34✔
401
            }
402
        }
403

404
        $amount = $this->amount->toBigRational()->minus($amount);
14✔
405

406
        return self::create($amount, $this->currency, $this->context, $roundingMode);
14✔
407
    }
408

409
    /**
410
     * Returns the product of this Money and the given number.
411
     *
412
     * The resulting Money has the same context as this Money. If the result needs rounding to fit this context, a
413
     * rounding mode can be provided. If a rounding mode is not provided and rounding is necessary, an exception is
414
     * thrown.
415
     *
416
     * @param BigNumber|int|string $that         The multiplier.
417
     * @param RoundingMode         $roundingMode An optional RoundingMode constant.
418
     *
419
     * @throws MathException If the argument is an invalid number or rounding is necessary.
420
     */
421
    public function multipliedBy(BigNumber|int|string $that, RoundingMode $roundingMode = RoundingMode::Unnecessary): Money
422
    {
423
        $amount = $this->amount->toBigRational()->multipliedBy($that);
48✔
424

425
        return self::create($amount, $this->currency, $this->context, $roundingMode);
48✔
426
    }
427

428
    /**
429
     * Returns the result of the division of this Money by the given number.
430
     *
431
     * The resulting Money has the same context as this Money. If the result needs rounding to fit this context, a
432
     * rounding mode can be provided. If a rounding mode is not provided and rounding is necessary, an exception is
433
     * thrown.
434
     *
435
     * @param BigNumber|int|string $that         The divisor.
436
     * @param RoundingMode         $roundingMode An optional RoundingMode constant.
437
     *
438
     * @throws MathException If the argument is an invalid number or is zero, or rounding is necessary.
439
     */
440
    public function dividedBy(BigNumber|int|string $that, RoundingMode $roundingMode = RoundingMode::Unnecessary): Money
441
    {
442
        $amount = $this->amount->toBigRational()->dividedBy($that);
31✔
443

444
        return self::create($amount, $this->currency, $this->context, $roundingMode);
30✔
445
    }
446

447
    /**
448
     * Returns the quotient of the division of this Money by the given number.
449
     *
450
     * The given number must be a integer value. The resulting Money has the same context as this Money.
451
     * This method can serve as a basis for a money allocation algorithm.
452
     *
453
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigInteger.
454
     *
455
     * @throws MathException If the divisor cannot be converted to a BigInteger.
456
     */
457
    public function quotient(BigNumber|int|string $that): Money
458
    {
459
        $that = BigInteger::of($that);
19✔
460
        $step = $this->context->getStep();
19✔
461

462
        $scale = $this->amount->getScale();
19✔
463
        $amount = $this->amount->withPointMovedRight($scale)->dividedBy($step, 0);
19✔
464

465
        $q = $amount->quotient($that);
19✔
466
        $q = $q->multipliedBy($step)->withPointMovedLeft($scale);
19✔
467

468
        return new Money($q, $this->currency, $this->context);
19✔
469
    }
470

471
    /**
472
     * Returns the quotient and the remainder of the division of this Money by the given number.
473
     *
474
     * The given number must be an integer value. The resulting monies have the same context as this Money.
475
     * This method can serve as a basis for a money allocation algorithm.
476
     *
477
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigInteger.
478
     *
479
     * @return array{Money, Money} The quotient and the remainder.
480
     *
481
     * @throws MathException If the divisor cannot be converted to a BigInteger.
482
     */
483
    public function quotientAndRemainder(BigNumber|int|string $that): array
484
    {
485
        $that = BigInteger::of($that);
24✔
486
        $step = $this->context->getStep();
23✔
487

488
        $scale = $this->amount->getScale();
23✔
489
        $amount = $this->amount->withPointMovedRight($scale)->dividedBy($step, 0);
23✔
490

491
        [$q, $r] = $amount->quotientAndRemainder($that);
23✔
492

493
        $q = $q->multipliedBy($step)->withPointMovedLeft($scale);
23✔
494
        $r = $r->multipliedBy($step)->withPointMovedLeft($scale);
23✔
495

496
        $quotient = new Money($q, $this->currency, $this->context);
23✔
497
        $remainder = new Money($r, $this->currency, $this->context);
23✔
498

499
        return [$quotient, $remainder];
23✔
500
    }
501

502
    /**
503
     * Allocates this Money according to a list of ratios.
504
     *
505
     * If the allocation yields a remainder, its amount is split over the first monies in the list,
506
     * so that the total of the resulting monies is always equal to this Money.
507
     *
508
     * For example, given a `USD 49.99` money in the default context,
509
     * `allocate(1, 2, 3, 4)` returns [`USD 5.00`, `USD 10.00`, `USD 15.00`, `USD 19.99`]
510
     *
511
     * The resulting monies have the same context as this Money.
512
     *
513
     * @param int[] $ratios The ratios.
514
     *
515
     * @return Money[]
516
     *
517
     * @throws InvalidArgumentException If called with invalid parameters.
518
     */
519
    public function allocate(int ...$ratios): array
520
    {
521
        if (! $ratios) {
22✔
522
            throw new InvalidArgumentException('Cannot allocate() an empty list of ratios.');
1✔
523
        }
524

525
        foreach ($ratios as $ratio) {
21✔
526
            if ($ratio < 0) {
21✔
527
                throw new InvalidArgumentException('Cannot allocate() negative ratios.');
1✔
528
            }
529
        }
530

531
        $total = array_sum($ratios);
20✔
532

533
        if ($total === 0) {
20✔
534
            throw new InvalidArgumentException('Cannot allocate() to zero ratios only.');
1✔
535
        }
536

537
        $step = $this->context->getStep();
19✔
538

539
        $monies = [];
19✔
540

541
        $unit = BigDecimal::ofUnscaledValue($step, $this->amount->getScale());
19✔
542
        $unit = new Money($unit, $this->currency, $this->context);
19✔
543

544
        if ($this->isNegative()) {
19✔
545
            $unit = $unit->negated();
1✔
546
        }
547

548
        $remainder = $this;
19✔
549

550
        foreach ($ratios as $ratio) {
19✔
551
            $money = $this->multipliedBy($ratio)->quotient($total);
19✔
552
            $remainder = $remainder->minus($money);
19✔
553
            $monies[] = $money;
19✔
554
        }
555

556
        foreach ($monies as $key => $money) {
19✔
557
            if ($remainder->isZero()) {
19✔
558
                break;
19✔
559
            }
560

561
            $monies[$key] = $money->plus($unit);
16✔
562
            $remainder = $remainder->minus($unit);
16✔
563
        }
564

565
        return $monies;
19✔
566
    }
567

568
    /**
569
     * Allocates this Money according to a list of ratios.
570
     *
571
     * The remainder is also present, appended at the end of the list.
572
     *
573
     * For example, given a `USD 49.99` money in the default context,
574
     * `allocateWithRemainder(1, 2, 3, 4)` returns [`USD 4.99`, `USD 9.98`, `USD 14.97`, `USD 19.96`, `USD 0.09`]
575
     *
576
     * The resulting monies have the same context as this Money.
577
     *
578
     * @param int[] $ratios The ratios.
579
     *
580
     * @return Money[]
581
     *
582
     * @throws InvalidArgumentException If called with invalid parameters.
583
     */
584
    public function allocateWithRemainder(int ...$ratios): array
585
    {
586
        if (! $ratios) {
22✔
587
            throw new InvalidArgumentException('Cannot allocateWithRemainder() an empty list of ratios.');
1✔
588
        }
589

590
        foreach ($ratios as $ratio) {
21✔
591
            if ($ratio < 0) {
21✔
592
                throw new InvalidArgumentException('Cannot allocateWithRemainder() negative ratios.');
1✔
593
            }
594
        }
595

596
        $total = array_sum($ratios);
20✔
597

598
        if ($total === 0) {
20✔
599
            throw new InvalidArgumentException('Cannot allocateWithRemainder() to zero ratios only.');
1✔
600
        }
601

602
        $ratios = $this->simplifyRatios(array_values($ratios));
19✔
603
        $total = array_sum($ratios);
19✔
604

605
        [, $remainder] = $this->quotientAndRemainder($total);
19✔
606

607
        $toAllocate = $this->minus($remainder);
19✔
608

609
        $monies = array_map(
19✔
610
            fn (int $ratio) => $toAllocate->multipliedBy($ratio)->dividedBy($total),
19✔
611
            $ratios,
19✔
612
        );
19✔
613

614
        $monies[] = $remainder;
19✔
615

616
        return $monies;
19✔
617
    }
618

619
    /**
620
     * Splits this Money into a number of parts.
621
     *
622
     * If the division of this Money by the number of parts yields a remainder, its amount is split over the first
623
     * monies in the list, so that the total of the resulting monies is always equal to this Money.
624
     *
625
     * For example, given a `USD 100.00` money in the default context,
626
     * `split(3)` returns [`USD 33.34`, `USD 33.33`, `USD 33.33`]
627
     *
628
     * The resulting monies have the same context as this Money.
629
     *
630
     * @param int $parts The number of parts.
631
     *
632
     * @return Money[]
633
     *
634
     * @throws InvalidArgumentException If called with invalid parameters.
635
     */
636
    public function split(int $parts): array
637
    {
638
        if ($parts < 1) {
10✔
639
            throw new InvalidArgumentException('Cannot split() into less than 1 part.');
2✔
640
        }
641

642
        return $this->allocate(...array_fill(0, $parts, 1));
8✔
643
    }
644

645
    /**
646
     * Splits this Money into a number of parts and a remainder.
647
     *
648
     * For example, given a `USD 100.00` money in the default context,
649
     * `splitWithRemainder(3)` returns [`USD 33.33`, `USD 33.33`, `USD 33.33`, `USD 0.01`]
650
     *
651
     * The resulting monies have the same context as this Money.
652
     *
653
     * @param int $parts The number of parts.
654
     *
655
     * @return Money[]
656
     *
657
     * @throws InvalidArgumentException If called with invalid parameters.
658
     */
659
    public function splitWithRemainder(int $parts): array
660
    {
661
        if ($parts < 1) {
10✔
662
            throw new InvalidArgumentException('Cannot splitWithRemainder() into less than 1 part.');
2✔
663
        }
664

665
        return $this->allocateWithRemainder(...array_fill(0, $parts, 1));
8✔
666
    }
667

668
    /**
669
     * Returns a Money whose value is the absolute value of this Money.
670
     *
671
     * The resulting Money has the same context as this Money.
672
     */
673
    public function abs(): Money
674
    {
675
        return new Money($this->amount->abs(), $this->currency, $this->context);
3✔
676
    }
677

678
    /**
679
     * Returns a Money whose value is the negated value of this Money.
680
     *
681
     * The resulting Money has the same context as this Money.
682
     */
683
    public function negated(): Money
684
    {
685
        return new Money($this->amount->negated(), $this->currency, $this->context);
3✔
686
    }
687

688
    /**
689
     * Converts this Money to another currency, using an exchange rate.
690
     *
691
     * By default, the resulting Money has the same context as this Money.
692
     * This can be overridden by providing a Context.
693
     *
694
     * For example, converting a default money of `USD 1.23` to `EUR` with an exchange rate of `0.91` and
695
     * RoundingMode::Up will yield `EUR 1.12`.
696
     *
697
     * @param Currency|string      $currency     The Currency instance or ISO currency code.
698
     * @param BigNumber|int|string $exchangeRate The exchange rate to multiply by.
699
     * @param Context|null         $context      An optional context, defaults to DefaultContext.
700
     * @param RoundingMode         $roundingMode An optional rounding mode.
701
     *
702
     * @throws UnknownCurrencyException If an unknown currency code is given.
703
     * @throws MathException            If the exchange rate or rounding mode is invalid, or rounding is necessary.
704
     */
705
    public function convertedTo(
706
        Currency|string $currency,
707
        BigNumber|int|string $exchangeRate,
708
        ?Context $context = null,
709
        RoundingMode $roundingMode = RoundingMode::Unnecessary,
710
    ): Money {
711
        if (! $currency instanceof Currency) {
3✔
712
            $currency = Currency::of($currency);
3✔
713
        }
714

715
        if ($context === null) {
3✔
716
            trigger_error(
1✔
717
                'Passing null for the $context parameter to Money::convertedTo() is deprecated, use an explicit Context instance. ' .
1✔
718
                'This parameter will default to DefaultContext in a future version.',
1✔
719
                E_USER_DEPRECATED,
1✔
720
            );
1✔
721

722
            $context = $this->context;
1✔
723
        }
724

725
        $amount = $this->amount->toBigRational()->multipliedBy($exchangeRate);
3✔
726

727
        return self::create($amount, $currency, $context, $roundingMode);
3✔
728
    }
729

730
    /**
731
     * Formats this Money to the given locale.
732
     *
733
     * Note that this method uses MoneyLocaleFormatter, which in turn internally uses NumberFormatter, which represents values using floating
734
     * point arithmetic, so discrepancies can appear when formatting very large monetary values.
735
     *
736
     * @param string $locale           The locale to format to, for example 'fr_FR' or 'en_US'.
737
     * @param bool   $allowWholeNumber Whether to allow formatting as a whole number if the amount has no fraction.
738
     */
739
    public function formatToLocale(string $locale, bool $allowWholeNumber = false): string
740
    {
741
        return (new MoneyLocaleFormatter($locale, $allowWholeNumber))->format($this);
12✔
742
    }
743

744
    #[Override]
745
    public function toRational(): RationalMoney
746
    {
747
        return new RationalMoney($this->amount->toBigRational(), $this->currency);
26✔
748
    }
749

750
    /**
751
     * Returns a non-localized string representation of this Money, e.g. "EUR 23.00".
752
     */
753
    #[Override]
754
    public function __toString(): string
755
    {
756
        return $this->currency . ' ' . $this->amount;
169✔
757
    }
758

759
    /**
760
     * @param Context $context The Context to check against this Money.
761
     * @param string  $method  The invoked method name.
762
     *
763
     * @throws MoneyMismatchException If monies don't match.
764
     */
765
    protected function checkContext(Context $context, string $method): void
766
    {
767
        if ($this->context != $context) { // non-strict equality on purpose
43✔
768
            throw MoneyMismatchException::contextMismatch($method);
1✔
769
        }
770
    }
771

772
    /**
773
     * @param non-empty-list<int> $ratios
774
     *
775
     * @return non-empty-list<int>
776
     */
777
    private function simplifyRatios(array $ratios): array
778
    {
779
        $gcd = $this->gcdOfMultipleInt($ratios);
19✔
780

781
        return array_map(fn (int $ratio) => intdiv($ratio, $gcd), $ratios);
19✔
782
    }
783

784
    /**
785
     * @param non-empty-list<int> $values
786
     */
787
    private function gcdOfMultipleInt(array $values): int
788
    {
789
        $values = array_map(fn (int $value) => BigInteger::of($value), $values);
19✔
790

791
        return BigInteger::gcdAll(...$values)->toInt();
19✔
792
    }
793
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc