Completed
Pull Request — master (#6)
by
unknown
02:56
created

DebtAmortizator::getDebtSingleRepaymentRounded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 12

Duplication

Lines 17
Ratio 100 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 17
loc 17
rs 9.4285
cc 1
eloc 12
nc 1
nop 0
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
            "debtSingleRepaymentRounded",
52
            "debtRepayments" => "debtRepaymentsAsArrays",
53
            "debtRepaymentsRounded" => "debtRepaymentsAsArraysRounded"
54
        ];
55
56
        /**
57
         * @param $debtPrincipal
58
         * @param $debtNoOfCompoundingPeriods
59
         * @param TimeSpan $debtPeriodLength
60
         * @param $debtInterest
61
         */
62
        public function __construct(
63
            $debtPrincipal,
64
            $debtNoOfCompoundingPeriods,
65
            TimeSpan $debtPeriodLength,
66
            $debtInterest
67
        ) {
68
            $this->setDebtPrincipalWithoutRecalculation($debtPrincipal);
69
            $this->setDebtNoOfCompoundingPeriodsWithoutRecalculation($debtNoOfCompoundingPeriods);
70
            $this->setDebtPeriodLength($debtPeriodLength);
71
            $this->setDebtInterestWithoutRecalculation($debtInterest);
72
            $this->calculateDebtRepayments();
73
        }
74
75
        /**
76
         * @param $debtPrincipal
77
         */
78
        private function setDebtPrincipalWithoutRecalculation($debtPrincipal)
79
        {
80
            $this->setProperty("debtPrincipal", $debtPrincipal, Lambdas::checkIfPositive());
81
        }
82
83
        /**
84
         * @param $debtNoOfCompoundingPeriods
85
         */
86
        private function setDebtNoOfCompoundingPeriodsWithoutRecalculation($debtNoOfCompoundingPeriods)
87
        {
88
            $this->setProperty("debtNoOfCompoundingPeriods", $debtNoOfCompoundingPeriods, Lambdas::checkIfPositive());
89
        }
90
91
        /**
92
         * @param $debtInterest
93
         */
94
        private function setDebtInterestWithoutRecalculation($debtInterest)
95
        {
96
            $this->setProperty("debtInterest", $debtInterest, Lambdas::checkIfPositive());
97
        }
98
99
        /**
100
         * @param $debtPrincipal
101
         */
102
        public function setDebtPrincipal($debtPrincipal)
103
        {
104
            $this->setDebtPrincipalWithoutRecalculation($debtPrincipal);
105
            $this->calculateDebtRepayments();
106
        }
107
108
        /**
109
         * @param $debtNoOfCompoundingPeriods
110
         */
111
        public function setDebtNoOfCompoundingPeriods($debtNoOfCompoundingPeriods)
112
        {
113
            $this->setDebtNoOfCompoundingPeriodsWithoutRecalculation($debtNoOfCompoundingPeriods);
114
            $this->calculateDebtRepayments();
115
        }
116
117
        /**
118
         * @param $debtPeriodLength
119
         */
120
        public function setDebtPeriodLength(TimeSpan $debtPeriodLength)
121
        {
122
            $this->setProperty("debtPeriodLength", $debtPeriodLength, Lambdas::checkIfPositive());
123
        }
124
125
        /**
126
         * @param $debtInterest
127
         */
128
        public function setDebtInterest($debtInterest)
129
        {
130
            $this->setDebtInterestWithoutRecalculation($debtInterest);
131
            $this->calculateDebtRepayments();
132
        }
133
134
        /**
135
         * Private function populating the $debtRepayments array which represents the amortization schedule
136
         * constructed on basis of the initial parameters passed to the constructor
137
         */
138
        private function calculateDebtRepayments()
139
        {
140
            $this->debtRepayments = array();
141
            $unpaidBalance = $this->debtPrincipal;
142
143
            // calculate each repayment (more precisely its interest/principal components) and add it to the array
144
            // storing the debt repayments (i.e., representing the amortization schedule)
145
            // NOTE: rounding to two decimal places takes place when calculating the interest and principal parts
146
            // of a single repayment
147
            for ($i = 1; $i <= $this->debtNoOfCompoundingPeriods; $i++) {
148
                $interestAmount = MathFuncs::mul($this->debtInterest, $unpaidBalance);
149
                $principalAmount = MathFuncs::sub($this->getDebtSingleRepayment(), $interestAmount);
150
151
                $this->debtRepayments[$i] = new RepaymentInstance($principalAmount, $interestAmount);
152
153
                $unpaidBalance = MathFuncs::sub($unpaidBalance, $principalAmount);
154
            }
155
        }
