Completed
Push — master ( fd7c36...49f0d1 )
by Luke
02:28
created

LaraCart::getFromModel()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.2
cc 4
eloc 8
nc 4
nop 3
1
<?php
2
3
namespace LukePOLO\LaraCart;
4
5
use Illuminate\Auth\AuthManager;
6
use Illuminate\Contracts\Events\Dispatcher;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Session\SessionManager;
9
use LukePOLO\LaraCart\Contracts\CouponContract;
10
use LukePOLO\LaraCart\Contracts\LaraCartContract;
11
use LukePOLO\LaraCart\Exceptions\ModelNotFound;
12
13
/**
14
 * Class LaraCart
15
 *
16
 * @package LukePOLO\LaraCart
17
 */
18
class LaraCart implements LaraCartContract
19
{
20
    const SERVICE = 'laracart';
21
    const HASH = 'generateCartHash';
22
    const RANHASH = 'generateRandomCartItemHash';
23
24
    protected $events;
25
    protected $session;
26
    protected $authManager;
27
28
    public $cart;
29
    public $prefix;
30
    public $itemModel;
31
    public $itemModelRelations;
32
33
    /**
34
     * LaraCart constructor.
35
     *
36
     * @param SessionManager $session
37
     * @param Dispatcher $events
38
     * @param AuthManager $authManager
39
     */
40
    public function __construct(SessionManager $session, Dispatcher $events, AuthManager $authManager)
41
    {
42
        $this->session = $session;
43
        $this->events = $events;
44
        $this->authManager = $authManager;
45
        $this->prefix = config('laracart.cache_prefix', 'laracart');
46
        $this->itemModel = config('laracart.item_model', null);
47
        $this->itemModelRelations = config('laracart.item_model_relations', []);
48
49
        $this->setInstance($this->session->get($this->prefix . '.instance', 'default'));
50
    }
51
52
    /**
53
     * Gets all current instances inside the session
54
     *
55
     * @return mixed
56
     */
57
    public function getInstances()
58
    {
59
        return $this->session->get($this->prefix . '.instances', []);
60
    }
61
62
    /**
63
     * Sets and Gets the instance of the cart in the session we should be using
64
     *
65
     * @param string $instance
66
     *
67
     * @return LaraCart
68
     */
69
    public function setInstance($instance = 'default')
70
    {
71
        $this->get($instance);
72
73
        $this->session->set($this->prefix . '.instance', $instance);
74
75
        if (!in_array($instance, $this->getInstances())) {
76
            $this->session->push($this->prefix . '.instances', $instance);
77
        }
78
        $this->events->fire('laracart.new');
79
80
        return $this;
81
    }
82
83
    /**
84
     * Gets the instance in the session
85
     *
86
     * @param string $instance
87
     *
88
     * @return $this cart instance
89
     */
90
    public function get($instance = 'default')
91
    {
92
        if (config('laracart.cross_devices', false) && $this->authManager->check()) {
93
            if (!empty($cartSessionID = $this->authManager->user()->cart_session_id)) {
94
                $this->session->setId($cartSessionID);
95
                $this->session->start();
96
            }
97
        }
98
99
        if (empty($this->cart = $this->session->get($this->prefix . '.' . $instance))) {
100
            $this->cart = new Cart($instance);
101
        }
102
103
        return $this;
104
    }
105
106
    /**
107
     * Gets an an attribute from the cart
108
     *
109
     * @param $attribute
110
     * @param $defaultValue
111
     *
112
     * @return mixed
113
     */
114
    public function getAttribute($attribute, $defaultValue = null)
115
    {
116
        return array_get($this->cart->attributes, $attribute, $defaultValue);
117
    }
118
119
    /**
120
     * Gets all the carts attributes
121
     *
122
     * @return mixed
123
     */
124
    public function getAttributes()
125
    {
126
        return $this->cart->attributes;
127
    }
128
129
    /**
130
     * Adds an Attribute to the cart
131
     *
132
     * @param $attribute
133
     * @param $value
134
     */
135
    public function setAttribute($attribute, $value)
136
    {
137
        array_set($this->cart->attributes, $attribute, $value);
138
139
        $this->update();
140
    }
141
142
    /**
143
     * Updates cart session
144
     */
145
    public function update()
146
    {
147
        $this->session->set($this->prefix . '.' . $this->cart->instance, $this->cart);
148
149
        if (config('laracart.cross_devices', false) && $this->authManager->check()) {
150
            $this->authManager->user()->cart_session_id = $this->session->getId();
151
            $this->authManager->user()->save();
152
        }
153
154
        $this->session->save();
155
156
        $this->events->fire('laracart.update', $this->cart);
157
    }
158
159
    /**
160
     * Removes an attribute from the cart
161
     *
162
     * @param $attribute
163
     */
164
    public function removeAttribute($attribute)
165
    {
166
        array_forget($this->cart->attributes, $attribute);
167
168
        $this->update();
169
    }
170
171
    /**
172
     * Creates a CartItem and then adds it to cart
173
     *
174
     * @param string|int $itemID
175
     * @param null $name
176
     * @param int $qty
177
     * @param string $price
178
     * @param array $options
179
     * @param bool|true $taxable
180
     *
181
     * @return CartItem
182
     */
183
    public function addLine($itemID, $name = null, $qty = 1, $price = '0.00', $options = [], $taxable = true)
184
    {
185
        return $this->add($itemID, $name, $qty, $price, $options, $taxable, true);
186
    }
187
188
    /**
189
     * Creates a CartItem and then adds it to cart
190
     *
191
     * @param $itemID
192
     * @param null $name
193
     * @param int $qty
194
     * @param string $price
195
     * @param array $options
196
     * @param bool|false $taxable
197
     * @param bool|false $lineItem
198
     *
199
     * @return CartItem
200
     *
201
     * @throws ModelNotFound
202
     */
203
    public function add(
204
        $itemID,
205
        $name = null,
206
        $qty = 1,
207
        $price = '0.00',
208
        $options = [],
209
        $taxable = true,
210
        $lineItem = false
211
    ) {
212
213
        if (!empty(config('laracart.item_model'))) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
214
215
            $itemModel = $itemID;
216
217
            if (!$this->isItemModel($itemModel)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->isItemModel($itemModel) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
218
                $itemModel = new $this->itemModel;
219
                $itemModel->with($this->itemModelRelations)->find($itemID);
220
            }
221
222
            if (empty($itemModel)) {
223
                throw new ModelNotFound('Could not find the item '.$itemID);
224
            }
225
226
            $bindings = config('laracart.item_model_bindings');
227
228
            $itemID = $itemModel[$bindings[\LukePOLO\LaraCart\CartItem::ITEM_ID]];
229
            $name = $itemModel[$bindings[\LukePOLO\LaraCart\CartItem::ITEM_NAME]];
230
231
            if (empty($qty = $name) || !is_int($name)) {
232
                $qty = 1;
233
            }
234
235
            $price = $itemModel[$bindings[\LukePOLO\LaraCart\CartItem::ITEM_PRICE]];
236
            $options = $this->getItemModelOptions($itemModel, $bindings[\LukePOLO\LaraCart\CartItem::ITEM_OPTIONS]);
237
            $taxable = $itemModel[$bindings[\LukePOLO\LaraCart\CartItem::ITEM_TAXABLE]];
238
        }
239
240
        $item = $this->addItem(new CartItem(
241
            $itemID,
242
            $name,
243
            $qty,
244
            $price,
245
            $options,
246
            $taxable,
247
            $lineItem
248
        ));
249
250
        $this->update();
251
252
        return $this->getItem($item->getHash());
253
    }
