AnnuityCalculator   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 6
Bugs 0 Features 1
Metric Value
wmc 35
c 6
b 0
f 1
lcom 1
cbo 8
dl 0
loc 354
rs 9

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A setAnnuitySinglePaymentAmount() 0 4 1
A setAnnuityNoOfCompoundingPeriods() 0 9 2
A setAnnuityPeriodLength() 0 10 3
A setAnnuityInterest() 0 4 1
A getAnnuitySinglePaymentAmount() 0 4 1
A getAnnuityNoOfCompoundingPeriods() 0 4 1
A getAnnuityPeriodLength() 0 4 1
A getAnnuityPeriodLengthInYears() 0 4 1
A getAnnuityPeriodLengthInMonths() 0 4 1
A getAnnuityPeriodLengthInDays() 0 4 1
A getAnnuityInterest() 0 4 1
A getAnnuityLengthInYears() 0 7 1
A getAnnuityLengthInMonths() 0 7 1
A getAnnuityLengthInDays() 0 7 1
A getAnnuityEndDate() 0 6 1
A getAnnuityPresentValue() 0 8 1
A getAnnuityPresentValueInAdvance() 0 7 1
A getAnnuityPresentValueInArrears() 0 7 1
A getAnnuityFutureValue() 0 8 1
A getAnnuityFutureValueInAdvance() 0 7 1
A getAnnuityFutureValueInArrears() 0 6 1
C getAnnuityValue() 0 78 10
1
<?php
2
3
namespace FinanCalc\Calculators {
4
5
    use DateTime;
6
    use Exception;
7
    use FinanCalc\Constants\AnnuityPaymentTypes;
8
    use FinanCalc\Constants\AnnuityValueTypes;
9
    use FinanCalc\Interfaces\Calculator\CalculatorAbstract;
10
    use FinanCalc\Utils\Helpers;
11
    use FinanCalc\Utils\Lambdas;
12
    use FinanCalc\Utils\MathFuncs;
13
    use FinanCalc\Utils\Time\TimeSpan;
14
    use FinanCalc\Utils\Time\TimeUtils;
15
16
    /**
17
     * Class AnnuityCalculator
18
     * @package FinanCalc\Calculators
19
     */
20
    class AnnuityCalculator extends CalculatorAbstract
21
    {
22
23
        // amount of each individual payment = 'K'
24
        protected $annuitySinglePaymentAmount;
25
        // number of periods pertaining to the interest compounding = 'n'
26
        // if 'n = 0', the annuity is considered a perpetuity
27
        protected $annuityNoOfCompoundingPeriods;
28
        // length of a single period as a FinanCalc\Utils\Time\TimeSpan object
29
        /** @var  TimeSpan */
30
        protected $annuityPeriodLength;
31
        // the interest rate by which the unpaid balance is multiplied (i.e., a decimal number) = 'i'
32
        protected $annuityInterest;
33
34
        // props returned by the getResultAsArray method by default
35
        protected $propResultArray = [
36
            "annuitySinglePaymentAmount",
37
            "annuityNoOfCompoundingPeriods",
38
            "annuityInterest",
39
            "annuityPeriodLength" =>
40
                [
41
                    "years" => "annuityPeriodLengthInYears",
42
                    "months" => "annuityPeriodLengthInMonths",
43
                    "days" => "annuityPeriodLengthInDays"
44
                ],
45
            "annuityPresentValue" =>
46
                [
47
                    "in_advance" => "annuityPresentValueInAdvance",
48
                    "in_arrears" => "annuityPresentValueInArrears"
49
                ],
50
            "annuityFutureValue" =>
51
                [
52
                    "in_advance" => "annuityFutureValueInAdvance",
53
                    "in_arrears" => "annuityFutureValueInArrears"
54
                ],
55
        ];
56
57
        /**
58
         * @param $annuitySinglePaymentAmount
59
         * @param $annuityNoOfCompoundingPeriods
60
         * @param $annuityPeriodLength
61
         * @param $annuityInterest
62
         */
63
        public function __construct(
64
            $annuitySinglePaymentAmount,
65
            $annuityNoOfCompoundingPeriods,
66
            TimeSpan $annuityPeriodLength,
67
            $annuityInterest
68
        ) {
69
            $this->setAnnuitySinglePaymentAmount($annuitySinglePaymentAmount);
70
            $this->setAnnuityNoOfCompoundingPeriods($annuityNoOfCompoundingPeriods);
71
            $this->setAnnuityPeriodLength($annuityPeriodLength);
72
            $this->setAnnuityInterest($annuityInterest);
73
        }
74
75
        /**
76
         * @param $annuitySinglePaymentAmount
77
         */
78
        public function setAnnuitySinglePaymentAmount($annuitySinglePaymentAmount)
79
        {
80
            $this->setProperty("annuitySinglePaymentAmount", $annuitySinglePaymentAmount, Lambdas::checkIfPositive());
81
        }
82
83
        /**
84
         * @param $annuityNoOfCompoundingPeriods
85
         */
86
        public function setAnnuityNoOfCompoundingPeriods($annuityNoOfCompoundingPeriods)
87
        {
88
            $this->setProperty("annuityNoOfCompoundingPeriods", $annuityNoOfCompoundingPeriods,
89
                Lambdas::checkIfNotNegative());
90
91
            if ($this->annuityPeriodLength !== null) {
92
                $this->setAnnuityPeriodLength($this->annuityPeriodLength);
93
            }
94
        }
95
96
        /**
97
         * @param $annuityPeriodLength
98
         */
99
        public function setAnnuityPeriodLength(TimeSpan $annuityPeriodLength)
100
        {
101
            if (Helpers::checkIfNotNegativeNumberOrThrowAnException((string)$annuityPeriodLength)) {
102
                if (Helpers::checkIfZero($this->annuityNoOfCompoundingPeriods)) {
103
                    $annuityPeriodLength = TimeSpan::asDuration(0);
104
                }
105
106
                $this->setProperty("annuityPeriodLength", $annuityPeriodLength);
107
            }
108
        }
109
110
        /**
111
         * @param $annuityInterest
112
         */
113
        public function setAnnuityInterest($annuityInterest)
114
        {
115
            $this->setProperty("annuityInterest", $annuityInterest, Lambdas::checkIfPositive());
116
        }
117
118
        /**
119
         * @return mixed
120
         */
121
        public function getAnnuitySinglePaymentAmount()
122
        {
123
            return $this->annuitySinglePaymentAmount;
124
        }
125
126
        /**
127
         * @return mixed
128
         */
129
        public function getAnnuityNoOfCompoundingPeriods()
130
        {
131
            return $this->annuityNoOfCompoundingPeriods;
132
        }
133
134
        /**
135
         * @return TimeSpan
136
         */
137
        public function getAnnuityPeriodLength()
138
        {
139
            return $this->annuityPeriodLength;
140
        }
141
142
        /**
143
         * @return string
144
         */
145
        public function getAnnuityPeriodLengthInYears()
146
        {
147
            return $this->annuityPeriodLength->toYears();
148
        }
149
150
        /**
151
         * @return string
152
         */
153
        public function getAnnuityPeriodLengthInMonths()
154
        {
155
            return $this->annuityPeriodLength->toMonths();
156
        }
157
158
        /**
159
         * @return string
160
         */
161
        public function getAnnuityPeriodLengthInDays()
162
        {
163
            return $this->annuityPeriodLength->toDays();
164
        }
165
166
        /**
167
         * @return mixed
168
         */
169
        public function getAnnuityInterest()
170
        {
171
            return $this->annuityInterest;
172
        }
173
174
        /**
175
         * @return string
176
         * @throws Exception
177
         */
178
        public function getAnnuityLengthInYears()
179
        {
180
            return MathFuncs::div(
181
                $this->getAnnuityLengthInDays(),
182
                TimeUtils::getCurrentDayCountConvention()['days_in_a_year']
183
            );
184
        }
185
186
        /**
187
         * @return string
188
         * @throws Exception
189
         */
190
        public function getAnnuityLengthInMonths()
191
        {
192
            return MathFuncs::div(
193
                $this->getAnnuityLengthInDays(),
194
                TimeUtils::getCurrentDayCountConvention()['days_in_a_month']
195
            );
196
        }
197
198
        /**
199
         * @return string
200
         */
201
        public function getAnnuityLengthInDays()
202
        {
203
            return MathFuncs::mul(
204
                $this->annuityNoOfCompoundingPeriods,
205
                $this->annuityPeriodLength->toDays()
206
            );
207
        }
208
209
        /**
210
         * @param DateTime $startDate
211
         * @return DateTime
212
         */
213
        public function getAnnuityEndDate(DateTime $startDate)
214
        {
215
            return TimeSpan
216
                ::asDurationWithStartDate($startDate, 0, 0, (int)$this->getAnnuityLengthInDays())
217
                ->getEndDate();
218
        }
219
220
        /**
221
         * @param AnnuityPaymentTypes $annuityType
222
         * @return null|string
223
         */
224
        public function getAnnuityPresentValue(AnnuityPaymentTypes $annuityType = null)
225
        {
226
            return $this
227
                ->getAnnuityValue(
228
                    $annuityType,
229
                    new AnnuityValueTypes(AnnuityValueTypes::PRESENT_VALUE)
230
                );
231
        }
232
233
        /**
234
         * @return null|string
235
         */
236
        public function getAnnuityPresentValueInAdvance()
237
        {
238
            return $this
239
                ->getAnnuityPresentValue(
240
                    new AnnuityPaymentTypes(AnnuityPaymentTypes::IN_ADVANCE)
241
                );
242
        }
243
244
        /**
245
         * @return null|string
246
         */
247
        public function getAnnuityPresentValueInArrears()
248
        {
249
            return $this
250
                ->getAnnuityPresentValue(
251
                    new AnnuityPaymentTypes(AnnuityPaymentTypes::IN_ARREARS)
252
                );
253
        }
254
255
        /**
256
         * @param AnnuityPaymentTypes $annuityType
257
         * @return null|string
258
         */
259
        public function getAnnuityFutureValue(AnnuityPaymentTypes $annuityType = null)
260
        {
261
            return $this
262
                ->getAnnuityValue(
263
                    $annuityType,
264
                    new AnnuityValueTypes(AnnuityValueTypes::FUTURE_VALUE)
265
                );
266
        }
267
268
        /**
269
         * @return null|string
270
         */
271
        public function getAnnuityFutureValueInAdvance()
272
        {
273
            return $this
274
                ->getAnnuityFutureValue(
275
                    new AnnuityPaymentTypes(AnnuityPaymentTypes::IN_ADVANCE)
276
                );
277
        }
278
279
        /**
280
         * @return null|string
281
         */
282
        public function getAnnuityFutureValueInArrears()
283
        {
284
            return $this->getAnnuityFutureValue(
285
                new AnnuityPaymentTypes(AnnuityPaymentTypes::IN_ARREARS)
286
            );
287
        }
288
289
        /**
290
         * @param AnnuityPaymentTypes $annuityPaymentType
291
         * @param AnnuityValueTypes $annuityValueType
292
         * @return null|string
293
         * @throws Exception
294
         */
295
        public function getAnnuityValue(
296
            AnnuityPaymentTypes $annuityPaymentType = null,
297
            AnnuityValueTypes $annuityValueType
298
        ) {
299
            // if the number of the annuity's compounding periods
300
            // is set to zero, we're dealing with a perpetuity
301
            if (Helpers::checkIfZero($this->annuityNoOfCompoundingPeriods)) {
302
                // we cannot calculate FV of a perpetuity, we therefore return null
303
                // in case such a value is demanded
304
                if ($annuityValueType->getValue() == AnnuityValueTypes::FUTURE_VALUE) {
305
                    return null;
306
                }
307
308
                // PV of a perpetuity = K/i
309
                return Helpers::roundMoneyForDisplay(
310
                    MathFuncs::div(
311
                        $this->annuitySinglePaymentAmount,
312
                        $this->annuityInterest)
313
                );
314
            }
315
316
            // when the annuity is not a perpetuity, we first need to check that
317
            // the $annuityPaymentType is not null
318
            if (Helpers::checkIfNotNull($annuityPaymentType)) {
319
320
                // discount factor 'v = 1/(1+i)'
321
                $discountFactor = MathFuncs::div(
322
                    1,
323
                    MathFuncs::add(
324
                        1,
325
                        $this->annuityInterest
326
                    )
327
                );
328
329
                if ($annuityValueType->getValue() == AnnuityValueTypes::PRESENT_VALUE) {
330
                    // PV numerator = 1-v^n
331
                    $numerator = MathFuncs::sub(
332
                        1,
333
                        MathFuncs::pow(
334
                            $discountFactor,
335
                            $this->annuityNoOfCompoundingPeriods
336
                        )
337
                    );
338
                } elseif ($annuityValueType->getValue() == AnnuityValueTypes::FUTURE_VALUE) {
339
                    // FV numerator = (1+i)^n-1
340
                    $numerator = MathFuncs::sub(
341
                        MathFuncs::pow(
342
                            MathFuncs::add(
343
                                1,
344
                                $this->annuityInterest
345
                            ),
346
                            $this->annuityNoOfCompoundingPeriods
347
                        ),
348
                        1
349
                    );
350
                }
351
352
                if ($annuityPaymentType->getValue() == AnnuityPaymentTypes::IN_ADVANCE) {
353
                    // in advance denom. = 1-v
354
                    $denominator = MathFuncs::sub(1, $discountFactor);
355
                } elseif ($annuityPaymentType->getValue() == AnnuityPaymentTypes::IN_ARREARS) {
0 ignored issues
show
Bug introduced by
It seems like $annuityPaymentType is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
356
                    // in arrears denom. = i
357
                    $denominator = $this->annuityInterest;
358
                }
359
360
                if (isset($numerator) && isset($denominator)) {
361
                    return
362
                        // PV|FV = K*(PV|FV of unit annuity)
363
                        MathFuncs::mul(
364
                            MathFuncs::div(
365
                                $numerator,
366
                                $denominator),
367
                            $this->annuitySinglePaymentAmount);
368
                }
369
            }
370
371
            return null;
372
        }
373
    }
374
}
375