Completed
Pull Request — master (#85)
by Luke
04:25 queued 02:02
created

LaraCart   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 653
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 36
Bugs 5 Features 3
Metric Value
wmc 74
c 36
b 5
f 3
lcom 1
cbo 4
dl 0
loc 653
rs 5.261

35 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A setInstance() 0 10 1
A getAttribute() 0 4 1
A getAttributes() 0 4 1
A setAttribute() 0 6 1
A update() 0 13 2
A removeAttribute() 0 6 1
A addItem() 0 14 2
A increment() 0 8 1
A decrement() 0 14 2
A find() 0 12 3
A getItem() 0 4 1
A getItems() 0 11 3
A updateItem() 0 12 2
A removeItem() 0 13 3
A emptyCart() 0 8 1
A destroyCart() 0 12 1
A getCoupons() 0 4 1
A findCoupon() 0 4 1
A addCoupon() 0 10 2
A removeCoupon() 0 14 4
A getFee() 0 4 1
A addFee() 0 6 1
A removeFee() 0 6 1
C taxTotal() 0 30 7
A total() 0 14 3
A subTotal() 0 12 3
A count() 0 14 3
B formatMoney() 0 17 5
B feeTotals() 0 14 5
A getFees() 0 4 1
A totalDiscount() 0 12 3
A get() 0 15 4
A addLine() 0 4 1
B add() 0 25 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace LukePOLO\LaraCart;
4
5
use Illuminate\Auth\AuthManager;
6
use Illuminate\Contracts\Events\Dispatcher;
7
use Illuminate\Session\SessionManager;
8
use LukePOLO\LaraCart\Contracts\CouponContract;
9
use LukePOLO\LaraCart\Contracts\LaraCartContract;
10
11
/**
12
 * Class LaraCart
13
 *
14
 * @package LukePOLO\LaraCart
15
 */
16
class LaraCart implements LaraCartContract
17
{
18
    const QTY = 'qty';
19
    const HASH = 'generateCartHash';
20
    const PRICE = 'price';
21
    const SERVICE = 'laracart';
22
    const RANHASH = 'generateRandomCartItemHash';
23
24
    protected $events;
25
    protected $session;
26
    protected $authManager;
27
28
    public $cart;
29
30
    /**
31
     * LaraCart constructor.
32
     *
33
     * @param SessionManager $session
34
     * @param Dispatcher $events
35
     * @param AuthManager $authManager
36
     */
37
    public function __construct(SessionManager $session, Dispatcher $events, AuthManager $authManager)
38
    {
39
        $this->session = $session;
40
        $this->events = $events;
41
        $this->authManager = $authManager;
42
43
        $this->setInstance($this->session->get('laracart.instance', 'default'));
44
    }
45
46
    /**
47
     * Sets and Gets the instance of the cart in the session we should be using
48
     *
49
     * @param string $instance
50
     *
51
     * @return LaraCart
52
     */
53
    public function setInstance($instance = 'default')
54
    {
55
        $this->get($instance);
56
57
        $this->session->set('laracart.instance', $instance);
58
59
        $this->events->fire('laracart.new');
60
61
        return $this;
62
    }
63
64
    /**
65
     * Gets the instance in the session
66
     *
67
     * @param string $instance
68
     *
69
     * @return $this cart instance
70
     */
71
    public function get($instance = 'default')
72
    {
73
        if (config('laracart.cross_devices', false)) {
74
            if (!empty($cartSessionID = $this->authManager->user()->cart_session_id)) {
75
                $this->session->setId($cartSessionID);
76
                $this->session->start();
77
            }
78
        }
79
80
        if (empty($this->cart = $this->session->get(config('laracart.cache_prefix', 'laracart') . '.' . $instance))) {
81
            $this->cart = new Cart($instance);
82
        }
83
84
        return $this;
85
    }
86
87
    /**
88
     * Gets an an attribute from the cart
89
     *
90
     * @param $attribute
91
     * @param $defaultValue
92
     *
93
     * @return mixed
94
     */
95
    public function getAttribute($attribute, $defaultValue = null)
96
    {
97
        return array_get($this->cart->attributes, $attribute, $defaultValue);
98
    }
99
100
    /**
101
     * Gets all the carts attributes
102
     *
103
     * @return mixed
104
     */
105
    public function getAttributes()
106
    {
107
        return $this->cart->attributes;
108
    }
109
110
    /**
111
     * Adds an Attribute to the cart
112
     *
113
     * @param $attribute
114
     * @param $value
115
     */
116
    public function setAttribute($attribute, $value)
117
    {
118
        array_set($this->cart->attributes, $attribute, $value);
119
120
        $this->update();
121
    }
122
123
    /**
124
     * Updates cart session
125
     */
126
    public function update()
127
    {
128
        $this->session->set(config('laracart.cache_prefix', 'laracart') . '.' . $this->cart->instance, $this->cart);
129
130
        if (config('laracart.cross_devices', false)) {
131
            $this->authManager->user()->update([
132
                'cart_session_id',
133
                $this->session->getId()
134
            ]);
135
        }
136
137
        $this->events->fire('laracart.update', $this->cart);
138
    }
139
140
    /**
141
     * Removes an attribute from the cart
142
     *
143
     * @param $attribute
144
     */
145
    public function removeAttribute($attribute)
146
    {
147
        array_forget($this->cart->attributes, $attribute);
148
149
        $this->update();
150
    }
151
152
    /**
153
     * Creates a CartItem and then adds it to cart
154
     *
155
     * @param string|int $itemID
156
     * @param null $name
157
     * @param int $qty
158
     * @param string $price
159
     * @param array $options
160
     * @param bool|true $taxable
161
     *
162
     * @return CartItem
163
     */
164
    public function addLine($itemID, $name = null, $qty = 1, $price = '0.00', $options = [], $taxable = true)
165
    {
166
        return $this->add($itemID, $name, $qty, $price, $options, $taxable, true);
167
    }
168
169
    /**
170
     * Creates a CartItem and then adds it to cart
171
     *
172
     * @param $itemID
173
     * @param null $name
174
     * @param int $qty
175
     * @param string $price
176
     * @param array $options
177
     * @param bool|false $taxable
178
     * @param bool|false $lineItem
179
     *
180
     * @return CartItem
181
     */
182
    public function add(
183
        $itemID,
184
        $name = null,
185
        $qty = 1,
186
        $price = '0.00',
187
        $options = [],
188
        $taxable = true,
189
        $lineItem = false
190
    ) {
191
        $item = $this->addItem(
192
            new CartItem(
193
                $itemID,
194
                $name,
195
                $qty,
196
                $price,
197
                $options,
198
                $taxable,
199
                $lineItem
200
            )
201
        );
202
203
        $this->update();
204
205
        return $this->getItem($item->getHash());
206
    }
207
208
    /**
209
     * Adds the cartItem into the cart session
210
     *
211
     * @param CartItem $cartItem
212
     *
213
     * @return CartItem
214
     */
215
    public function addItem(CartItem $cartItem)
216
    {
217
        $itemHash = $cartItem->generateHash();
218
219
        if ($this->getItem($itemHash)) {
220
            $this->getItem($itemHash)->qty += $cartItem->qty;
0 ignored issues
show
Documentation introduced by
The property qty does not exist on object<LukePOLO\LaraCart\CartItem>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
221
        } else {
222
            $this->cart->items[] = $cartItem;
223
        }
224
225
        $this->events->fire('laracart.addItem', $cartItem);
226
227
        return $cartItem;
228
    }
229
230
    /**
231
     * Increment the quantity of a cartItem based on the itemHash
232
     *
233
     * @param $itemHash
234
     *
235
     * @return CartItem | null
236
     */
237
    public function increment($itemHash)
238
    {
239
        $item = $this->getItem($itemHash);
240
        $item->qty++;
0 ignored issues
show
Documentation introduced by
The property qty does not exist on object<LukePOLO\LaraCart\CartItem>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
241
        $this->update();
242
243
        return $item;
244
    }
245
246
    /**
247
     * Decrement the quantity of a cartItem based on the itemHash
248
     *
249
     * @param $itemHash
250
     *
251
     * @return CartItem | null
252
     */
253
    public function decrement($itemHash)
254
    {
255
        $item = $this->getItem($itemHash);
256
        if ($item->qty > 1) {
0 ignored issues
show
Documentation introduced by
The property qty does not exist on object<LukePOLO\LaraCart\CartItem>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
257
            $item->qty--;
0 ignored issues
show
Documentation introduced by
The property qty does not exist on object<LukePOLO\LaraCart\CartItem>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
258
            $this->update();
259
260
            return $item;
261
        }
262
        $this->removeItem($itemHash);
263
        $this->update();
264
265
        return null;
266
    }
267
268
    /*
269
     * Find items in the cart matching a data set
270
     *
271
     * @return array
272
     */
273
    public function find($data)
274
    {
275
        $matches = [];
276
277
        foreach ($this->getItems() as $item) {
278
            if ($item->find($data)) {
279
                $matches[] = $item;
280
            }
281
        }
282
283
        return $matches;
284
    }
285
286
    /**
287
     * Finds a cartItem based on the itemHash
288
     *
289
     * @param $itemHash
290
     *
291
     * @return CartItem | null
292
     */
293
    public function getItem($itemHash)
294
    {
295
        return array_get($this->getItems(), $itemHash);
296
    }
297
298
    /**
299
     * Gets all the items within the cart
300
     *
301
     * @return array
302
     */
303
    public function getItems()
304
    {
305
        $items = [];
306
        if (isset($this->cart->items) === true) {
307
            foreach ($this->cart->items as $item) {
308
                $items[$item->getHash()] = $item;
309
            }
310
        }
311
312
        return $items;
313
    }
314
315
    /**
316
     * Updates an items attributes
317
     *
318
     * @param $itemHash
319
     * @param $key
320
     * @param $value
321
     *
322
     * @return CartItem
323
     *
324
     * @throws Exceptions\InvalidPrice
325
     * @throws Exceptions\InvalidQuantity
326
     */
327
    public function updateItem($itemHash, $key, $value)
328
    {
329
        if (empty($item = $this->getItem($itemHash)) === false) {
330
            $item->$key = $value;
331
        }
332
333
        $item->generateHash();
334
335
        $this->update();
336
337
        return $item;
338
    }
339
340
    /**
341
     * Removes a CartItem based on the itemHash
342
     *
343
     * @param $itemHash
344
     */
345
    public function removeItem($itemHash)
346
    {
347
        foreach ($this->cart->items as $itemKey => $item) {
348
            if ($item->getHash() == $itemHash) {
349
                unset($this->cart->items[$itemKey]);
350
                break;
351
            }
352
        }
353
354
        $this->events->fire('laracart.removeItem', $itemHash);
355
356
        $this->update();
357
    }
358
359
    /**
360
     * Empties the carts items
361
     */
362
    public function emptyCart()
363
    {
364
        unset($this->cart->items);
365
366
        $this->update();
367
368
        $this->events->fire('laracart.empty', $this->cart->instance);
369
    }
370
371
    /**
372
     * Completely destroys cart and anything associated with it
373
     */
374
    public function destroyCart()
375
    {
376
        $instance = $this->cart->instance;
377
378
        $this->session->forget(config('laracart.cache_prefix', 'laracart') . '.' . $instance);
379
380
        $this->setInstance('default');
381
382
        $this->events->fire('laracart.destroy', $instance);
383
384
        $this->update();
385
    }
386
387
    /**
388
     * Gets the coupons for the current cart
389
     *
390
     * @return array
391
     */
392
    public function getCoupons()
393
    {
394
        return $this->cart->coupons;
395
    }
396
397
    /**
398
     * Finds a specific coupon in the cart
399
     *
400
     * @param $code
401
     * @return mixed
402
     */
403
    public function findCoupon($code)
404
    {
405
        return array_get($this->cart->coupons, $code);
406
    }
407
408
    /**
409
     * Applies a coupon to the cart
410
     *
411
     * @param CouponContract $coupon
412
     */
413
    public function addCoupon(CouponContract $coupon)
414
    {
415
        if (!$this->cart->multipleCoupons) {
416
            $this->cart->coupons = [];
417
        }
418
419
        $this->cart->coupons[$coupon->code] = $coupon;
0 ignored issues
show
Bug introduced by
Accessing code on the interface LukePOLO\LaraCart\Contracts\CouponContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
420
421
        $this->update();
422
    }
423
424
    /**
425
     * Removes a coupon in the cart
426
     *
427
     * @param $code
428
     */
429
    public function removeCoupon($code)
430
    {
431
        foreach ($this->getItems() as $item) {
432
            if (isset($item->code) && $item->code == $code) {
433
                $item->code = null;
434
                $item->discount = null;
435
                $item->couponInfo = [];
436
            }
437
        }
438
439
        array_forget($this->cart->coupons, $code);
440
441
        $this->update();
442
    }
443
444
    /**
445
     * Gets a speific fee from the fees array
446
     *
447
     * @param $name
448
     *
449
     * @return mixed
450
     */
451
    public function getFee($name)
452
    {
453
        return array_get($this->cart->fees, $name, new CartFee(null, false));
454
    }
455
456
    /**
457
     * Allows to charge for additional fees that may or may not be taxable
458
     * ex - service fee , delivery fee, tips
459
     *
460
     * @param $name
461
     * @param $amount
462
     * @param bool|false $taxable
463
     * @param array $options
464
     */
465
    public function addFee($name, $amount, $taxable = false, array $options = [])
466
    {
467
        array_set($this->cart->fees, $name, new CartFee($amount, $taxable, $options));
468
469
        $this->update();
470
    }
471
472
    /**
473
     * Reemoves a fee from the fee array
474
     *
475
     * @param $name
476
     */
477
    public function removeFee($name)
478
    {
479
        array_forget($this->cart->fees, $name);
480
481
        $this->update();
482
    }
483
484
    /**
485
     * Gets the total tax for the cart
486
     *
487
     * @param bool|true $format
488
     *
489
     * @return string
490
     */
491
    public function taxTotal($format = true)
492
    {
493
        $totalTax = 0;
494
        $discounted = 0;
495
        $totalDiscount = $this->totalDiscount(false);
496
497
        if ($this->count() != 0) {
498
            foreach ($this->getItems() as $item) {
499
                if ($discounted >= $totalDiscount) {
500
                    $totalTax += $item->tax();
501
                } else {
502
                    $itemPrice = $item->subTotal(false);
503
504
                    if (($discounted + $itemPrice) > $totalDiscount) {
505
                        $totalTax += $item->tax($totalDiscount - $discounted);
506
                    }
507
508
                    $discounted += $itemPrice;
509
                }
510
            }
511
        }
512
513
        foreach ($this->getFees() as $fee) {
514
            if ($fee->taxable) {
515
                $totalTax += $fee->amount * $fee->tax;
516
            }
517
        }
518
519
        return $this->formatMoney($totalTax, null, null, $format);
520
    }
521
522
    /**
523
     * Gets the total of the cart with or without tax
524
     *
525
     * @param boolean $format
526
     * @param boolean $withDiscount
527
     *
528
     * @return string
529
     */
530
    public function total($format = true, $withDiscount = true, $withTax = true)
531
    {
532
        $total = $this->subTotal(false) + $this->feeTotals(false);
533
534
        if ($withDiscount) {
535
            $total -= $this->totalDiscount(false);
536
        }
537
538
        if ($withTax) {
539
            $total += $this->taxTotal(false);
540
        }
541
542
        return $this->formatMoney($total, null, null, $format);
543
    }
544
545
    /**
546
     * Gets the subtotal of the cart with or without tax
547
     *
548
     * @param boolean $format
549
     * @param boolean $withDiscount
550
     *
551
     * @return string
552
     */
553
    public function subTotal($format = true, $withDiscount = true)
554
    {
555
        $total = 0;
556
557
        if ($this->count() != 0) {
558
            foreach ($this->getItems() as $item) {
559
                $total += $item->subTotal(false, $withDiscount);
560
            }
561
        }
562
563
        return $this->formatMoney($total, null, null, $format);
564
    }
565
566
    /**
567
     * Get the count based on qty, or number of unique items
568
     *
569
     * @param bool $withItemQty
570
     *
571
     * @return int
572
     */
573
    public function count($withItemQty = true)
574
    {
575
        $count = 0;
576
577
        foreach ($this->getItems() as $item) {
578
            if ($withItemQty) {
579
                $count += $item->qty;
580
            } else {
581
                $count++;
582
            }
583
        }
584
585
        return $count;
586
    }
587
588
    /**
589
     *
590
     * Formats the number into a money format based on the locale and international formats
591
     *
592
     * @param $number
593
     * @param $locale
594
     * @param $internationalFormat
595
     * @param $format
596
     *
597
     * @return string
598
     */
599
    public static function formatMoney($number, $locale = null, $internationalFormat = null, $format = true)
600
    {
601
        $number = number_format($number, 2, '.', '');
602
603
        if ($format) {
604
            setlocale(LC_MONETARY, null);
605
            setlocale(LC_MONETARY, empty($locale) ? config('laracart.locale', 'en_US.UTF-8') : $locale);
606
607
            if (empty($internationalFormat) === true) {
608
                $internationalFormat = config('laracart.international_format', false);
609
            }
610
611
            $number = money_format($internationalFormat ? '%i' : '%n', $number);
612
        }
613
614
        return $number;
615
    }
616
617
    /**
618
     * Gets all the fee totals
619
     *
620
     * @param boolean $format
621
     *
622
     * @return string
623
     */
624
    public function feeTotals($format = true, $withTax = false)
625
    {
626
        $feeTotal = 0;
627
628
        foreach ($this->getFees() as $fee) {
629
            $feeTotal += $fee->amount;
630
631
            if ($withTax && $fee->taxable && $fee->tax > 0) {
632
                $feeTotal += $fee->amount * $fee->tax;
633
            }
634
        }
635
636
        return $this->formatMoney($feeTotal, null, null, $format);
637
    }
638
639
    /**
640
     * Gets all the fees on the cart object
641
     *
642
     * @return mixed
643
     */
644
    public function getFees()
645
    {
646
        return $this->cart->fees;
647
    }
648
649
    /**
650
     * Gets the total amount discounted
651
     *
652
     * @param boolean $format
653
     *
654
     * @return string
655
     */
656
    public function totalDiscount($format = true)
657
    {
658
        $total = 0;
659
660
        foreach ($this->cart->coupons as $coupon) {
661
            if ($coupon->appliedToCart) {
662
                $total += $coupon->discount();
663
            }
664
        }
665
666
        return $this->formatMoney($total, null, null, $format);
667
    }
668
}
669