254
255
    /**
256
     * Adds the cartItem into the cart session
257
     *
258
     * @param CartItem $cartItem
259
     *
260
     * @return CartItem
261
     */
262
    public function addItem(CartItem $cartItem)
263
    {
264
        $itemHash = $cartItem->generateHash();
265
266
        if ($this->getItem($itemHash)) {
267
            $this->getItem($itemHash)->qty += $cartItem->qty;
268
        } else {
269
            $this->cart->items[] = $cartItem;
270
        }
271
272
        $this->events->fire('laracart.addItem', $cartItem);
273
274
        return $cartItem;
275
    }
276
277
    /**
278
     * Increment the quantity of a cartItem based on the itemHash
279
     *
280
     * @param $itemHash
281
     *
282
     * @return CartItem | null
283
     */
284
    public function increment($itemHash)
285
    {
286
        $item = $this->getItem($itemHash);
287
        $item->qty++;
288
        $this->update();
289
290
        return $item;
291
    }
292
293
    /**
294
     * Decrement the quantity of a cartItem based on the itemHash
295
     *
296
     * @param $itemHash
297
     *
298
     * @return CartItem | null
299
     */
300
    public function decrement($itemHash)
301
    {
302
        $item = $this->getItem($itemHash);
303
        if ($item->qty > 1) {
304
            $item->qty--;
305
            $this->update();
306
307
            return $item;
308
        }
309
        $this->removeItem($itemHash);
310
        $this->update();
311
312
        return null;
313
    }
