RepaymentInstance   A
last analyzed

Complexity

Total Complexity 4

Size/Duplication

Total Lines 41
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 4
c 0
b 0
f 0
lcom 1
cbo 1
dl 0
loc 41
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getPrincipalAmount() 0 4 1
A getInterestAmount() 0 4 1
A getTotalAmount() 0 4 1
1
<?php
2
3
namespace FinanCalc\Calculators {
4
5
    use DateTime;
6
    use FinanCalc\Interfaces\Calculator\CalculatorAbstract;
7
    use FinanCalc\Utils\Lambdas;
8
    use FinanCalc\Utils\MathFuncs;
9
    use FinanCalc\Utils\Time\TimeSpan;
10
    use FinanCalc\Utils\Time\TimeUtils;
11
12
    /**
13
     * Class DebtAmortizator
14
     * @package FinanCalc\Calculators
15
     */
16
    class DebtAmortizator extends CalculatorAbstract
17
    {
18
        /** @var  RepaymentInstance[] */
19
        // list of individual debt's repayments as an array of RepaymentInstance objects
20
        protected $debtRepayments;
21
22
        // principal of the debt = 'PV'
23
        protected $debtPrincipal;
24
        // number of periods pertaining to the interest compounding = 'n'
25
        protected $debtNoOfCompoundingPeriods;
26
        // length of a single period in days
27
        /** @var  TimeSpan */
28
        protected $debtPeriodLength;
29
        // the interest rate by which the unpaid balance is multiplied (i.e., a decimal number) = 'i'
30
        protected $debtInterest;
31
32
        // props returned by the getResultAsArray method by default
33
        protected $propResultArray = [
34
            "debtPrincipal",
35
            "debtNoOfCompoundingPeriods",
36
            "debtPeriodLength" =>
37
                [
38
                    "years" => "debtPeriodLengthInYears",
39
                    "months" => "debtPeriodLengthInMonths",
40
                    "days" => "debtPeriodLengthInDays"
41
                ],
42
            "debtInterest",
43
            "debtDiscountFactor",
44
            "debtLength" =>
45
                [
46
                    "years" => "debtLengthInYears",
47
                    "months" => "debtLengthInMonths",
48
                    "days" => "debtLengthInDays"
49
                ],
50
            "debtSingleRepayment",
51
            "debtRepayments" => "debtRepaymentsAsArrays"
52
        ];
53
54
        /**
55
         * @param $debtPrincipal
56
         * @param $debtNoOfCompoundingPeriods
57
         * @param TimeSpan $debtPeriodLength
58
         * @param $debtInterest
59
         */
60
        public function __construct(
61
            $debtPrincipal,
62
            $debtNoOfCompoundingPeriods,
63
            TimeSpan $debtPeriodLength,
64
            $debtInterest
65
        ) {
66
            $this->setDebtPrincipalWithoutRecalculation($debtPrincipal);
67
            $this->setDebtNoOfCompoundingPeriodsWithoutRecalculation($debtNoOfCompoundingPeriods);
68
            $this->setDebtPeriodLength($debtPeriodLength);
69
            $this->setDebtInterestWithoutRecalculation($debtInterest);
70
            $this->calculateDebtRepayments();
71
        }
72
73
        /**
74
         * @param $debtPrincipal
75
         */
76
        private function setDebtPrincipalWithoutRecalculation($debtPrincipal)
77
        {
78
            $this->setProperty("debtPrincipal", $debtPrincipal, Lambdas::checkIfPositive());
79
        }
80
81
        /**
82
         * @param $debtNoOfCompoundingPeriods
83
         */
84
        private function setDebtNoOfCompoundingPeriodsWithoutRecalculation($debtNoOfCompoundingPeriods)
85
        {
86
            $this->setProperty("debtNoOfCompoundingPeriods", $debtNoOfCompoundingPeriods, Lambdas::checkIfPositive());
87
        }
88
89
        /**
90
         * @param $debtInterest
91
         */
92
        private function setDebtInterestWithoutRecalculation($debtInterest)
93
        {
94
            $this->setProperty("debtInterest", $debtInterest, Lambdas::checkIfPositive());
95
        }
96
97
        /**
98
         * @param $debtPrincipal
99
         */
100
        public function setDebtPrincipal($debtPrincipal)
101
        {
102
            $this->setDebtPrincipalWithoutRecalculation($debtPrincipal);
103
            $this->calculateDebtRepayments();
104
        }
105
106
        /**
107
         * @param $debtNoOfCompoundingPeriods
108
         */
109
        public function setDebtNoOfCompoundingPeriods($debtNoOfCompoundingPeriods)
110
        {
111
            $this->setDebtNoOfCompoundingPeriodsWithoutRecalculation($debtNoOfCompoundingPeriods);
112
            $this->calculateDebtRepayments();
113
        }
114
115
        /**
116
         * @param $debtPeriodLength
117
         */
118
        public function setDebtPeriodLength(TimeSpan $debtPeriodLength)
119
        {
120
            $this->setProperty("debtPeriodLength", $debtPeriodLength, Lambdas::checkIfPositive());
121
        }
122
123
        /**
124
         * @param $debtInterest
125
         */
126
        public function setDebtInterest($debtInterest)
127
        {
128
            $this->setDebtInterestWithoutRecalculation($debtInterest);
129
            $this->calculateDebtRepayments();
130
        }
131
132
        /**
133
         * Private function populating the $debtRepayments array which represents the amortization schedule
134
         * constructed on basis of the initial parameters passed to the constructor
135
         */
136
        private function calculateDebtRepayments()
137
        {
138
            $this->debtRepayments = array();
139
            $unpaidBalance = $this->debtPrincipal;
140
141
            // calculate each repayment (more precisely its interest/principal components) and add it to the array
142
            // storing the debt repayments (i.e., representing the amortization schedule)
143
            // NOTE: rounding to two decimal places takes place when calculating the interest and principal parts
144
            // of a single repayment
145
            for ($i = 1; $i <= $this->debtNoOfCompoundingPeriods; $i++) {
146
                $interestAmount = MathFuncs::mul($this->debtInterest, $unpaidBalance);
147
                $principalAmount = MathFuncs::sub($this->getDebtSingleRepayment(), $interestAmount);
148
149
                $this->debtRepayments[$i] = new RepaymentInstance($principalAmount, $interestAmount);
150
151
                $unpaidBalance = MathFuncs::sub($unpaidBalance, $principalAmount);
152
            }
153
        }
154
155
        /**
156
         * @return string [Value of the debt principal as a string]
157
         */
158
        public function getDebtPrincipal()
159
        {
160
            return $this->debtPrincipal;
161
        }
162
163
        /**
164
         * @return string [Number of the debt's compounding periods as a string]
165
         */
166
        public function getDebtNoOfCompoundingPeriods()
167
        {
168
            return $this->debtNoOfCompoundingPeriods;
169
        }
170
171
        /**
172
         * @return TimeSpan
173
         */
174
        public function getDebtPeriodLength()
175
        {
176
            return $this->debtPeriodLength;
177
        }
178
179
        /**
180
         * @return string [Length of each of the debt's compounding periods in years as a string]
181
         */
182
        public function getDebtPeriodLengthInYears()
183
        {
184
            return $this->debtPeriodLength->toYears();
185
        }
186
187
        /**
188
         * @return string [Length of each of the debt's compounding periods in months as a string]
189
         */
190
        public function getDebtPeriodLengthInMonths()
191
        {
192
            return $this->debtPeriodLength->toMonths();
193
        }
194
195
        /**
196
         * @return string [Length of each of the debt's compounding periods in days as a string]
197
         */
198
        public function getDebtPeriodLengthInDays()
199
        {
200
            return $this->debtPeriodLength->toDays();
201
        }
202
203
        /**
204
         * @return string [Value of the debt's interest in a decimal number 'multiplier' form as a string]
205
         */
206
        public function getDebtInterest()
207
        {
208
            return $this->debtInterest;
209
        }
210
211
        /**
212
         * @return string [Value of the debt's discount factor as a string]
213
         */
214
        public function getDebtDiscountFactor()
215
        {
216
            // discount factor 'v = 1/(1+i)'
217
            return MathFuncs::div(
218
                1,
219
                MathFuncs::add(
220
                    1,
221
                    $this->debtInterest
222
                )
223
            );
224
225
        }
226
227
        /**
228
         * @return string [Length of the debt in years as a string]
229
         */
230
        public function getDebtLengthInYears()
231
        {
232
            return MathFuncs::div(
233
                $this->getDebtLengthInDays(),
234
                TimeUtils::getCurrentDayCountConvention()['days_in_a_year']
235
            );
236
        }
237
238
        /**
239
         * @return string [Length of the debt in months as a string]
240
         */
241
        public function getDebtLengthInMonths()
242
        {
243
            return MathFuncs::div(
244
                $this->getDebtLengthInDays(),
245
                TimeUtils::getCurrentDayCountConvention()['days_in_a_month']
246
            );
247
        }
248
249
        /**
250
         * @return string [Length of the debt in years as a string]
251
         */
252
        public function getDebtLengthInDays()
253
        {
254
            return MathFuncs::mul(
255
                $this->debtNoOfCompoundingPeriods,
256
                $this->debtPeriodLength->toDays()
257
            );
258
        }
259
260
        /**
261
         * @param DateTime $startDate [The start date of the debt]
262
         * @return DateTime [The end date of the debt]
263
         */
264
        public function getDebtEndDate(DateTime $startDate)
265
        {
266
            return TimeSpan
267
                ::asDurationWithStartDate($startDate, 0, 0, (int)$this->getDebtLengthInDays())
268
                ->getEndDate();
269
        }
270
271
        /**
272
         * @return string [Value of a single debt repayment instance as a string]
273
         */
274
        public function getDebtSingleRepayment()
275
        {
276
            // single repayment 'K = PV/((1-v^n)/i)'
277
            return MathFuncs::div(
278
                $this->debtPrincipal,
279
                MathFuncs::div(
280
                    MathFuncs::sub(
281
                        1,
282
                        MathFuncs::pow(
283
                            $this->getDebtDiscountFactor(),
284
                            $this->debtNoOfCompoundingPeriods
285
                        )
286
                    ),
287
                    $this->debtInterest
288
                )
289
            );
290
        }
291
292
        /**
293
         * @return RepaymentInstance[] [Array of individual debt repayments (RepaymentInstances)]
294
         */
295
        public function getDebtRepayments()
296
        {
297
            return $this->debtRepayments;
298
        }
299
300
        /**
301
         * @return array
302
         */
303
        public function getDebtRepaymentsAsArrays()
304
        {
305
            $repayments = array();
306
            $i = 1;
307
            foreach ($this->debtRepayments as $repayment) {
308
                $repayments[$i++] = [
309
                    "principalAmount" => $repayment->getPrincipalAmount(),
310
                    "interestAmount" => $repayment->getInterestAmount(),
311
                    "totalAmount" => $repayment->getTotalAmount()
312
                ];
313
            }
314
315
            return $repayments;
316
        }
317
318
    }
319
320
    /**
321
     * Class RepaymentInstance
322
     * @package FinanCalc\Calculators\DebtAmortizator
323
     */
324
    class RepaymentInstance
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
325
    {
326
        // the "principal" part of the individual repayment instance
327
        private $principalAmount;
328
        // the "interest" part of the individual repayment instance
329
        private $interestAmount;
330
331
        /**
332
         * @param string $principalAmount [Value of the amount of the payment's 'principal part' as a string]
333
         * @param string $interestAmount [Value of the amount of the payment's 'interest part' as a string]
334
         */
335
        public function __construct($principalAmount, $interestAmount)
336
        {
337
            $this->principalAmount = $principalAmount;
338
            $this->interestAmount = $interestAmount;
339
        }
340
341
        /**
342
         * @return string [Value of the amount of the payment's 'principal part' as a string]
343
         */
344
        public function getPrincipalAmount()
345
        {
346
            return $this->principalAmount;
347
        }
348
349
        /**
350
         * @return string [Value of the amount of the payment's 'interest part' as a string]
351
         */
352
        public function getInterestAmount()
353
        {
354
            return $this->interestAmount;
355
        }
356
357
        /**
358
         * @return string [Value of the total amount that the payment represents as a string]
359
         */
360
        public function getTotalAmount()
361
        {
362
            return MathFuncs::add($this->principalAmount, $this->interestAmount);
363
        }
364
    }
365
}
366