Completed
Pull Request — master (#33)
by Luke
07:50 queued 02:45
created

LaraCart   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 536
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 39
Bugs 3 Features 2
Metric Value
wmc 55
c 39
b 3
f 2
lcom 1
cbo 3
dl 0
loc 536
rs 6.8

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setInstance() 0 10 1
A get() 0 8 2
A update() 0 6 1
B formatMoney() 0 14 5
A getAttribute() 0 4 1
A getAttributes() 0 4 1
A setAttribute() 0 6 1
A removeAttribute() 0 6 1
A getItem() 0 4 1
A getItems() 0 11 3
A add() 0 21 1
A addLine() 0 4 1
A addItem() 0 15 2
A updateItem() 0 18 2
A removeItem() 0 11 3
A count() 0 13 3
A emptyCart() 0 8 1
A destroyCart() 0 8 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 getFees() 0 4 1
A addFee() 0 6 1
A removeFee() 0 6 1
A feeTotals() 0 13 3
A totalDiscount() 0 10 2
A taxTotal() 0 7 1
A subTotal() 0 11 3
A total() 0 10 2

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 LukePOLO\LaraCart\Contracts\CouponContract;
6
use LukePOLO\LaraCart\Contracts\LaraCartContract;
7
8
/**
9
 * Class LaraCart
10
 *
11
 * @package LukePOLO\LaraCart
12
 */
13
class LaraCart implements LaraCartContract
14
{
15
    const SERVICE = 'laracart';
16
    const QTY = 'qty';
17
    const PRICE = 'price';
18
    const HASH = 'generateCartHash';
19
    const RANHASH = 'generateRandomCartItemHash';
20
21
    public $cart;
22
23
    /**
24
     * LaraCart constructor.
25
     */
26
    public function __construct()
27
    {
28
        $this->setInstance(\Session::get('laracart.instance', 'default'));
29
    }
30
31
    /**
32
     * Sets and Gets the instance of the cart in the session we should be using
33
     *
34
     * @param string $instance
35
     *
36
     * @return LaraCart
37
     */
38
    public function setInstance($instance = 'default')
39
    {
40
        $this->get($instance);
41
42
        \Session::set('laracart.instance', $instance);
43
44
        \Event::fire('laracart.new');
45
46
        return $this;
47
    }
48
49
    /**
50
     * Gets the instance in the session
51
     *
52
     * @param string $instance
53
     *
54
     * @return $this cart instance
55
     */
56
    public function get($instance = 'default')
57
    {
58
        if (empty($this->cart = \Session::get(config('laracart.cache_prefix', 'laracart').'.'.$instance))) {
59
            $this->cart = new Cart($instance);
60
        }
61
62
        return $this;
63
    }
64
65
    /**
66
     * Updates cart session
67
     */
68
    public function update()
69
    {
70
        \Session::set(config('laracart.cache_prefix', 'laracart').'.'.$this->cart->instance, $this->cart);
71
72
        \Event::fire('laracart.update', $this->cart);
73
    }
74
75
    /**
76
     *
77
     * Formats the number into a money format based on the locale and international formats
78
     *
79
     * @param $number
80
     * @param $locale
81
     * @param $internationalFormat
82
     * @param $format
83
     *
84
     * @return string
85
     */
86
    public function formatMoney($number, $locale = null, $internationalFormat = null, $format = true)
87
    {
88
        if ($format) {
89
            setlocale(LC_MONETARY, empty($locale) ? config('laracart.locale', 'en_US.UTF-8') : $locale);
90
91
            if (empty($internationalFormat) === true) {
92
                $internationalFormat = config('laracart.international_format', false);
93
            }
94
95
            return money_format($internationalFormat ? '%i' : '%n', $number);
96
        }
97
98
        return number_format($number, 2, '.', '');
99
    }
100
101
    /**
102
     * Gets an an attribute from the cart
103
     *
104
     * @param $attribute
105
     * @param $defaultValue
106
     *
107
     * @return mixed
108
     */
109
    public function getAttribute($attribute, $defaultValue = null)
110
    {
111
        return array_get($this->cart->attributes, $attribute, $defaultValue);
112
    }
113
114
    /**
115
     * Gets all the carts attributes
116
     *
117
     * @return mixed
118
     */
119
    public function getAttributes()
120
    {
121
        return $this->cart->attributes;
122
    }
123
124
    /**
125
     * Adds an Attribute to the cart
126
     *
127
     * @param $attribute
128
     * @param $value
129
     */
130
    public function setAttribute($attribute, $value)
131
    {
132
        array_set($this->cart->attributes, $attribute, $value);
133
134
        $this->update();
135
    }
136
137
    /**
138
     * Removes an attribute from the cart
139
     *
140
     * @param $attribute
141
     */
142
    public function removeAttribute($attribute)
143
    {
144
        array_forget($this->cart->attributes, $attribute);
145
146
        $this->update();
147
    }
148
149
    /**
150
     * Finds a cartItem based on the itemHash
151
     *
152
     * @param $itemHash
153
     *
154
     * @return CartItem | null
155
     */
156
    public function getItem($itemHash)
157
    {
158
        return array_get($this->getItems(), $itemHash);
159
    }
160
161
    /**
162
     * Gets all the items within the cart
163
     *
164
     * @return array
165
     */
166
    public function getItems()
167
    {
168
        $items = [];
169
        if (isset($this->cart->items) === true) {
170
            foreach ($this->cart->items as $item) {
171
                $items[$item->getHash()] = $item;
172
            }
173
        }
174
175
        return $items;
176
    }
177
178
    /**
179
     * Creates a CartItem and then adds it to cart
180
     *
181
     * @param $itemID
182
     * @param null $name
183
     * @param int $qty
184
     * @param string $price
185
     * @param array $options
186
     * @param bool|false $taxable
187
     * @param bool|false $lineItem
188
     *
189
     * @return CartItem
190
     */
191
    public function add(
192
        $itemID,
193
        $name = null,
194
        $qty = 1,
195
        $price = '0.00',
196
        $options = [],
197
        $taxable = true,
198
        $lineItem = false
199
    ) {
200
        return $this->addItem(
201
            new CartItem(
202
                $itemID,
203
                $name,
204
                $qty,
205
                $price,
206
                $options,
207
                $taxable,
208
                $lineItem
209
            )
210
        );
211
    }
212
213
    /**
214
     * Creates a CartItem and then adds it to cart
215
     *
216
     * @param string|int $itemID
217
     * @param null $name
218
     * @param int $qty
219
     * @param string $price
220
     * @param array $options
221
     *
222
     * @return CartItem
223
     */
224
    public function addLine($itemID, $name = null, $qty = 1, $price = '0.00', $options = [], $taxable = true)
225
    {
226
        return $this->add($itemID, $name, $qty, $price, $options, $taxable, true);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->add($itemI...tions, $taxable, true); (LukePOLO\LaraCart\CartItem) is incompatible with the return type declared by the interface LukePOLO\LaraCart\Contra...raCartContract::addLine of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
227
    }
228
229
    /**
230
     * Adds the cartItem into the cart session
231
     *
232
     * @param CartItem $cartItem
233
     *
234
     * @return CartItem
235
     */
236
    public function addItem(CartItem $cartItem)
237
    {
238
        $itemHash = $cartItem->generateHash();
239
240
        if ($this->getItem($itemHash)) {
241
            $this->getItem($itemHash)->qty += $cartItem->qty;
242
        } else {
243
            $this->cart->items[] = $cartItem;
244
            \Event::fire('laracart.addItem', $cartItem);
245
        }
246
247
        $this->update();
248
249
        return $cartItem;
250
    }
251
252
    /**
253
     * Updates an items attributes
254
     *
255
     * @param $itemHash
256
     * @param $key
257
     * @param $value
258
     *
259
     * @return CartItem
260
     *
261
     * @throws Exceptions\InvalidPrice
262
     * @throws Exceptions\InvalidQuantity
263
     */
264
    public function updateItem($itemHash, $key, $value)
265
    {
266
        if (empty($item = $this->getItem($itemHash)) === false) {
267
            $item->update($key, $value);
268
        }
269
270
        $newHash = $item->generateHash();
271
272
        \Event::fire(
273
            'laracart.updateItem',
274
            [
275
                'item' => $item,
276
                'newHash' => $newHash,
277
            ]
278
        );
279
280
        return $item;
281
    }
282
283
    /**
284
     * Removes a CartItem based on the itemHash
285
     *
286
     * @param $itemHash
287
     */
288
    public function removeItem($itemHash)
289
    {
290
        foreach ($this->cart->items as $itemKey => $item) {
291
            if ($item->getHash() == $itemHash) {
292
                unset($this->cart->items[$itemKey]);
293
                break;
294
            }
295
        }
296
297
        \Event::fire('laracart.removeItem', $itemHash);
298
    }
299
300
    /**
301
     * Get the count based on qty, or number of unique items
302
     *
303
     * @param bool $withItemQty
304
     *
305
     * @return int
306
     */
307
    public function count($withItemQty = true)
308
    {
309
        $count = 0;
310
        foreach ($this->getItems() as $item) {
311
            if ($withItemQty) {
312
                $count += $item->qty;
313
            } else {
314
                $count++;
315
            }
316
        }
317
318
        return $count;
319
    }
320
321
    /**
322
     * Empties the carts items
323
     */
324
    public function emptyCart()
325
    {
326
        unset($this->cart->items);
327
328
        $this->update();
329
330
        \Event::fire('laracart.empty', $this->cart->instance);
331
    }
332
333
    /**
334
     * Completely destroys cart and anything associated with it
335
     */
336
    public function destroyCart()
337
    {
338
        $this->cart = new Cart($this->cart->instance);
339
340
        $this->update();
341
342
        \Event::fire('laracart.destroy', $this->cart->instance);
343
    }
344
345
    /**
346
     * Gets the coupons for the current cart
347
     *
348
     * @return array
349
     */
350
    public function getCoupons()
351
    {
352
        return $this->cart->coupons;
353
    }
354
355
    /**
356
     * // TODO - badly named
357
     * Finds a specific coupon in the cart
358
     *
359
     * @param $code
360
     * @return mixed
361
     */
362
    public function findCoupon($code)
363
    {
364
        return array_get($this->cart->coupons, $code);
365
    }
366
367
    /**
368
     * // todo - badly named
369
     * Applies a coupon to the cart
370
     *
371
     * @param CouponContract $coupon
372
     */
373
    public function addCoupon(CouponContract $coupon)
374
    {
375
        if(!$this->cart->multipleCoupons) {
376
            $this->cart->coupons = [];
377
        }
378
379
        $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...
380
381
        $this->update();
382
    }
383
384
    /**
385
     * Removes a coupon in the cart
386
     *
387
     * @param $code
388
     */
389
    public function removeCoupon($code)
390
    {
391
        foreach($this->getItems() as $item) {
392
            if(isset($item->code) && $item->code == $code) {
393
                $item->code = null;
394
                $item->discount = null;
395
                $item->couponInfo = null;
396
            }
397
        }
398
399
        array_forget($this->cart->coupons, $code);
400
401
        $this->update();
402
    }
403
404
    /**
405
     * Gets a speific fee from the fees array
406
     *
407
     * @param $name
408
     *
409
     * @return mixed
410
     */
411
    public function getFee($name)
412
    {
413
        return array_get($this->cart->fees, $name, new CartFee(null, false));
414
    }
415
416
    /**
417
     * Getes all the fees on the cart object
418
     *
419
     * @return mixed
420
     */
421
    public function getFees()
422
    {
423
        return $this->cart->fees;
424
    }
425
426
    /**
427
     * Allows to charge for additional fees that may or may not be taxable
428
     * ex - service fee , delivery fee, tips
429
     *
430
     * @param $name
431
     * @param $amount
432
     * @param bool|false $taxable
433
     * @param array $options
434
     */
435
    public function addFee($name, $amount, $taxable = false, Array $options = [])
436
    {
437
        array_set($this->cart->fees, $name, new CartFee($amount, $taxable, $options));
438
439
        $this->update();
440
    }
441
442
    /**
443
     * Reemoves a fee from the fee array
444
     *
445
     * @param $name
446
     */
447
    public function removeFee($name)
448
    {
449
        array_forget($this->cart->fees, $name);
450
451
        $this->update();
452
    }
453
454
    /**
455
     * Gets all the fee totals
456
     *
457
     * @param boolean $format
458
     *
459
     * @return string
460
     */
461
    public function feeTotals($format = true)
462
    {
463
        $feeTotal = 0;
464
465
        foreach ($this->getFees() as $fee) {
466
            $feeTotal += $fee->amount;
467
            if ($fee->taxable) {
468
                $feeTotal += $fee->amount * $this->cart->tax;
469
            }
470
        }
471
472
        return $this->formatMoney($feeTotal, null, null, $format);
473
    }
474
475
    /**
476
     * Gets the total amount discounted
477
     *
478
     * @param bool|true $format
479
     *
480
     * @return int|string
481
     */
482
    public function totalDiscount($format = true)
483
    {
484
        $total = 0;
485
486
        foreach ($this->cart->coupons as $coupon) {
487
            $total += $coupon->discount();
488
        }
489
490
        return $this->formatMoney($total, null, null, $format);
491
    }
492
493
494
    /**
495
     * Gets the total tax for the cart
496
     *
497
     * @param bool|true $format
498
     *
499
     * @return string
500
     */
501
    public function taxTotal($format = true)
502
    {
503
        $totalTax = $this->total(false, false) - $this->subTotal(false, false, false) - $this->feeTotals(false);
504
505
506
        return $this->formatMoney($totalTax, null, null, $format);
507
    }
508
509
    /**
510
     * Gets the subtotal of the cart with or without tax
511
     *
512
     * @param bool|false $tax
513
     * @param boolean $format
514
     * @param boolean $withDiscount
515
     *
516
     * @return string
517
     */
518
    public function subTotal($tax = false, $format = true, $withDiscount = true)
519
    {
520
        $total = 0;
521
        if ($this->count() != 0) {
522
            foreach ($this->getItems() as $item) {
523
                $total += $item->subTotal($tax, false, $withDiscount);
524
            }
525
        }
526
527
        return $this->formatMoney($total, null, null, $format);
528
    }
529
530
    /**
531
     * Gets the total of the cart with or without tax
532
     *
533
     * @param boolean $format
534
     * @param boolean $withDiscount
535
     *
536
     * @return string
537
     */
538
    public function total($format = true, $withDiscount = true)
539
    {
540
        $total = $this->subTotal(true, false, false) + $this->feeTotals(false);
541
542
        if ($withDiscount) {
543
            $total -= $this->totalDiscount(false);
544
        }
545
546
        return $this->formatMoney($total, null, null, $format);
547
    }
548
}
549