314
315
    /**
316
     * Find items in the cart matching a data set
317
     *
318
     *
319
     * param $data
320
     * @return array
321
     */
322
    public function find($data)
323
    {
324
        $matches = [];
325
326
        foreach ($this->getItems() as $item) {
327
            if ($item->find($data)) {
328
                $matches[] = $item;
329
            }
330
        }
331
332
        return $matches;
333
    }
334
335
    /**
336
     * Finds a cartItem based on the itemHash
337
     *
338
     * @param $itemHash
339
     *
340
     * @return CartItem | null
341
     */
342
    public function getItem($itemHash)
343
    {
344
        return array_get($this->getItems(), $itemHash);
345
    }
346
347
    /**
348
     * Gets all the items within the cart
349
     *
350
     * @return array
351
     */
352
    public function getItems()
353
    {
354
        $items = [];
355
        if (isset($this->cart->items) === true) {
356
            foreach ($this->cart->items as $item) {
357
                $items[$item->getHash()] = $item;
358
            }
359
        }
360
361
        return $items;
362
    }
363
364
    /**
365
     * Updates an items attributes
366
     *
367
     * @param $itemHash
368
     * @param $key
369
     * @param $value
370
     *
371
     * @return CartItem
372
     *
373
     * @throws Exceptions\InvalidPrice
374
     * @throws Exceptions\InvalidQuantity
375
     */
376
    public function updateItem($itemHash, $key, $value)
377
    {
378
        if (empty($item = $this->getItem($itemHash)) === false) {
379
            $item->$key = $value;
380
        }
381
382
        $item->generateHash();
383
384
        $this->update();
385
386
        return $item;
387
    }
388
389
    /**
390
     * Removes a CartItem based on the itemHash
391
     *
392
     * @param $itemHash
393
     */
394
    public function removeItem($itemHash)
395
    {
396
        foreach ($this->cart->items as $itemKey => $item) {
397
            if ($item->getHash() == $itemHash) {
398
                unset($this->cart->items[$itemKey]);
399
                break;
400
            }
401
        }
402
403
        $this->events->fire('laracart.removeItem', $item);
0 ignored issues
show
Bug introduced by
The variable $item seems to be defined by a foreach iteration on line 396. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
404
405
        $this->update();
406
    }
407
408
    /**
409
     * Empties the carts items
410
     */
411
    public function emptyCart()
412
    {
413
        unset($this->cart->items);
414
415
        $this->update();
416
417
        $this->events->fire('laracart.empty', $this->cart->instance);
418
    }
419
420
    /**
421
     * Completely destroys cart and anything associated with it
422
     */
423
    public function destroyCart()
424
    {
425
        $instance = $this->cart->instance;
426
427
        $this->session->forget($this->prefix . '.' . $instance);
428
429
        $this->setInstance('default');
430
431
        $this->events->fire('laracart.destroy', $instance);
432
433
        $this->update();
434
    }
435
436
    /**
437
     * Gets the coupons for the current cart
438
     *
439
     * @return array
440
     */
441
    public function getCoupons()
442
    {
443
        return $this->cart->coupons;
444
    }
445
446
    /**
447
     * Finds a specific coupon in the cart
448
     *
449
     * @param $code
450
     * @return mixed
451
     */
452
    public function findCoupon($code)
453
    {
454
        return array_get($this->cart->coupons, $code);
455
    }
456
457
    /**
458
     * Applies a coupon to the cart
459
     *
460
     * @param CouponContract $coupon
461
     */
