Completed
Push — master ( 99b9b3...0bcb3b )
by Kamil
18:37
created

Order   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 542
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 7

Importance

Changes 0
Metric Value
wmc 64
lcom 7
cbo 7
dl 0
loc 542
rs 3.4883
c 0
b 0
f 0

49 Methods

Rating   Name   Duplication   Size   Complexity  
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 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 6 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 getTokenValue() 0 4 1
A setTokenValue() 0 4 1
A getCustomerIp() 0 4 1
A setCustomerIp() 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
namespace Sylius\Component\Core\Model;
13
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\Common\Collections\Collection;
16
use Sylius\Component\Channel\Model\ChannelInterface as BaseChannelInterface;
17
use Sylius\Component\Core\OrderCheckoutStates;
18
use Sylius\Component\Core\OrderPaymentStates;
19
use Sylius\Component\Core\OrderShippingStates;
20
use Sylius\Component\Customer\Model\CustomerInterface as BaseCustomerInterface;
21
use Sylius\Component\Order\Model\Order as BaseOrder;
22
use Sylius\Component\Payment\Model\PaymentInterface as BasePaymentInterface;
23
use Sylius\Component\Promotion\Model\PromotionCouponInterface as BaseCouponInterface;
24
use Sylius\Component\Promotion\Model\PromotionInterface as BasePromotionInterface;
25
use Webmozart\Assert\Assert;
26
27
/**
28
 * @author Paweł Jędrzejewski <[email protected]>
29
 * @author Michał Marcinkowski <[email protected]>
30
 */