156
157
        /**
158
         * @return string [Value of the debt principal as a string]
159
         */
160
        public function getDebtPrincipal()
161
        {
162
            return $this->debtPrincipal;
163
        }
164
165
        /**
166
         * @return string [Number of the debt's compounding periods as a string]
167
         */
168
        public function getDebtNoOfCompoundingPeriods()
169
        {
170
            return $this->debtNoOfCompoundingPeriods;
171
        }
172
173
        /**
174
         * @return TimeSpan
175
         */
176
        public function getDebtPeriodLength()
177
        {
178
            return $this->debtPeriodLength;
179
        }
180
181
        /**
182
         * @return string [Length of each of the debt's compounding periods in years as a string]
183
         */
184
        public function getDebtPeriodLengthInYears()
185
        {
186
            return $this->debtPeriodLength->toYears();
187
        }
188
189
        /**
190
         * @return string [Length of each of the debt's compounding periods in months as a string]
191
         */
192
        public function getDebtPeriodLengthInMonths()
193
        {
194
            return $this->debtPeriodLength->toMonths();
195
        }
196
197
        /**
198
         * @return string [Length of each of the debt's compounding periods in days as a string]
199
         */
200
        public function getDebtPeriodLengthInDays()
201
        {
202
            return $this->debtPeriodLength->toDays();
203
        }
204
205
        /**
206
         * @return string [Value of the debt's interest in a decimal number 'multiplier' form as a string]
207
         */
208
        public function getDebtInterest()
209
        {
210
            return $this->debtInterest;
211
        }
212
213
        /**
214
         * @return string [Value of the debt's discount factor as a string]
215
         */
216
        public function getDebtDiscountFactor()
217
        {
218
            // discount factor 'v = 1/(1+i)'
219
            return MathFuncs::div(
220
                1,
221
                MathFuncs::add(
222
                    1,
223
                    $this->debtInterest
224
                )
225
            );
226
227
        }
228
229
        /**
230
         * @return string [Length of the debt in years as a string]
231
         */
232
        public function getDebtLengthInYears()
233
        {
234
            return MathFuncs::div(
235
                $this->getDebtLengthInDays(),
236
                TimeUtils::getCurrentDayCountConvention()['days_in_a_year']
237
            );
238
        }
239
240
        /**
241
         * @return string [Length of the debt in months as a string]
242
         */
243
        public function getDebtLengthInMonths()
244
        {
245
            return MathFuncs::div(
246
                $this->getDebtLengthInDays(),
247
                TimeUtils::getCurrentDayCountConvention()['days_in_a_month']
248
            );
249
        }
250
251
        /**
252
         * @return string [Length of the debt in years as a string]
253
         */
254
        public function getDebtLengthInDays()
255
        {
256
            return MathFuncs::mul(
257
                $this->debtNoOfCompoundingPeriods,
258
                $this->debtPeriodLength->toDays()
259
            );
260
        }
261
262
        /**
263
         * @param DateTime $startDate [The start date of the debt]
264
         * @return DateTime [The end date of the debt]
265
         */
266
        public function getDebtEndDate(DateTime $startDate)
267
        {
268
            return TimeSpan
269
                ::asDurationWithStartDate($startDate, 0, 0, (int)$this->getDebtLengthInDays())
270
                ->getEndDate();
271
        }
272
273
        /**
274
         * @return string [Value of a single debt repayment instance as a string]
275
         */
276 View Code Duplication
        public function getDebtSingleRepayment()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
277
        {
278
            // single repayment 'K = PV/((1-v^n)/i)'
279
            return MathFuncs::div(
280
                $this->debtPrincipal,
281
                MathFuncs::div(
282
                    MathFuncs::sub(
283
                        1,
284
                        MathFuncs::pow(
285
                            $this->getDebtDiscountFactor(),
286
                            $this->debtNoOfCompoundingPeriods
287
                        )
288
                    ),
289
                    $this->debtInterest
290
                )
291
            );
292
        }
293
        
294
        /**
295
         * @return string [Value of a single debt repayment instance as a string]
296
         */
