Invoice   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 509
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 96.4%

Importance

Changes 0
Metric Value
wmc 54
lcom 1
cbo 9
dl 0
loc 509
ccs 134
cts 139
cp 0.964
rs 6.4799
c 0
b 0
f 0

44 Methods

Rating   Name   Duplication   Size   Complexity  
F createFromArray() 0 59 11
A getId() 0 4 1
A getAmountDue() 0 4 1
A getAmountPaid() 0 4 1
A getAmountRemaining() 0 4 1
A getApplicationFeeAmount() 0 4 1
A getAttemptCount() 0 4 1
A isAttempted() 0 4 1
A isAutoAdvance() 0 4 1
A getBilling() 0 6 1
A getCollectionMethod() 0 4 1
A getBillingReason() 0 4 1
A getCharge() 0 4 1
A getCreatedAt() 0 4 1
A getCurrency() 0 4 1
A getCustomFields() 0 4 1
A getCustomer() 0 4 1
A getDefaultSource() 0 4 1
A getDescription() 0 4 1
A getDiscount() 0 4 1
A getDueAt() 0 4 1
A getEndingBalance() 0 4 1
A getFooter() 0 4 1
A getHostedInvoiceUrl() 0 4 1
A getInvoicePdf() 0 4 1
A getLines() 0 4 1
A getNextPaymentAttempt() 0 4 1
A getNumber() 0 4 1
A isPaid() 0 4 1
A getPaymentIntentId() 0 4 1
A getPeriodEndAt() 0 4 1
A getPeriodStartAt() 0 4 1
A getReceiptNumber() 0 4 1
A getStartingBalance() 0 4 1
A getStatementDescriptor() 0 4 1
A getStatus() 0 4 1
A getSubscription() 0 4 1
A getSubscriptionProrationAt() 0 4 1
A getSubtotal() 0 4 1
A getTax() 0 4 1
A getTaxPercent() 0 4 1
A getThresholdReason() 0 4 1
A getTotal() 0 4 1
A getWebhooksDeliveredAt() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Invoice often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Invoice, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This software may be modified and distributed under the terms
7
 * of the MIT license. See the LICENSE file for details.
8
 */
