Passed
Push — develop ( 06779a...b45a0e )
by Alec
07:13
created

Asset::allocate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * User: alec
4
 * Date: 05.11.18
5
 * Time: 23:51
6
 */
7
8
namespace AlecRabbit\Assets;
9
10
use AlecRabbit\Assets\Contracts\AssetInterface;
11
use AlecRabbit\Assets\Subclasses\AllocationCalculator;
12
use AlecRabbit\Assets\Subclasses\AssetFactory;
13
use AlecRabbit\Assets\Subclasses\AssetFunctions;
14
use AlecRabbit\Currency\Currency;
15
use function AlecRabbit\Helpers\trim_zeros;
16
use function AlecRabbit\typeOf;
17
use AlecRabbit\Money\CalculatorFactory as Factory;
18
19
/**
20
 * Money Value Object.
21
 *
22
 * @author Mathias Verraes
23
 */
24
class Asset implements AssetInterface, \JsonSerializable
25
{
26
    use AssetFactory,
27
        AssetFunctions;
28
29
    /** @var string */
30
    protected $amount;
31
32
    /** @var Currency */
33
    protected $currency;
34
35
    /**
36
     * @param null|int|float|string $amount
37
     * @param Currency $currency
38
     *
39
     * @throws \InvalidArgumentException If amount is not numeric
40
     */
41 141
    public function __construct($amount, Currency $currency)
42
    {
43 141
        $this->setAmount($this->assertAmount($amount));
44 139
        $this->setCurrency($currency);
45 139
        $this->calculator = Factory::getCalculator();
46 139
    }
47
48
    /**
49
     * @param null|int|float|string $amount
50
     * @return string
51
     */
52 141
    private function assertAmount($amount): string
53
    {
54 141
        if (null === $amount) {
55 3
            $amount = 0;
56
        }
57 141
        if (!\is_numeric($amount)) {
58 2
            throw new \InvalidArgumentException('Amount must be type of int|float|string or NULL');
59
        }
60 139
        return (string)$amount;
61
    }
62
63
    /**
64
     * @param string $amount
65
     */
66 139
    private function setAmount(string $amount): void
67
    {
68 139
        $this->amount = trim_zeros($amount);
69 139
    }
70
71
    /**
72
     * @param Currency $currency
73
     */
74 139
    private function setCurrency(Currency $currency): void
75
    {
76 139
        $this->currency = $currency;
77 139
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 7
    public function compare(Asset $other): int
83
    {
84 7
        $this->assertSameCurrency($other);
85
86 7
        return $this->calculator->compare($this->amount, $other->getAmount());
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 42
    protected function assertSameCurrency(Asset $other): void
93
    {
94 42
        if (!$this->isSameCurrency($other)) {
95 3
            throw new \InvalidArgumentException('Currencies must be identical.');
96
        }
97 42
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 50
    public function isSameCurrency(Asset $other): bool
103
    {
104 50
        return $this->currency->equals($other->currency);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 128
    public function getAmount(): string
111
    {
112 128
        return $this->amount;
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 80
    public function getCurrency(): Currency
119
    {
120 80
        return $this->currency;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     *
126
     * @return array
127
     */
128 1
    public function jsonSerialize(): array
129
    {
130
        return [
131 1
            'amount' => $this->amount,
132 1
            'currency' => $this->currency,
133
        ];
134
    }
135
136
    /**
137
     * Allocate the money among N targets.
138
     *
139
     * @param int $n
140
     *
141
     * @param int|null $precision
142
     * @return Asset[]
143
     *
144
     */
145 7
    public function allocateTo(int $n, ?int $precision = null): array
146
    {
147 7
        if ($n <= 0) {
148 2
            throw new \InvalidArgumentException('Number to allocateTo must be greater than zero.');
149
        }
150
151 5
        return $this->allocate(array_fill(0, $n, 1), $precision);
152
    }
153
154
    /**
155
     * Allocate the money according to a list of ratios.
156
     *
157
     * @param array $ratios
158
     *
159
     * @param int|null $precision
160
     * @return Asset[]
161
     */
162 41
    public function allocate(array $ratios, ?int $precision = null): array
163
    {
164
        return
165 41
            (new AllocationCalculator($this))->compute($ratios, $precision);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 25
    protected function assertOperand($operand): void
172
    {
173 25
        if (!\is_numeric($operand)) {
174 10
            throw new \InvalidArgumentException(
175 10
                sprintf(
176 10
                    'Operand should be a numeric value, "%s" given.',
177 10
                    typeOf($operand)
178
                )
179
            );
180
        }
181 15
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186 33
    protected function newInstance($amount): Asset
187
    {
188 33
        return new Asset($amount, $this->currency);
189
    }
190
191
    /**
192
     * @return Asset
193
     */
194 7
    public function absolute(): Asset
195
    {
196
        return
197 7
            $this->newInstance($this->calculator->absolute($this->getAmount()));
198
    }
199
200
    /**
201
     * @return Asset
202
     */
203 8
    public function negative(): Asset
204
    {
205
        return
206 8
            $this->newInstance(0)->subtract($this);
207
    }
208
}
209