297 View Code Duplication
        public function getDebtSingleRepaymentRounded() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
            // single repayment 'K = PV/((1-v^n)/i)'
299
            return MathFuncs::roundUp(
300
                MathFuncs::div(
301
                $this->debtPrincipal,
302
                MathFuncs::div(
303
                    MathFuncs::sub(
304
                        1,
305
                        MathFuncs::pow(
306
                            $this->getDebtDiscountFactor(),
307
                            $this->debtNoOfCompoundingPeriods
308
                        )
309
                    ),
310
                    $this->debtInterest
311
                )
312
            ), 2);
313
        }
314
315
        /**
316
         * @return RepaymentInstance[] [Array of individual debt repayments (RepaymentInstances)]
317
         */
318
        public function getDebtRepayments()
319
        {
320
            return $this->debtRepayments;
321
        }
322
323
        /**
324
         * @return array
325
         */
326
        public function getDebtRepaymentsAsArrays()
327
        {
328
            $repayments = array();
329
            $i = 1;
330
            foreach ($this->debtRepayments as $repayment) {
331
                $repayments[$i++] = [
332
                    "principalAmount" => $repayment->getPrincipalAmount(),
333
                    "interestAmount" => $repayment->getInterestAmount(),
334
                    "totalAmount" => $repayment->getTotalAmount()
335
                ];
336
            }
337
338
            return $repayments;
339
        }
340
				
341
				 /**
342
         * @return array
343
         */
344
        public function getDebtRepaymentsAsArraysRounded()
345
        {
346
            $repayments = array();
347
            $i = 1;
348
            $balance = $this->debtPrincipal;            
349
            foreach ($this->debtRepayments as $repayment) {
350
                if($balance > $repayment->getTotalAmount()) {      
351
                    $totalAmount = MathFuncs::roundUp($repayment->getTotalAmount(), 2);
352
                    $interestAmount = MathFuncs::round($repayment->getInterestAmount(), 2);
353
                    $principalAmount = MathFuncs::round($totalAmount - $interestAmount, 2);
354
                }
355
                else {
356
                    $interestAmount = MathFuncs::round($repayment->getInterestAmount(), 2);
357
                    $totalAmount = MathFuncs::round(MathFuncs::add($balance, $interestAmount), 2);
358
                    $principalAmount = MathFuncs::round($balance, 2);
359
                }
360
361
                $beginningBalance = $balance;
362
                $endingBalance = MathFuncs::sub($balance, $principalAmount);
363
                $balance = MathFuncs::sub($balance, $principalAmount);
364
365
                $repayments[$i++] = [
366
                    "beginningBalance" => MathFuncs::round($beginningBalance, 2),
367
                    "totalAmount" => $totalAmount,
368
                    "principalAmount" => $principalAmount,
369
                    "interestAmount" => $interestAmount,
370
                    "endingBalance" => MathFuncs::round($endingBalance, 2)
371
                ];
372
            }
373
374
            return $repayments;
375
        }
376
        
377
    }
378
379
    /**
380
     * Class RepaymentInstance
381
     * @package FinanCalc\Calculators\DebtAmortizator
382
     */
383
    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...
384
    {
385
        // the "principal" part of the individual repayment instance
386
        private $principalAmount;
387
        // the "interest" part of the individual repayment instance
388
        private $interestAmount;
389
390
        /**
391
         * @param string $principalAmount [Value of the amount of the payment's 'principal part' as a string]
392
         * @param string $interestAmount [Value of the amount of the payment's 'interest part' as a string]
393
         */
394
        public function __construct($principalAmount, $interestAmount)
395
        {
396
            $this->principalAmount = $principalAmount;
397
            $this->interestAmount = $interestAmount;
398
        }
399
400
        /**
401
         * @return string [Value of the amount of the payment's 'principal part' as a string]
402
         */
403
        public function getPrincipalAmount()
404
        {
405
            return $this->principalAmount;
406
        }
407
408
        /**
409
         * @return string [Value of the amount of the payment's 'interest part' as a string]
410
         */
411
        public function getInterestAmount()
412
        {
413
            return $this->interestAmount;
414
        }
415
416
        /**
417
         * @return string [Value of the total amount that the payment represents as a string]
418
         */
419
        public function getTotalAmount()
420
        {
421
            return MathFuncs::add($this->principalAmount, $this->interestAmount);
422
        }
423
    }
424
}
425