9
10
namespace Shapin\Stripe\Model\Invoice;
11
12
use Money\Currency;
13
use Money\Money;
14
use Shapin\Stripe\Model\ContainsMetadata;
15
use Shapin\Stripe\Model\CreatableFromArray;
16
use Shapin\Stripe\Model\Customer\CustomField;
17
use Shapin\Stripe\Model\Discount\Discount;
18
use Shapin\Stripe\Model\LivemodeTrait;
19
use Shapin\Stripe\Model\MetadataCollection;
20
use Shapin\Stripe\Model\MetadataTrait;
21
22
final class Invoice implements CreatableFromArray, ContainsMetadata
23
{
24
    use LivemodeTrait;
25
    use MetadataTrait;
26
27
    const BILLING_CHARGE_AUTOMATICALLY = 'charge_automatically';
28
    const BILLING_SEND_INVOICE = 'send_invoice';
29
30
    const BILLING_REASON_SUBSCRIPTION_CYCLE = 'subscription_cycle';
31
    const BILLING_REASON_SUBSCRIPTION_CREATE = 'subscription_create';
32
    const BILLING_REASON_SUBSCRIPTION_UPDATE = 'subscription_update';
33
    const BILLING_REASON_SUBSCRIPTION = 'subscription';
34
    const BILLING_REASON_MANUAL = 'manual';
35
    const BILLING_REASON_UPCOMING = 'upcoming';
36
    const BILLING_REASON_SUBSCRIPTION_THRESHOLD = 'subscription_threshold';
37
38
    const STATUS_DRAFT = 'draft';
39
    const STATUS_OPEN = 'open';
40
    const STATUS_PAID = 'paid';
41
    const STATUS_UNCOLLECTIBLE = 'uncollectible';
42
    const STATUS_VOID = 'void';
43
44
    /**
45
     * @var ?string
46
     */
47
    private $id;
48
49
    /**
50
     * @var Money
51
     */
52
    private $amountDue;
53
54
    /**
55
     * @var Money
56
     */
57
    private $amountPaid;
58
59
    /**
60
     * @var Money
61
     */
62
    private $amountRemaining;
63
64
    /**
65
     * @var ?Money
66
     */
67
    private $applicationFeeAmount;
68
69
    /**
70
     * @var int
71
     */
72
    private $attemptCount;
73
74
    /**
75
     * @var bool
76
     */
77
    private $attempted;
78
79
    /**
80
     * @var bool
81
     */
82
    private $autoAdvance;
83
84
    /**
85
     * @var string
86
     */
87
    private $collectionMethod;
88
89
    /**
90
     * @var string
91
     */
92
    private $billingReason;
93
94
    /**
95
     * @var ?string
96
     */
97
    private $charge;
98
99
    /**
100
     * @var \DateTimeImmutable
101
     */
102
    private $createdAt;
103
104
    /**
105
     * @var Currency
106
     */
107
    private $currency;
108
109
    /**
110
     * @var array
111
     */
112
    private $customFields;
113
114
    /**
115
     * @var string
116
     */
117
    private $customer;
118
119
    /**
120
     * @var ?string
121
     */
122
    private $defaultSource;
123
124
    /**
125
     * @var string
126
     */
127
    private $description;
128
129
    /**
130
     * @var ?Discount
131
     */
132
    private $discount;
133
134
    /**
135
     * @var ?\DateTimeImmutable
136
     */
137
    private $dueAt;
138
139
    /**
140
     * @var ?Money
141
     */
142
    private $endingBalance;
143
144
    /**
145
     * @var ?string
146
     */
147
    private $footer;
148
149
    /**
150
     * @var ?string
151
     */
152
    private $hostedInvoiceUrl;
153
154
    /**
155
     * @var ?string
156
     */
157
    private $invoicePdf;
158
159
    /**
160
     * @var LineItemCollection
161
     */
162
    private $lines;
163
164
    /**
165
     * @var ?\DateTimeImmutable
166
     */
167
    private $nextPaymentAttempt;
168
169
    /**
170
     * @var string
171
     */
172
    private $number;
173
174
    /**
175
     * @var bool
176
     */
177
    private $paid;
178
179
    /**
180
     * @var ?string
181
     */
182
    private $paymentIntentId;
183
184
    /**
185
     * @var \DateTimeImmutable
186
     */
187
    private $periodEndAt;
188
189
    /**
190
     * @var \DateTimeImmutable
191
     */
192
    private $periodStartAt;
193
194
    /**
195
     * @var ?string
196
     */
197
    private $receiptNumber;
198
199
    /**
200
     * @var Money
201
     */
202
    private $startingBalance;
203
204
    /**
205
     * @var ?string
206
     */
207
    private $statementDescriptor;
208
209
    /**
210
     * @var string
211
     */
212
    private $status;
213
214
    /**
215
     * @var string
216
     */
217
    private $subscription;
218
219
    /**
220
     * @var ?\DateTimeImmutable
221
     */
222
    private $subscriptionProrationAt;
223
224
    /**
225
     * @var Money
226
     */
227
    private $subtotal;
228
229
    /**
230
     * @var Money
231
     */
232
    private $tax;
233
234
    /**
235
     * @var float
236
     */
237
    private $taxPercent;
238
239
    /**
240
     * @var ?ThresholdReason
241
     */
242
    private $thresholdReason;
243
244
    /**
245
     * @var Money
246
     */
247
    private $total;
248
249
    /**
250
     * @var ?\DateTimeImmutable
251
     */
252
    private $webhooksDeliveredAt;
253
254 11
    public static function createFromArray(array $data): self
255
    {
256 11
        $currency = new Currency(strtoupper($data['currency']));
257
258 11
        $customFields = [];
259 11
        if (isset($data['custom_fields'])) {
260
            foreach ($data['custom_fields'] as $customField) {
261
                $customFields[] = new CustomField($customField['name'], $customField['value']);
262
            }
263
        }
264
265 11
        $model = new self();
266 11
        $model->id = $data['id'];
267 11
        $model->amountDue = new Money($data['amount_due'], $currency);
268 11
        $model->amountPaid = new Money($data['amount_paid'], $currency);
269 11
        $model->amountRemaining = new Money($data['amount_remaining'], $currency);
270 11
        $model->applicationFeeAmount = isset($data['application_fee_amount']) ? new Money($data['application_fee_amount'], $currency) : null;
271 11
        $model->attemptCount = (int) $data['attempt_count'];
272 11
        $model->attempted = (bool) $data['attempted'];
273 11
        $model->autoAdvance = (bool) $data['auto_advance'];
274 11
        $model->collectionMethod = $data['collection_method'];
275 11
        $model->billingReason = $data['billing_reason'];
276 11
        $model->charge = $data['charge'];
277 11
        $model->createdAt = new \DateTimeImmutable('@'.$data['created']);
278 11
        $model->currency = $currency;
279 11
        $model->customFields = $customFields;
280 11
        $model->customer = $data['customer'];
281 11
        $model->defaultSource = $data['default_source'] ?? null;
282 11
        $model->description = $data['description'];
283 11
        $model->discount = isset($data['discount']) ? Discount::createFromArray($data['discount']) : null;
284 11
        $model->dueAt = isset($data['due_at']) ? new \DateTimeImmutable('@'.$data['due_at']) : null;
285 11
        $model->endingBalance = isset($data['ending_balance']) ? new Money($data['ending_balance'], $currency) : null;
286 11
        $model->footer = $data['footer'] ?? null;
287 11
        $model->hostedInvoiceUrl = $data['hosted_invoice_url'];
288 11
        $model->invoicePdf = $data['invoice_pdf'];
289 11
        $model->lines = LineItemCollection::createFromArray($data['lines']);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Shapin\Stripe\Model\Inv...omArray($data['lines']) of type object<self> is incompatible with the declared type object<Shapin\Stripe\Mod...ice\LineItemCollection> of property $lines.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
290 11
        $model->live = $data['livemode'];
291 11
        $model->metadata = MetadataCollection::createFromArray($data['metadata']);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Shapin\Stripe\Model\Met...rray($data['metadata']) of type object<self> is incompatible with the declared type object<Shapin\Stripe\Model\MetadataCollection> of property $metadata.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
292 11
        $model->nextPaymentAttempt = isset($data['next_payment_attempt']) ? new \DateTimeImmutable('@'.$data['next_payment_attempt']) : null;
293 11
        $model->number = $data['number'];
294 11
        $model->paid = (bool) $data['paid'];
295 11
        $model->paymentIntentId = $data['payment_intent'];
296 11
        $model->periodEndAt = new \DateTimeImmutable('@'.$data['period_end']);
297 11
        $model->periodStartAt = new \DateTimeImmutable('@'.$data['period_start']);
298 11
        $model->receiptNumber = $data['receipt_number'];
299 11
        $model->startingBalance = new Money($data['starting_balance'], $currency);
300 11
        $model->statementDescriptor = $data['statement_descriptor'];
301 11
        $model->status = $data['status'];
302 11
        $model->subscription = $data['subscription'];
303 11
        $model->subscriptionProrationAt = isset($data['subscription_proration_at']) ? new \DateTimeImmutable('@'.$data['subscription_proration_at']) : null;
304 11
        $model->subtotal = new Money($data['subtotal'], $currency);
305 11
        $model->tax = new Money($data['tax'], $currency);
306 11
        $model->taxPercent = (float) $data['tax_percent'];
307 11
        $model->thresholdReason = isset($data['threshold_reason']) ? ThresholdReason::createFromArray($data['threshold_reason']) : null;
308 11
        $model->total = new Money($data['total'], $currency);
309 11
        $model->webhooksDeliveredAt = isset($data['webhooks_delivered_at']) ? new \DateTimeImmutable('@'.$data['webhooks_delivered_at']) : null;
310
311 11
        return $model;
312
    }
313
314 1
    public function getId(): ?string
315
    {
316 1
        return $this->id;
317
    }
318
319 1
    public function getAmountDue(): Money
320
    {
321 1
        return $this->amountDue;
322
    }
323
324 1
    public function getAmountPaid(): Money
325
    {
326 1
        return $this->amountPaid;
327
    }
328
329 1
    public function getAmountRemaining(): Money
330
    {
331 1
        return $this->amountRemaining;
332
    }
333
334 1
    public function getApplicationFeeAmount(): ?Money
335
    {
336 1
        return $this->applicationFeeAmount;
337
    }
338
339 1
    public function getAttemptCount(): int
340
    {
341 1
        return $this->attemptCount;
342
    }
343
344 1
    public function isAttempted(): bool
345
    {
346 1
        return $this->attempted;
347
    }
348
349 1
    public function isAutoAdvance(): bool
350
    {
351 1
        return $this->autoAdvance;
352
    }
353
354
    public function getBilling(): string
355
    {
356
        @trigger_error('Calling the method getBilling is deprecated since Stripe API 2019-10-17. Use getCollectionMethod instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
357
358
        return $this->collectionMethod;
359
    }
360
361 1
    public function getCollectionMethod(): string
362
    {
363 1
        return $this->collectionMethod;
364
    }
365
366 1
    public function getBillingReason(): string
367
    {
368 1
        return $this->billingReason;
369
    }
370
371 1
    public function getCharge(): ?string
372
    {
373 1
        return $this->charge;
374
    }
375
376 1
    public function getCreatedAt(): \DateTimeImmutable
377
    {
378 1
        return $this->createdAt;
379
    }
380
381 1
    public function getCurrency(): Currency
382
    {
383 1
        return $this->currency;
384
    }
385
386 1
    public function getCustomFields(): array
387
    {
388 1
        return $this->customFields;
389
    }
390
391 1
    public function getCustomer(): string
392
    {
393 1
        return $this->customer;
394
    }
395
396 1
    public function getDefaultSource(): ?string
397
    {
398 1
        return $this->defaultSource;
399
    }
400
401 1
    public function getDescription(): ?string
402
    {
403 1
        return $this->description;
404
    }
405
406 1
    public function getDiscount(): ?Discount
407
    {
408 1
        return $this->discount;
409
    }
410
411 1
    public function getDueAt(): ?\DateTimeImmutable
412
    {
413 1
        return $this->dueAt;
414
    }
415
416 1
    public function getEndingBalance(): ?Money
417
    {
418 1
        return $this->endingBalance;
419
    }
420
421 1
    public function getFooter(): ?string
422
    {
423 1
        return $this->footer;
424
    }
425
426 1
    public function getHostedInvoiceUrl(): ?string
427
    {
428 1
        return $this->hostedInvoiceUrl;
429
    }
430
431 1
    public function getInvoicePdf(): ?string
432
    {
433 1
        return $this->invoicePdf;
434
    }
435
436 1
    public function getLines(): LineItemCollection
437
    {
438 1
        return $this->lines;
439
    }
440
441 1
    public function getNextPaymentAttempt(): ?\DateTimeImmutable
442
    {
443 1
        return $this->nextPaymentAttempt;
444
    }
445
446 1
    public function getNumber(): string
447
    {
448 1
        return $this->number;
449
    }
450
451 1
    public function isPaid(): bool
452
    {
453 1
        return $this->paid;
454
    }
455
456 1
    public function getPaymentIntentId(): ?string
457
    {
458 1
        return $this->paymentIntentId;
459
    }
460
461 1
    public function getPeriodEndAt(): \DateTimeImmutable
462
    {
463 1
        return $this->periodEndAt;
464
    }
465
466 1
    public function getPeriodStartAt(): \DateTimeImmutable
467
    {
468 1
        return $this->periodStartAt;
469
    }
470
471 1
    public function getReceiptNumber(): ?string
472
    {
473 1
        return $this->receiptNumber;
474
    }
475
476 1
    public function getStartingBalance(): Money
477
    {
478 1
        return $this->startingBalance;
479
    }
480
481 1
    public function getStatementDescriptor(): ?string
482
    {
483 1
        return $this->statementDescriptor;
484
    }
485
486 1
    public function getStatus(): string
487
    {
488 1
        return $this->status;
489
    }
490
491 1
    public function getSubscription(): ?string
492
    {
493 1
        return $this->subscription;
494
    }
495
496 1
    public function getSubscriptionProrationAt(): ?\DateTimeImmutable
497
    {
498 1
        return $this->subscriptionProrationAt;
499
    }
500
501 1
    public function getSubtotal(): Money
502
    {
503 1
        return $this->subtotal;
504
    }
505
506 1
    public function getTax(): Money
507
    {
508 1
        return $this->tax;
509
    }
510
511 1
    public function getTaxPercent(): float
512
    {
513 1
        return $this->taxPercent;
514
    }
515
516 1
    public function getThresholdReason(): ?ThresholdReason
517
    {
518 1
        return $this->thresholdReason;
519
    }
520
521 1
    public function getTotal(): Money
522
    {
523 1
        return $this->total;
524
    }
525
526 1
    public function getWebhooksDeliveredAt(): ?\DateTimeImmutable
527
    {
528 1
        return $this->webhooksDeliveredAt;
529
    }
530
}
531