462
    public function addCoupon(CouponContract $coupon)
463
    {
464
        if (!$this->cart->multipleCoupons) {
465
            $this->cart->coupons = [];
466
        }
467
468
        $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...
469
470
        $this->update();
471
    }
472
473
    /**
474
     * Removes a coupon in the cart
475
     *
476
     * @param $code
477
     */
478
    public function removeCoupon($code)
479
    {
480
        foreach ($this->getItems() as $item) {
481
            if (isset($item->code) && $item->code == $code) {
482
                $item->code = null;
483
                $item->discount = null;
484
                $item->couponInfo = [];
485
            }
486
        }
487
488
        array_forget($this->cart->coupons, $code);
489
490
        $this->update();
491
    }
492
493
    /**
494
     * Gets a specific fee from the fees array
495
     *
496
     * @param $name
497
     *
498
     * @return mixed
499
     */
500
    public function getFee($name)
501
    {
502
        return array_get($this->cart->fees, $name, new CartFee(null, false));
503
    }
504
505
    /**
506
     * Allows to charge for additional fees that may or may not be taxable
507
     * ex - service fee , delivery fee, tips
508
     *
509
     * @param $name
510
     * @param $amount
511
     * @param bool|false $taxable
512
     * @param array $options
513
     */
514
    public function addFee($name, $amount, $taxable = false, array $options = [])
515
    {
516
        array_set($this->cart->fees, $name, new CartFee($amount, $taxable, $options));
517
518
        $this->update();
519
    }
520
521
    /**
522
     * Removes a fee from the fee array
523
     *
524
     * @param $name
525
     */
526
    public function removeFee($name)
527
    {
528
        array_forget($this->cart->fees, $name);
529
530
        $this->update();
531
    }
532
533
    /**
534
     * Removes all the fees set in the cart
535
     *
536
     */
537
    public function removeFees()
538
    {
539
        $this->cart->fees = [];
540
541
        $this->update();
542
    }
543
544
    /**
545
     * Gets the total tax for the cart
546
     *
547
     * @param bool|true $format
548
     *
549
     * @return string
550
     */
551
    public function taxTotal($format = true)
552
    {
553
        $totalTax = 0;
554
        $discounted = 0;
555
        $totalDiscount = $this->totalDiscount(false);
556
557
        if ($this->count() != 0) {
558
            foreach ($this->getItems() as $item) {
559
                if ($discounted >= $totalDiscount) {
560
                    $totalTax += $item->tax();
561
                } else {
562
                    $itemPrice = $item->subTotal(false);
563
564
                    if (($discounted + $itemPrice) > $totalDiscount) {
565
                        $totalTax += $item->tax($totalDiscount - $discounted);
566
                    }
567
568
                    $discounted += $itemPrice;
569
                }
570
            }
571
        }
572
573
        foreach ($this->getFees() as $fee) {
574
            if ($fee->taxable) {
575
                $totalTax += $fee->amount * $fee->tax;
576
            }
577
        }
578
579
        return $this->formatMoney($totalTax, null, null, $format);
580
    }
581
582
    /**
583
     * Gets the total of the cart with or without tax
584
     *
585
     * @param boolean $format
586
     * @param boolean $withDiscount
587
     *
588
     * @return string
589
     */
590
    public function total($format = true, $withDiscount = true, $withTax = true)
591
    {
592
        $total = $this->subTotal(false) + $this->feeTotals(false);
593
594
        if ($withDiscount) {
595
            $total -= $this->totalDiscount(false);
596
        }
597
598
        if ($withTax) {
599
            $total += $this->taxTotal(false);
600
        }
601
602
        return $this->formatMoney($total, null, null, $format);
603
    }
604
605
    /**
606
     * Gets the subtotal of the cart with or without tax
607
     *
608
     * @param boolean $format
609
     * @param boolean $withDiscount
610
     *
611
     * @return string
612
     */
613
    public function subTotal($format = true, $withDiscount = true)
614
    {
615
        $total = 0;
616
617
        if ($this->count() != 0) {
618
            foreach ($this->getItems() as $item) {
619
                $total += $item->subTotal(false, $withDiscount);
620
            }
621
        }
622
623
        return $this->formatMoney($total, null, null, $format);
624
    }
