Passed
Push — master ( 8b81ae...74fc3b )
by Alec
03:24
created

Money::isPositive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * User: alec
4
 * Date: 05.11.18
5
 * Time: 23:51
6
 */
7
8
namespace AlecRabbit\Money;
9
10
use AlecRabbit\Money\CalculatorFactory as Factory;
11
use AlecRabbit\Money\Contracts\CalculatorInterface;
12
use AlecRabbit\Money\Contracts\MoneyInterface;
13
14
/**
15
 * Money Value Object.
16
 *
17
 * @author Mathias Verraes
18
 */
19
class Money implements MoneyInterface, \JsonSerializable
20
{
21
    use MoneyFactory,
22
        MoneyFunctions;
23
24
    /** @var CalculatorInterface */
25
    private $calculator;
26
27
    /** @var string */
28
    private $amount;
29
30
    /** @var Currency */
31
    private $currency;
32
33
    /**
34
     * @param null|int|float|string $amount
35
     * @param Currency $currency
36
     *
37
     * @throws \InvalidArgumentException If amount is not integer
38
     */
39 137
    public function __construct($amount, Currency $currency)
40
    {
41 137
        if (null === $amount) {
42 3
            $amount = 0;
43
        }
44 137
        if (!\is_numeric($amount)) {
45 2
            throw new \InvalidArgumentException('Amount must be int|float|string');
46
        }
47 135
        $this->calculator = Factory::getCalculator();
48
49 135
        $this->setAmount((string)$amount);
50 135
        $this->setCurrency($currency);
51 135
    }
52
53
    /**
54
     * @param string $amount
55
     */
56 135
    private function setAmount(string $amount): void
57
    {
58 135
        $this->amount = trim_zeros($amount);
59 135
    }
60
61
    /**
62
     * @param Currency $currency
63
     */
64 135
    private function setCurrency(Currency $currency): void
65
    {
66 135
        $this->currency = $currency;
67 135
    }
68
69
70
    /**
71
     * Asserts that a Money has the same currency as this.
72
     *
73
     * @param Money $other
74
     *
75
     * @throws \InvalidArgumentException If $other has a different currency
76
     */
77 42
    private function assertSameCurrency(Money $other): void
78
    {
79 42
        if (!$this->isSameCurrency($other)) {
80 3
            throw new \InvalidArgumentException('Currencies must be identical.');
81
        }
82 42
    }
83
84
    /**
85
     * Returns an integer less than, equal to, or greater than zero
86
     * if the value of this object is considered to be respectively
87
     * less than, equal to, or greater than the other.
88
     *
89
     * @param Money $other
90
     *
91
     * @return int
92
     */
93 7
    public function compare(Money $other): int
94
    {
95 7
        $this->assertSameCurrency($other);
96
97 7
        return $this->calculator->compare($this->amount, $other->amount);
98
    }
99
100
    /**
101
     * Checks whether a Money has the same Currency as this.
102
     *
103
     * @param Money $other
104
     *
105
     * @return bool
106
     */
107 50
    public function isSameCurrency(Money $other): bool
108
    {
109 50
        return $this->currency->equals($other->currency);
110
    }
111
112
    /**
113
     * Returns a new Money object that represents
114
     * the sum of this and an other Money object.
115
     *
116
     * @param Money ...$addends
117
     *
118
     * @return Money
119
     */
120 28
    public function add(Money ...$addends): Money
121
    {
122 28
        $amount = $this->amount;
123 28
        $calculator = $this->calculator;
124
125 28
        foreach ($addends as $addend) {
126 25
            $this->assertSameCurrency($addend);
127
128 25
            $amount = $calculator->add($amount, $addend->amount);
129
        }
130 28
        return new self($amount, $this->currency);
131
    }
132
133
    /**
134
     * @param Money $first
135
     * @param Money ...$collection
136
     *
137
     * @return Money
138
     */
139 5
    public static function avg(Money $first, Money ...$collection): Money
140
    {
141 5
        return $first->add(...$collection)->divide(\func_num_args());
142
    }
143
144
    /**
145
     * Returns a new Money object that represents
146
     * the divided value by the given factor.
147
     *
148
     * @param float|int|string $divisor
149
     *
150
     * @return Money
151
     */
152 16
    public function divide($divisor): Money
153
    {
154 16
        $this->assertOperand($divisor);
155
156 11
        if ($this->calculator->compare((string)$divisor, '0') === 0) {
157 1
            throw new \InvalidArgumentException('Division by zero.');
158
        }
159
160 10
        $quotient = $this->calculator->divide($this->amount, $divisor);
161
        return
162 10
            $this->newInstance($quotient);
163
    }
164
165
    /**
166
     * Asserts that the operand is integer or float.
167
     *
168
     * @param float|int|string|object $operand
169
     *
170
     * @throws \InvalidArgumentException If $operand is neither integer nor float
171
     */
172 25
    private function assertOperand($operand): void
173
    {
174 25
        if (!\is_numeric($operand)) {
175 10
            throw new \InvalidArgumentException(sprintf(
176 10
                'Operand should be a numeric value, "%s" given.',
177 10
                \is_object($operand) ? \get_class($operand) : \gettype($operand)
178
            ));
179
        }
180 15
    }
181
182
    /**
183
     * Returns a new Money instance based on the current one using the Currency.
184
     *
185
     * @param int|string|float|null $amount
186
     *
187
     * @return Money
188
     *
189
     * @throws \InvalidArgumentException
190
     */
191 64
    private function newInstance($amount): Money
192
    {
193 64
        return new self($amount, $this->currency);
194
    }
195
196
    /**
197
     * Checks whether the value represented by this object equals to the other.
198
     *
199
     * @param Money $other
200
     *
201
     * @return bool
202
     */
203 8
    public function equals(Money $other): bool
204
    {
205 8
        return $this->isSameCurrency($other) && $this->amount === $other->amount;
206
    }
207
208
    /**
209
     * @param Money $other
210
     *
211
     * @return bool
212
     */
213 3
    public function greaterThanOrEqual(Money $other): bool
214
    {
215 3
        return $this->compare($other) >= 0;
216
    }
217
218
    /**
219
     * @param Money $other
220
     *
221
     * @return bool
222
     */
223 3
    public function lessThanOrEqual(Money $other): bool
224
    {
225 3
        return $this->compare($other) <= 0;
226
    }
227
228
    /**
229
     * Returns the value represented by this object.
230
     *
231
     * @return string
232
     */
233 32
    public function getAmount(): string
234
    {
235 32
        return $this->amount;
236
    }
237
238
    /**
239
     * Returns the currency of this object.
240
     *
241
     * @return Currency
242
     */
243 55
    public function getCurrency(): Currency
244
    {
245 55
        return $this->currency;
246
    }
247
248
    /**
249
     * Returns a new Money object that represents
250
     * the multiplied value by the given factor.
251
     *
252
     * @param float|int|string $multiplier
253
     *
254
     * @return Money
255
     */
256 9
    public function multiply($multiplier): Money
257
    {
258 9
        $this->assertOperand($multiplier);
259
260 4
        $product = $this->calculator->multiply($this->amount, $multiplier);
261
262
        return
263 4
            $this->newInstance($product);
264
    }
265
266
    /**
267
     * Returns a new Money object that represents
268
     * the remainder after dividing the value by
269
     * the given factor.
270
     *
271
     * @param Money $divisor
272
     *
273
     * @return Money
274
     */
275 4
    public function mod(Money $divisor): Money
276
    {
277 4
        $this->assertSameCurrency($divisor);
278
279 4
        return new self($this->calculator->mod($this->amount, $divisor->amount), $this->currency);
280
    }
281
282
    /**
283
     * Allocate the money among N targets.
284
     *
285
     * @param int $n
286
     *
287
     * @param int|null $precision
288
     * @return Money[]
289
     *
290
     */
291 7
    public function allocateTo(int $n, ?int $precision = null): array
292
    {
293 7
        if ($n <= 0) {
294 2
            throw new \InvalidArgumentException('Number to allocateTo must be greater than zero.');
295
        }
296
297 5
        return $this->allocate(array_fill(0, $n, 1), $precision);
298
    }
299
300
    /**
301
     * Allocate the money according to a list of ratios.
302
     *
303
     * @param array $ratios
304
     *
305
     * @param int|null $precision
306
     * @return Money[]
307
     */
308 37
    public function allocate(array $ratios, ?int $precision = null): array
309
    {
310 37
        $precision = $precision ?? 2;
311 37
        if (0 === $allocations = \count($ratios)) {
312 1
            throw new \InvalidArgumentException('Cannot allocate to none, ratios cannot be an empty array.');
313
        }
314
315 36
        $remainder = $this->amount;
316 36
        $results = [];
317 36
        $total = array_sum($ratios);
318
319 36
        if ($total <= 0) {
320 1
            throw new \InvalidArgumentException('Sum of ratios must be greater than zero.');
321
        }
322
323 35
        foreach ($ratios as $ratio) {
324 35
            if ($ratio < 0) {
325 1
                throw new \InvalidArgumentException('Ratio must be zero or positive.');
326
            }
327
328 35
            $share = $this->calculator->share($this->amount, $ratio, $total, $precision);
329 35
            $results[] = $this->newInstance($share);
330 35
            $remainder = $this->calculator->subtract($remainder, $share);
331
        }
332 34
        switch ($this->calculator->compare($remainder, '0')) {
333
            case -1:
334 13
                for ($i = $allocations - 1; $i >= 0; $i--) {
335 13
                    if (!$ratios[$i]) {
336 1
                        continue;
337
                    }
338 13
                    $results[$i]->setAmount($this->calculator->add($results[$i]->amount, $remainder));
339 13
                    break;
340
                }
341 13
                break;
342 21
            case 1:
343 7
                for ($i = 0; $i < $allocations; $i++) {
344 7
                    if (!$ratios[$i]) {
345 1
                        continue;
346
                    }
347 7
                    $results[$i]->setAmount($this->calculator->add($results[$i]->amount, $remainder));
348 7
                    break;
349
                }
350 7
                break;
351
            default:
352 14
                break;
353
        }
354 34
        return $results;
355
    }
356
357
    /**
358
     * @param Money $money
359
     *
360
     * @return string
361
     */
362 2
    public function ratioOf(Money $money): string
363
    {
364 2
        if ($money->isZero()) {
365 1
            throw new \InvalidArgumentException('Cannot calculate a ratio of zero.');
366
        }
367
368 1
        return $this->calculator->divide($this->amount, $money->amount);
369
    }
370
371
    /**
372
     * Checks if the value represented by this object is zero.
373
     *
374
     * @return bool
375
     */
376 30
    public function isZero(): bool
377
    {
378 30
        return $this->calculator->compare($this->amount, '0') === 0;
379
    }
380
381
    /**
382
     * @return Money
383
     */
384 7
    public function absolute(): Money
385
    {
386 7
        return $this->newInstance($this->calculator->absolute($this->amount));
387
    }
388
389
    /**
390
     * @return Money
391
     */
392 8
    public function negative(): Money
393
    {
394 8
        return $this->newInstance(0)->subtract($this);
395
    }
396
397
    /**
398
     * Returns a new Money object that represents
399
     * the difference of this and an other Money object.
400
     *
401
     * @param Money ...$subtrahends
402
     *
403
     * @return Money
404
     */
405 20
    public function subtract(Money ...$subtrahends): Money
406
    {
407 20
        $amount = $this->amount;
408 20
        $calculator = $this->calculator;
409
410 20
        foreach ($subtrahends as $subtrahend) {
411 20
            $this->assertSameCurrency($subtrahend);
412
413 20
            $amount = $calculator->subtract($amount, $subtrahend->amount);
414
        }
415
416 20
        return new self($amount, $this->currency);
417
    }
418
419
    /**
420
     * Checks if the value represented by this object is not negative.
421
     *
422
     * @return bool
423
     */
424 6
    public function isNotNegative(): bool
425
    {
426
        return
427 6
            !$this->isNegative();
428
    }
429
430
    /**
431
     * Checks if the value represented by this object is negative.
432
     *
433
     * @return bool
434
     */
435 13
    public function isNegative(): bool
436
    {
437 13
        return $this->calculator->compare($this->amount, '0') === -1;
438
    }
439
440
    /**
441
     * Checks if the value represented by this object is not zero.
442
     *
443
     * @return bool
444
     */
445 6
    public function isNotZero(): bool
446
    {
447
        return
448 6
            !$this->isZero();
449
    }
450
451
    /**
452
     * {@inheritdoc}
453
     *
454
     * @return array
455
     */
456 1
    public function jsonSerialize(): array
457
    {
458
        return [
459 1
            'amount' => $this->amount,
460 1
            'currency' => $this->currency,
461
        ];
462
    }
463
464
    /**
465
     * Checks if the value represented by this object is not positive.
466
     *
467
     * @return bool
468
     */
469 17
    public function isNotPositive(): bool
470
    {
471
        return
472 17
            !$this->isPositive();
473
    }
474
475
    /**
476
     * Checks if the value represented by this object is positive.
477
     *
478
     * @return bool
479
     */
480 23
    public function isPositive(): bool
481
    {
482 23
        return $this->calculator->compare($this->amount, '0') === 1;
483
    }
484
}
485