31
class Order extends BaseOrder implements OrderInterface
32
{
33
    /**
34
     * @var BaseCustomerInterface
35
     */
36
    protected $customer;
37
38
    /**
39
     * @var ChannelInterface
40
     */
41
    protected $channel;
42
43
    /**
44
     * @var AddressInterface
45
     */
46
    protected $shippingAddress;
47
48
    /**
49
     * @var AddressInterface
50
     */
51
    protected $billingAddress;
52
53
    /**
54
     * @var Collection|BasePaymentInterface[]
55
     */
56
    protected $payments;
57
58
    /**
59
     * @var Collection|ShipmentInterface[]
60
     */
61
    protected $shipments;
62
63
    /**
64
     * @var string
65
     */
66
    protected $currencyCode;
67
68
    /**
69
     * @var string
70
     */
71
    protected $localeCode;
72
73
    /**
74
     * @var BaseCouponInterface
75
     */
76
    protected $promotionCoupon;
77
78
    /**
79
     * @var string
80
     */
81
    protected $checkoutState = OrderCheckoutStates::STATE_CART;
82
83
    /**
84
     * @var string
85
     */
86
    protected $paymentState = OrderPaymentStates::STATE_CART;
87
88
    /**
89
     * @var string
90
     */
91
    protected $shippingState = OrderShippingStates::STATE_CART;
92
93
    /**
94
     * @var Collection|BasePromotionInterface[]
95
     */
96
    protected $promotions;
97
98
    /**
99
     * @var string
100
     */
101
    protected $tokenValue;
102
103
    /**
104
     * @var string
105
     */
106
    protected $customerIp;
107
108
    public function __construct()
109
    {
110
        parent::__construct();
111
112
        $this->payments = new ArrayCollection();
113
        $this->shipments = new ArrayCollection();
114
        $this->promotions = new ArrayCollection();
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function getCustomer()
121
    {
122
        return $this->customer;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function setCustomer(BaseCustomerInterface $customer = null)
129
    {
130
        $this->customer = $customer;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function getChannel()
137
    {
138
        return $this->channel;
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function setChannel(BaseChannelInterface $channel = null)
145
    {
146
        $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...
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function getUser()
153
    {
154
        if (null === $this->customer) {
155
            return null;
156
        }
157
158
        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...
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function getShippingAddress()
165
    {
166
        return $this->shippingAddress;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function setShippingAddress(AddressInterface $address)
173
    {
174
        $this->shippingAddress = $address;
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function getBillingAddress()
181
    {
182
        return $this->billingAddress;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function setBillingAddress(AddressInterface $address)
189
    {
190
        $this->billingAddress = $address;
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196
    public function getCheckoutState()
197
    {
198
        return $this->checkoutState;
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204
    public function setCheckoutState($checkoutState)
205
    {
206
        $this->checkoutState = $checkoutState;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function getPaymentState()
213
    {
214
        return $this->paymentState;
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function setPaymentState($paymentState)
221
    {
222
        $this->paymentState = $paymentState;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function getItemUnits()
229
    {
230
        $units = new ArrayCollection();
231
232
        /** @var $item OrderItem */
233
        foreach ($this->getItems() as $item) {
234
            foreach ($item->getUnits() as $unit) {
235
                $units->add($unit);
236
            }
237
        }
238
239
        return $units;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function getItemUnitsByVariant(ProductVariantInterface $variant)
246
    {
247
        return $this->getItemUnits()->filter(function (OrderItemUnitInterface $itemUnit) use ($variant) {
248
            return $variant === $itemUnit->getStockable();
249
        });
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function getPayments()
256
    {
257
        return $this->payments;
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function hasPayments()
264
    {
265
        return !$this->payments->isEmpty();
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function addPayment(BasePaymentInterface $payment)
272
    {
273
        /** @var $payment PaymentInterface */
274
        if (!$this->hasPayment($payment)) {
275
            $this->payments->add($payment);
276
            $payment->setOrder($this);
277
        }
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283
    public function removePayment(BasePaymentInterface $payment)
284
    {
285
        /** @var $payment PaymentInterface */
286
        if ($this->hasPayment($payment)) {
287
            $this->payments->removeElement($payment);
288
            $payment->setOrder(null);
289
        }
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295
    public function hasPayment(BasePaymentInterface $payment)
296
    {
297
        return $this->payments->contains($payment);
298
    }
299
300
    /**
301
     * @param string|null $state
302
     *
303
     * @return BasePaymentInterface|null
304
     */
305
    public function getLastPayment($state = null)
306
    {
307
        if ($this->payments->isEmpty()) {
308
            return null;
309
        }
310
311
        $payment = $this->payments->filter(function (BasePaymentInterface $payment) use ($state) {
312
            return null === $state || $payment->getState() === $state;
313
        })->last();
314
315
        return $payment !== false ? $payment : null;
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321
    public function getShipments()
322
    {
323
        return $this->shipments;
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329
    public function hasShipments()
330
    {
331
        return !$this->shipments->isEmpty();
332
    }
333
334
    /**
335
     * {@inheritdoc}
336
     */
337
    public function addShipment(ShipmentInterface $shipment)
338
    {
339
        if (!$this->hasShipment($shipment)) {
340
            $shipment->setOrder($this);
341
            $this->shipments->add($shipment);
342
        }
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348
    public function removeShipment(ShipmentInterface $shipment)
349
    {
350
        if ($this->hasShipment($shipment)) {
351
            $shipment->setOrder(null);
352
            $this->shipments->removeElement($shipment);
353
        }
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359
    public function removeShipments()
360
    {
361
        $this->shipments->clear();
362
    }
363
364
    /**
365
     * {@inheritdoc}
366
     */
367
    public function hasShipment(ShipmentInterface $shipment)
368
    {
369
        return $this->shipments->contains($shipment);
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375
    public function getPromotionCoupon()
376
    {
377
        return $this->promotionCoupon;
378
    }
379
380
    /**
381
     * {@inheritdoc}
382
     */
383
    public function setPromotionCoupon(BaseCouponInterface $coupon = null)
384
    {
385
        $this->promotionCoupon = $coupon;
386
    }
387
388
    /**
389
     * {@inheritdoc}
390
     */
391
    public function getPromotionSubjectTotal()
392
    {
393
        return $this->getItemsTotal();
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    public function getPromotionSubjectCount()
400
    {
401
        return $this->getTotalQuantity();
402
    }
403
404
    /**
405
     * {@inheritdoc}
406
     */
407
    public function getCurrencyCode()
408
    {
409
        return $this->currencyCode;
410
    }
411
412
    /**
413
     * {@inheritdoc}
414
     */
415
    public function setCurrencyCode($currencyCode)
416
    {
417
        Assert::string($currencyCode);
418
419
        $this->currencyCode = $currencyCode;
420
    }
421
422
    /**
423
     * {@inheritdoc}
424
     */
425
    public function getLocaleCode()
426
    {
427
        return $this->localeCode;
428
    }
429
430
    /**
431
     * {@inheritdoc}
432
     */
433
    public function setLocaleCode($localeCode)
434
    {
435
        Assert::string($localeCode);
436
437
        $this->localeCode = $localeCode;
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function getShippingState()
444
    {
445
        return $this->shippingState;
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451
    public function setShippingState($state)
452
    {
453
        $this->shippingState = $state;
454
    }
455
456
    /**
457
     * {@inheritdoc}
458
     */
459
    public function hasPromotion(BasePromotionInterface $promotion)
460
    {
461
        return $this->promotions->contains($promotion);
462
    }
463
464
    /**
465
     * {@inheritdoc}
466
     */
467
    public function addPromotion(BasePromotionInterface $promotion)
468
    {
469
        if (!$this->hasPromotion($promotion)) {
470
            $this->promotions->add($promotion);
471
        }
472
    }
473
474
    /**
475
     * {@inheritdoc}
476
     */
477
    public function removePromotion(BasePromotionInterface $promotion)
478
    {
479
        if ($this->hasPromotion($promotion)) {
480
            $this->promotions->removeElement($promotion);
481
        }
482
    }
483
484
    /**
485
     * {@inheritdoc}
486
     */
487
    public function getPromotions()
488
    {
489
        return $this->promotions;
490
    }
491
492
    /**
493
     * Returns sum of neutral and non neutral tax adjustments on order and total tax of order items.
494
     *
495
     * {@inheritdoc}
496
     */
497
    public function getTaxTotal()
498
    {
499
        $taxTotal = 0;
500
501
        foreach ($this->getAdjustments(AdjustmentInterface::TAX_ADJUSTMENT) as $taxAdjustment) {
502
            $taxTotal += $taxAdjustment->getAmount();
503
        }
504
        foreach ($this->items as $item) {
505
            $taxTotal += $item->getTaxTotal();
506
        }
507
508
        return $taxTotal;
509
    }
510
511
    /**
512
     * Returns shipping fee together with taxes decreased by shipping discount.
513
     *
514
     * @return int
515
     */
516
    public function getShippingTotal()
517
    {
518
        $shippingTotal = $this->getAdjustmentsTotal(AdjustmentInterface::SHIPPING_ADJUSTMENT);
519
        $shippingTotal += $this->getAdjustmentsTotal(AdjustmentInterface::ORDER_SHIPPING_PROMOTION_ADJUSTMENT);
520
        $shippingTotal += $this->getAdjustmentsTotal(AdjustmentInterface::TAX_ADJUSTMENT);
521
522
        return $shippingTotal;
523
    }
524
525
    /**
526
     * Returns amount of order discount. Does not include order item and shipping discounts.
527
     *
528
     * @return int
529
     */
530
    public function getOrderPromotionTotal()
531
    {
532
        $orderPromotionTotal = 0;
533
534
        foreach ($this->items as $item) {
535
            $orderPromotionTotal += $item->getAdjustmentsTotalRecursively(AdjustmentInterface::ORDER_PROMOTION_ADJUSTMENT);
536
        }
537
538
        return $orderPromotionTotal;
539
    }
540
541
    /**
542
     * {@inheritdoc}
543
     */
544
    public function getTokenValue()
545
    {
546
        return $this->tokenValue;
547
    }
548
549
    /**
550
     * {@inheritdoc}
551
     */
552
    public function setTokenValue($tokenValue)
553
    {
554
        $this->tokenValue = $tokenValue;
555
    }
556
557
    /**
558
     * {@inheritdoc}
559
     */
560
    public function getCustomerIp()
561
    {
562
        return $this->customerIp;
563
    }
564
565
    /**
566
     * {@inheritdoc}
567
     */
568
    public function setCustomerIp($customerIp)
569
    {
570
        $this->customerIp = $customerIp;
571
    }
572
}
573