625
626
    /**
627
     * Get the count based on qty, or number of unique items
628
     *
629
     * @param bool $withItemQty
630
     *
631
     * @return int
632
     */
633
    public function count($withItemQty = true)
634
    {
635
        $count = 0;
636
637
        foreach ($this->getItems() as $item) {
638
            if ($withItemQty) {
639
                $count += $item->qty;
640
            } else {
641
                $count++;
642
            }
643
        }
644
645
        return $count;
646
    }
647
648
    /**
649
     *
650
     * Formats the number into a money format based on the locale and international formats
651
     *
652
     * @param $number
653
     * @param $locale
654
     * @param $internationalFormat
655
     * @param $format
656
     *
657
     * @return string
658
     */
659
    public static function formatMoney($number, $locale = null, $internationalFormat = null, $format = true)
660
    {
661
        $number = number_format($number, 2, '.', '');
662
663
        if ($format) {
664
            setlocale(LC_MONETARY, null);
665
            setlocale(LC_MONETARY, empty($locale) ? config('laracart.locale', 'en_US.UTF-8') : $locale);
666
667
            if (empty($internationalFormat) === true) {
668
                $internationalFormat = config('laracart.international_format', false);
669
            }
670
671
            $number = money_format($internationalFormat ? '%i' : '%n', $number);
672
        }
673
674
        return $number;
675
    }
676
677
    /**
678
     * Gets all the fee totals
679
     *
680
     * @param boolean $format
681
     *
682
     * @return string
683
     */
684
    public function feeTotals($format = true, $withTax = false)
685
    {
686
        $feeTotal = 0;
687
688
        foreach ($this->getFees() as $fee) {
689
            $feeTotal += $fee->amount;
690
691
            if ($withTax && $fee->taxable && $fee->tax > 0) {
692
                $feeTotal += $fee->amount * $fee->tax;
693
            }
694
        }
695
696
        return $this->formatMoney($feeTotal, null, null, $format);
697
    }
698
699
    /**
700
     * Gets all the fees on the cart object
701
     *
702
     * @return mixed
703
     */
704
    public function getFees()
705
    {
706
        return $this->cart->fees;
707
    }
708
709
    /**
710
     * Gets the total amount discounted
711
     *
712
     * @param boolean $format
713
     *
714
     * @return string
715
     */
716
    public function totalDiscount($format = true)
717
    {
718
        $total = 0;
719
720
        foreach ($this->cart->coupons as $coupon) {
721
            if ($coupon->appliedToCart) {
722
                $total += $coupon->discount();
723
            }
724
        }
725
726
        return $this->formatMoney($total, null, null, $format);
727
    }
728
729
    /**
730
     * Checks to see if its an item model
731
     *
732
     * @param $itemModel
733
     *
734
     * @return bool
735
     */
736
    private function isItemModel($itemModel)
737
    {
738
        if (is_object($itemModel) && get_class($itemModel) == config('laracart.item_model')) {
739
            return true;
740
        }
741
    }
742
743
    /**
744
     * Gets the item models options based the config
745
     *
746
     * @param Model $itemModel
747
     * @param array $options
748
     *
749
     * @return array
750
     */
751
    private function getItemModelOptions(Model $itemModel, $options = [])
752
    {
753
        $itemOptions = [];
754
        foreach ($options as $option) {
755
            $itemOptions[$option] = $this->getFromModel($itemModel, $option);
756
        }
757
758
        return array_filter($itemOptions, function ($value) {
759
            if ($value !== false && empty($value)) {
760
                return false;
761
            }
762
763
            return true;
764
        });
765
    }
766
767
    /**
768
     * Gets a option from the model
769
     *
770
     * @param Model $itemModel
771
     * @param $attr
772
     * @param null $defaultValue
773
     * 
774
     * @return Model|null
775
     */
776
    private function getFromModel(Model $itemModel, $attr, $defaultValue = null)
777
    {
778
        $variable = $itemModel;
779
780
        if (!empty($attr)) {
781
            foreach (explode('.', $attr) as $attr) {
782
                $variable = array_get($variable, $attr);
783
            }
784
        }
785
786
        if (empty($variable)) {
787
            $variable = $defaultValue;
788
        }
789
        return $variable;
790
    }
791
792
}
793