Completed
Push — master ( d81c19...f57266 )
by Kamil
20s
created

Order   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 549
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 5

Importance

Changes 0
Metric Value
wmc 67
lcom 7
cbo 5
dl 0
loc 549
rs 3.0612
c 0
b 0
f 0

50 Methods

Rating   Name   Duplication   Size   Complexity  
A setTokenValue() 0 4 1
A getTokenValue() 0 4 1
A getLocaleCode() 0 4 1
A setLocaleCode() 0 6 1
A getShippingState() 0 4 1
A setShippingState() 0 4 1
A hasPromotion() 0 4 1
A addPromotion() 0 6 2
A removePromotion() 0 6 2
A getPromotions() 0 4 1
A getTaxTotal() 0 13 3
A getShippingTotal() 0 8 1
A getOrderPromotionTotal() 0 10 2
A getCustomerIp() 0 4 1
A setCustomerIp() 0 4 1
A __construct() 0 8 1
A getCustomer() 0 4 1
A setCustomer() 0 4 1
A getChannel() 0 4 1
A setChannel() 0 4 1
A getUser() 0 8 2
A getShippingAddress() 0 4 1
A setShippingAddress() 0 4 1
A getBillingAddress() 0 4 1
A setBillingAddress() 0 4 1
A getCheckoutState() 0 4 1
A setCheckoutState() 0 4 1
A getPaymentState() 0 4 1
A setPaymentState() 0 4 1
A getItemUnits() 0 13 3
A getItemUnitsByVariant() 0 6 1
A getPayments() 0 4 1
A hasPayments() 0 4 1
A addPayment() 0 8 2
A removePayment() 0 8 2
A hasPayment() 0 4 1
A getLastPayment() 0 12 4
A isShippingRequired() 0 10 3
A getShipments() 0 4 1
A hasShipments() 0 4 1
A addShipment() 0 7 2
A removeShipment() 0 7 2
A removeShipments() 0 4 1
A hasShipment() 0 4 1
A getPromotionCoupon() 0 4 1
A setPromotionCoupon() 0 4 1
A getPromotionSubjectTotal() 0 4 1
A getPromotionSubjectCount() 0 4 1
A getCurrencyCode() 0 4 1
A setCurrencyCode() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Order 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 Order, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Component\Core\Model;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Collections\Collection;
18
use Sylius\Component\Channel\Model\ChannelInterface as BaseChannelInterface;
19
use Sylius\Component\Core\OrderCheckoutStates;
20
use Sylius\Component\Core\OrderPaymentStates;
21
use Sylius\Component\Core\OrderShippingStates;
22
use Sylius\Component\Customer\Model\CustomerInterface as BaseCustomerInterface;
23
use Sylius\Component\Order\Model\Order as BaseOrder;
24
use Sylius\Component\Payment\Model\PaymentInterface as BasePaymentInterface;
25
use Sylius\Component\Promotion\Model\PromotionCouponInterface as BaseCouponInterface;
26
use Sylius\Component\Promotion\Model\PromotionInterface as BasePromotionInterface;
27
use Sylius\Component\User\Model\UserInterface as BaseUserInterface;
28
use Webmozart\Assert\Assert;
29
30
class Order extends BaseOrder implements OrderInterface
31
{
32
    /**
33
     * @var BaseCustomerInterface
34
     */
35
    protected $customer;
36
37
    /**
38
     * @var ChannelInterface
39
     */
40
    protected $channel;
41
42
    /**
43
     * @var AddressInterface
44
     */
45
    protected $shippingAddress;
46
47
    /**
48
     * @var AddressInterface
49
     */
50
    protected $billingAddress;
51
52
    /**
53
     * @var Collection|BasePaymentInterface[]
54
     */
55
    protected $payments;
56
57
    /**
58
     * @var Collection|ShipmentInterface[]
59
     */
60
    protected $shipments;
61
62
    /**
63
     * @var string
64
     */
65
    protected $currencyCode;
66
67
    /**
68
     * @var string
69
     */
70
    protected $localeCode;
71
72
    /**
73
     * @var BaseCouponInterface
74
     */
75
    protected $promotionCoupon;
76
77
    /**
78
     * @var string
79
     */
80
    protected $checkoutState = OrderCheckoutStates::STATE_CART;
81
82
    /**
83
     * @var string
84
     */
85
    protected $paymentState = OrderPaymentStates::STATE_CART;
86
87
    /**
88
     * @var string
89
     */
90
    protected $shippingState = OrderShippingStates::STATE_CART;
91
92
    /**
93
     * @var Collection|BasePromotionInterface[]
94
     */
95
    protected $promotions;
96
97
    /**
98
     * @var string
99
     */
100
    protected $tokenValue;
101
102
    /**
103
     * @var string
104
     */
105
    protected $customerIp;
106
107
    public function __construct()
108
    {
109
        parent::__construct();
110
111
        $this->payments = new ArrayCollection();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Common\Collections\ArrayCollection() of type object<Doctrine\Common\C...ctions\ArrayCollection> is incompatible with the declared type object<Doctrine\Common\C...odel\PaymentInterface>> of property $payments.

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...
112
        $this->shipments = new ArrayCollection();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Common\Collections\ArrayCollection() of type object<Doctrine\Common\C...ctions\ArrayCollection> is incompatible with the declared type object<Doctrine\Common\C...del\ShipmentInterface>> of property $shipments.

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...
113
        $this->promotions = new ArrayCollection();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Common\Collections\ArrayCollection() of type object<Doctrine\Common\C...ctions\ArrayCollection> is incompatible with the declared type object<Doctrine\Common\C...el\PromotionInterface>> of property $promotions.

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...
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function getCustomer(): ?BaseCustomerInterface
120
    {
121
        return $this->customer;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function setCustomer(?BaseCustomerInterface $customer): void
128
    {
129
        $this->customer = $customer;
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function getChannel(): ?BaseChannelInterface
136
    {
137
        return $this->channel;
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function setChannel(?BaseChannelInterface $channel): void
144
    {
145
        $this->channel = $channel;
0 ignored issues
show
Documentation Bug introduced by
It seems like $channel can also be of type object<Sylius\Component\...Model\ChannelInterface>. However, the property $channel is declared as type object<Sylius\Component\...Model\ChannelInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getUser(): ?BaseUserInterface
152
    {
153
        if (null === $this->customer) {
154
            return null;
155
        }
156
157
        return $this->customer->getUser();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Customer\Model\CustomerInterface as the method getUser() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\Customer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function getShippingAddress(): ?AddressInterface
164
    {
165
        return $this->shippingAddress;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function setShippingAddress(?AddressInterface $address): void
172
    {
173
        $this->shippingAddress = $address;
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function getBillingAddress(): ?AddressInterface
180
    {
181
        return $this->billingAddress;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function setBillingAddress(?AddressInterface $address): void
188
    {
189
        $this->billingAddress = $address;
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195
    public function getCheckoutState(): ?string
196
    {
197
        return $this->checkoutState;
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function setCheckoutState(?string $checkoutState): void
204
    {
205
        $this->checkoutState = $checkoutState;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function getPaymentState(): ?string
212
    {
213
        return $this->paymentState;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function setPaymentState(?string $paymentState): void
220
    {
221
        $this->paymentState = $paymentState;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    public function getItemUnits(): Collection
228
    {
229
        $units = new ArrayCollection();
230
231
        /** @var OrderItem $item */
232
        foreach ($this->getItems() as $item) {
233
            foreach ($item->getUnits() as $unit) {
234
                $units->add($unit);
235
            }
236
        }
237
238
        return $units;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244
    public function getItemUnitsByVariant(ProductVariantInterface $variant): Collection
245
    {
246
        return $this->getItemUnits()->filter(function (OrderItemUnitInterface $itemUnit) use ($variant): bool {
247
            return $variant === $itemUnit->getStockable();
248
        });
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public function getPayments(): Collection
255
    {
256
        return $this->payments;
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     */
262
    public function hasPayments(): bool
263
    {
264
        return !$this->payments->isEmpty();
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270
    public function addPayment(BasePaymentInterface $payment): void
271
    {
272
        /** @var PaymentInterface $payment */
273
        if (!$this->hasPayment($payment)) {
274
            $this->payments->add($payment);
275
            $payment->setOrder($this);
276
        }
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function removePayment(BasePaymentInterface $payment): void
283
    {
284
        /** @var PaymentInterface $payment */
285
        if ($this->hasPayment($payment)) {
286
            $this->payments->removeElement($payment);
287
            $payment->setOrder(null);
288
        }
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294
    public function hasPayment(BasePaymentInterface $payment): bool
295
    {
296
        return $this->payments->contains($payment);
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    public function getLastPayment(?string $state = null): ?PaymentInterface
303
    {
304
        if ($this->payments->isEmpty()) {
305
            return null;
306
        }
307
308
        $payment = $this->payments->filter(function (BasePaymentInterface $payment) use ($state): bool {
309
            return null === $state || $payment->getState() === $state;
310
        })->last();
311
312
        return $payment !== false ? $payment : null;
313
    }
314
315
    /**
316
     * @return bool
317
     */
318
    public function isShippingRequired(): bool
319
    {
320
        foreach ($this->getItems() as $orderItem) {
321
            if ($orderItem->getVariant()->isShippingRequired()) {
322
                return true;
323
            }
324
        }
325
326
        return false;
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332
    public function getShipments(): Collection
333
    {
334
        return $this->shipments;
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340
    public function hasShipments(): bool
341
    {
342
        return !$this->shipments->isEmpty();
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348
    public function addShipment(ShipmentInterface $shipment): void
349
    {
350
        if (!$this->hasShipment($shipment)) {
351
            $shipment->setOrder($this);
352
            $this->shipments->add($shipment);
353
        }
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359
    public function removeShipment(ShipmentInterface $shipment): void
360
    {
361
        if ($this->hasShipment($shipment)) {
362
            $shipment->setOrder(null);
363
            $this->shipments->removeElement($shipment);
364
        }
365
    }
366
367
    public function removeShipments(): void
368
    {
369
        $this->shipments->clear();
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375
    public function hasShipment(ShipmentInterface $shipment): bool
376
    {
377
        return $this->shipments->contains($shipment);
378
    }
379
380
    /**
381
     * {@inheritdoc}
382
     */
383
    public function getPromotionCoupon(): ?BaseCouponInterface
384
    {
385
        return $this->promotionCoupon;
386
    }
387
388
    /**
389
     * {@inheritdoc}
390
     */
391
    public function setPromotionCoupon(?BaseCouponInterface $coupon): void
392
    {
393
        $this->promotionCoupon = $coupon;
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    public function getPromotionSubjectTotal(): int
400
    {
401
        return $this->getItemsTotal();
402
    }
403
404
    /**
405
     * {@inheritdoc}
406
     */
407
    public function getPromotionSubjectCount(): int
408
    {
409
        return $this->getTotalQuantity();
410
    }
411
412
    /**
413
     * {@inheritdoc}
414
     */
415
    public function getCurrencyCode(): ?string
416
    {
417
        return $this->currencyCode;
418
    }
419
420
    /**
421
     * {@inheritdoc}
422
     */
423
    public function setCurrencyCode(?string $currencyCode): void
424
    {
425
        $this->currencyCode = $currencyCode;
426
    }
427
428
    /**
429
     * {@inheritdoc}
430
     */
431
    public function getLocaleCode(): ?string
432
    {
433
        return $this->localeCode;
434
    }
435
436
    /**
437
     * {@inheritdoc}
438
     */
439
    public function setLocaleCode(?string $localeCode): void
440
    {
441
        Assert::string($localeCode);
442
443
        $this->localeCode = $localeCode;
444
    }
445
446
    /**
447
     * {@inheritdoc}
448
     */
449
    public function getShippingState(): ?string
450
    {
451
        return $this->shippingState;
452
    }
453
454
    /**
455
     * {@inheritdoc}
456
     */
457
    public function setShippingState(?string $state): void
458
    {
459
        $this->shippingState = $state;
460
    }
461
462
    /**
463
     * {@inheritdoc}
464
     */
465
    public function hasPromotion(BasePromotionInterface $promotion): bool
466
    {
467
        return $this->promotions->contains($promotion);
468
    }
469
470
    /**
471
     * {@inheritdoc}
472
     */
473
    public function addPromotion(BasePromotionInterface $promotion): void
474
    {
475
        if (!$this->hasPromotion($promotion)) {
476
            $this->promotions->add($promotion);
477
        }
478
    }
479
480
    /**
481
     * {@inheritdoc}
482
     */
483
    public function removePromotion(BasePromotionInterface $promotion): void
484
    {
485
        if ($this->hasPromotion($promotion)) {
486
            $this->promotions->removeElement($promotion);
487
        }
488
    }
489
490
    /**
491
     * {@inheritdoc}
492
     */
493
    public function getPromotions(): Collection
494
    {
495
        return $this->promotions;
496
    }
497
498
    /**
499
     * Returns sum of neutral and non neutral tax adjustments on order and total tax of order items.
500
     *
501
     * {@inheritdoc}
502
     */
503
    public function getTaxTotal(): int
504
    {
505
        $taxTotal = 0;
506
507
        foreach ($this->getAdjustments(AdjustmentInterface::TAX_ADJUSTMENT) as $taxAdjustment) {
508
            $taxTotal += $taxAdjustment->getAmount();
509
        }
510
        foreach ($this->items as $item) {
511
            $taxTotal += $item->getTaxTotal();
512
        }
513
514
        return $taxTotal;
515
    }
516
517
    /**
518
     * Returns shipping fee together with taxes decreased by shipping discount.
519
     *
520
     * {@inheritdoc}
521
     */
522
    public function getShippingTotal(): int
523
    {
524
        $shippingTotal = $this->getAdjustmentsTotal(AdjustmentInterface::SHIPPING_ADJUSTMENT);
525
        $shippingTotal += $this->getAdjustmentsTotal(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT);
526
        $shippingTotal += $this->getAdjustmentsTotal(AdjustmentInterface::TAX_ADJUSTMENT);
527
528
        return $shippingTotal;
529
    }
530
531
    /**
532
     * Returns amount of order discount. Does not include order item and shipping discounts.
533
     *
534
     * {@inheritdoc}
535
     */
536
    public function getOrderPromotionTotal(): int
537
    {
538
        $orderPromotionTotal = 0;
539
540
        foreach ($this->items as $item) {
541
            $orderPromotionTotal += $item->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_PROMOTION_ADJUSTMENT);
542
        }
543
544
        return $orderPromotionTotal;
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550
    public function getTokenValue(): ?string
551
    {
552
        return $this->tokenValue;
553
    }
554
555
    /**
556
     * {@inheritdoc}
557
     */
558
    public function setTokenValue(?string $tokenValue): void
559
    {
560
        $this->tokenValue = $tokenValue;
561
    }
562
563
    /**
564
     * {@inheritdoc}
565
     */
566
    public function getCustomerIp(): ?string
567
    {
568
        return $this->customerIp;
569
    }
570
571
    /**
572
     * {@inheritdoc}
573
     */
574
    public function setCustomerIp(?string $customerIp): void
575
    {
576
        $this->customerIp = $customerIp;
577
    }
578
}
579