Completed
Pull Request — master (#72)
by
unknown
06:35
created

Cart::setDecPoint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php namespace Darryldecode\Cart;
2
3
use Darryldecode\Cart\Exceptions\InvalidConditionException;
4
use Darryldecode\Cart\Exceptions\InvalidItemException;
5
use Darryldecode\Cart\Helpers\Helpers;
6
use Darryldecode\Cart\Validators\CartItemValidator;
7
8
/**
9
 * Class Cart
10
 * @package Darryldecode\Cart
11
 */
12
class Cart {
13
14
    /**
15
     * the item storage
16
     *
17
     * @var
18
     */
19
    protected $session;
20
21
    /**
22
     * the event dispatcher
23
     *
24
     * @var
25
     */
26
    protected $events;
27
28
    /**
29
     * the cart session key
30
     *
31
     * @var
32
     */
33
    protected $instanceName;
34
35
    /**
36
     * the session key use to persist cart items
37
     *
38
     * @var
39
     */
40
    protected $sessionKeyCartItems;
41
42
    /**
43
     * the session key use to persist cart conditions
44
     *
45
     * @var
46
     */
47
    protected $sessionKeyCartConditions;
48
49
    /**
50
     * Sets the number of decimal points.
51
     *
52
     * @var
53
     */
54
    protected $decimals;
55
56
    /**
57
     * Definces the decimal point delimiter type
58
     *
59
     * @var
60
     */
61
    protected $dec_point;
62
63
    /**
64
     * Defines the thousands point delimiter type
65
     *
66
     * @var
67
     */
68
    protected $thousands_sep;
69
70
    /**
71
     * Configuration to pass to ItemCollection
72
     *
73
     * @var
74
     */
75
    protected $config;
76
77
    /**
78
     * our object constructor
79
     *
80
     * @param $session
81
     * @param $events
82
     * @param $instanceName
83
     * @param $session_key
84
     * @param $config
85
     */
86
    public function __construct($session, $events, $instanceName, $session_key, $config)
87
    {
88
        $this->events = $events;
89
        $this->session = $session;
90
        $this->instanceName = $instanceName;
91
        $this->sessionKeyCartItems = $session_key.'_cart_items';
92
        $this->sessionKeyCartConditions = $session_key.'_cart_conditions';
93
        $this->events->fire($this->getInstanceName().'.created', array($this));
94
        $this->config = $config;
95
        $this->decimals = $config['decimals'];
96
        $this->dec_point = $config['dec_point'];
97
        $this->thousands_sep = $config['thousands_sep'];
98
    }
99
100
    /**
101
     * get instance name of the cart
102
     *
103
     * @return string
104
     */
105
    public function getInstanceName()
106
    {
107
        return $this->instanceName;
108
    }
109
110
    /**
111
     * get an item on a cart by item ID
112
     *
113
     * @param $itemId
114
     * @return mixed
115
     */
116
    public function get($itemId)
117
    {
118
        return $this->getContent()->get($itemId);
119
    }
120
121
    /**
122
     * check if an item exists by item ID
123
     *
124
     * @param $itemId
125
     * @return bool
126
     */
127
    public function has($itemId)
128
    {
129
        return $this->getContent()->has($itemId);
130
    }
131
132
    /**
133
     * add item to the cart, it can be an array or multi dimensional array
134
     *
135
     * @param string|array $id
136
     * @param string $name
137
     * @param float $price
138
     * @param int $quantity
139
     * @param array $attributes
140
     * @param CartCondition|array $conditions
141
     * @return $this
142
     * @throws InvalidItemException
143
     */
144
    public function add($id, $name = null, $price = null, $quantity = null, $attributes = array(), $conditions = array())
145
    {
146
        // if the first argument is an array,
147
        // we will need to call add again
148
        if( is_array($id) )
149
        {
150
            // the first argument is an array, now we will need to check if it is a multi dimensional
151
            // array, if so, we will iterate through each item and call add again
152
            if( Helpers::isMultiArray($id) )
153
            {
154
                foreach($id as $item)
155
                {
156
                    $this->add(
157
                        $item['id'],
158
                        $item['name'],
159
                        $item['price'],
160
                        $item['quantity'],
161
                        Helpers::issetAndHasValueOrAssignDefault($item['attributes'], array()),
162
                        Helpers::issetAndHasValueOrAssignDefault($item['conditions'], array())
163
                    );
164
                }
165
            }
166
            else
167
            {
168
                $this->add(
169
                    $id['id'],
170
                    $id['name'],
171
                    $id['price'],
172
                    $id['quantity'],
173
                    Helpers::issetAndHasValueOrAssignDefault($id['attributes'], array()),
174
                    Helpers::issetAndHasValueOrAssignDefault($id['conditions'], array())
175
                );
176
            }
177
178
            return $this;
179
        }
180
181
        // validate data
182
        $item = $this->validate(array(
183
            'id' => $id,
184
            'name' => $name,
185
            'price' => Helpers::normalizePrice($price),
186
            'quantity' => $quantity,
187
            'attributes' => new ItemAttributeCollection($attributes),
188
            'conditions' => $conditions,
189
        ));
190
191
        // get the cart
192
        $cart = $this->getContent();
193
194
        // if the item is already in the cart we will just update it
195
        if( $cart->has($id) )
196
        {
197
198
            $this->update($id, $item);
199
        }
200
        else
201
        {
202
203
            $this->addRow($id, $item);
204
205
        }
206
207
        return $this;
208
    }
209
210
    /**
211
     * update a cart
212
     *
213
     * @param $id
214
     * @param $data
215
     *
216
     * the $data will be an associative array, you don't need to pass all the data, only the key value
217
     * of the item you want to update on it
218
     */
219
    public function update($id, $data)
220
    {
221
        $this->events->fire($this->getInstanceName().'.updating', array($data, $this));
222
223
        $cart = $this->getContent();
224
225
        $item = $cart->pull($id);
226
227
        foreach($data as $key => $value)
228
        {
229
            // if the key is currently "quantity" we will need to check if an arithmetic
230
            // symbol is present so we can decide if the update of quantity is being added
231
            // or being reduced.
232
            if( $key == 'quantity' )
233
            {
234
                // we will check if quantity value provided is array,
235
                // if it is, we will need to check if a key "relative" is set
236
                // and we will evaluate its value if true or false,
237
                // this tells us how to treat the quantity value if it should be updated
238
                // relatively to its current quantity value or just totally replace the value
239
                if( is_array($value) )
240
                {
241
                    if( isset($value['relative']) )
242
                    {
243
                        if( (bool) $value['relative'] )
244
                        {
245
                            $item = $this->updateQuantityRelative($item, $key, $value['value']);
246
                        }
247
                        else
248
                        {
249
                            $item = $this->updateQuantityNotRelative($item, $key, $value['value']);
250
                        }
251
                    }
252
                }
253
                else
254
                {
255
                    $item = $this->updateQuantityRelative($item, $key, $value);
256
                }
257
            }
258
            elseif( $key == 'attributes' )
259
            {
260
                $item[$key] = new ItemAttributeCollection($value);
261
            }
262
            else
263
            {
264
                $item[$key] = $value;
265
            }
266
        }
267
268
        $cart->put($id, $item);
269
270
        $this->save($cart);
271
272
        $this->events->fire($this->getInstanceName().'.updated', array($item, $this));
273
    }
274
275
    /**
276
     * add condition on an existing item on the cart
277
     *
278
     * @param int|string $productId
279
     * @param CartCondition $itemCondition
280
     * @return $this
281
     */
282
    public function addItemCondition($productId, $itemCondition)
283
    {
284
        if( $product = $this->get($productId) )
285
        {
286
            $conditionInstance = "\\Darryldecode\\Cart\\CartCondition";
287
288
            if( $itemCondition instanceof $conditionInstance )
289
            {
290
                // we need to copy first to a temporary variable to hold the conditions
291
                // to avoid hitting this error "Indirect modification of overloaded element of Darryldecode\Cart\ItemCollection has no effect"
292
                // this is due to laravel Collection instance that implements Array Access
293
                // // see link for more info: http://stackoverflow.com/questions/20053269/indirect-modification-of-overloaded-element-of-splfixedarray-has-no-effect
294
                $itemConditionTempHolder = $product['conditions'];
295
296
                if( is_array($itemConditionTempHolder) )
297
                {
298
                    array_push($itemConditionTempHolder, $itemCondition);
299
                }
300
                else
301
                {
302
                    $itemConditionTempHolder = $itemCondition;
303
                }
304
305
                $this->update($productId, array(
306
                    'conditions' => $itemConditionTempHolder // the newly updated conditions
307
                ));
308
            }
309
        }
310
311
        return $this;
312
    }
313
314
    /**
315
     * removes an item on cart by item ID
316
     *
317
     * @param $id
318
     */
319 View Code Duplication
    public function remove($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
320
    {
321
        $cart = $this->getContent();
322
323
        $this->events->fire($this->getInstanceName().'.removing', array($id, $this));
324
325
        $cart->forget($id);
326
327
        $this->save($cart);
328
329
        $this->events->fire($this->getInstanceName().'.removed', array($id, $this));
330
    }
331
332
    /**
333
     * clear cart
334
     */
335
    public function clear()
336
    {
337
        $this->events->fire($this->getInstanceName().'.clearing', array($this));
338
339
        $this->session->put(
340
            $this->sessionKeyCartItems,
341
            array()
342
        );
343
344
        $this->events->fire($this->getInstanceName().'.cleared', array($this));
345
    }
346
347
    /**
348
     * add a condition on the cart
349
     *
350
     * @param CartCondition|array $condition
351
     * @return $this
352
     * @throws InvalidConditionException
353
     */
354
    public function condition($condition)
355
    {
356
        if( is_array($condition) )
357
        {
358
            foreach($condition as $c)
359
            {
360
                $this->condition($c);
361
            }
362
363
            return $this;
364
        }
365
366
        if( ! $condition instanceof CartCondition ) throw new InvalidConditionException('Argument 1 must be an instance of \'Darryldecode\Cart\CartCondition\'');
367
368
        $conditions = $this->getConditions();
369
370
        // Check if order has been applied
371
        if($condition->getOrder() == 0) {
372
            $last = $conditions->last();
373
            $condition->setOrder(!is_null($last) ? $last->getOrder() + 1 : 1);
374
        }
375
376
        $conditions->put($condition->getName(), $condition);
377
378
        $conditions = $conditions->sortBy(function ($condition, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
379
            return $condition->getOrder();
380
        });
381
382
        $this->saveConditions($conditions);
383
384
        return $this;
385
    }
386
387
    /**
388
     * get conditions applied on the cart
389
     *
390
     * @return CartConditionCollection
391
     */
392
    public function getConditions()
393
    {
394
        return new CartConditionCollection($this->session->get($this->sessionKeyCartConditions));
395
    }
396
397
    /**
398
     * get condition applied on the cart by its name
399
     *
400
     * @param $conditionName
401
     * @return CartCondition
402
     */
403
    public function getCondition($conditionName)
404
    {
405
        return $this->getConditions()->get($conditionName);
406
    }
407
408
    /**
409
    * Get all the condition filtered by Type
410
    * Please Note that this will only return condition added on cart bases, not those conditions added
411
    * specifically on an per item bases
412
    *
413
    * @param $type
414
    * @return CartConditionCollection
415
    */
416
    public function getConditionsByType($type)
417
    {
418
        return $this->getConditions()->filter(function(CartCondition $condition) use ($type)
419
        {
420
            return $condition->getType() == $type;
421
        });
422
    }
423
424
425
    /**
426
     * Remove all the condition with the $type specified
427
     * Please Note that this will only remove condition added on cart bases, not those conditions added
428
     * specifically on an per item bases
429
     *
430
     * @param $type
431
     * @return $this
432
     */
433
    public function removeConditionsByType($type)
434
    {
435
        $this->getConditionsByType($type)->each(function($condition)
436
        {
437
            $this->removeCartCondition($condition->getName());
438
        });
439
    }
440
441
442
    /**
443
     * removes a condition on a cart by condition name,
444
     * this can only remove conditions that are added on cart bases not conditions that are added on an item/product.
445
     * If you wish to remove a condition that has been added for a specific item/product, you may
446
     * use the removeItemCondition(itemId, conditionName) method instead.
447
     *
448
     * @param $conditionName
449
     * @return void
450
     */
451
    public function removeCartCondition($conditionName)
452
    {
453
        $conditions = $this->getConditions();
454
455
        $conditions->pull($conditionName);
456
457
        $this->saveConditions($conditions);
458
    }
459
460
    /**
461
     * remove a condition that has been applied on an item that is already on the cart
462
     *
463
     * @param $itemId
464
     * @param $conditionName
465
     * @return bool
466
     */
467
    public function removeItemCondition($itemId, $conditionName)
468
    {
469
        if( ! $item = $this->getContent()->get($itemId) )
470
        {
471
            return false;
472
        }
473
474
        if( $this->itemHasConditions($item) )
475
        {
476
            // NOTE:
477
            // we do it this way, we get first conditions and store
478
            // it in a temp variable $originalConditions, then we will modify the array there
479
            // and after modification we will store it again on $item['conditions']
480
            // This is because of ArrayAccess implementation
481
            // see link for more info: http://stackoverflow.com/questions/20053269/indirect-modification-of-overloaded-element-of-splfixedarray-has-no-effect
482
483
            $tempConditionsHolder = $item['conditions'];
484
485
            // if the item's conditions is in array format
486
            // we will iterate through all of it and check if the name matches
487
            // to the given name the user wants to remove, if so, remove it
488
            if( is_array($tempConditionsHolder) )
489
            {
490
                foreach($tempConditionsHolder as $k => $condition)
491
                {
492
                    if( $condition->getName() == $conditionName )
493
                    {
494
                        unset($tempConditionsHolder[$k]);
495
                    }
496
                }
497
498
                $item['conditions'] = $tempConditionsHolder;
499
            }
500
501
            // if the item condition is not an array, we will check if it is
502
            // an instance of a Condition, if so, we will check if the name matches
503
            // on the given condition name the user wants to remove, if so,
504
            // lets just make $item['conditions'] an empty array as there's just 1 condition on it anyway
505
            else
506
            {
507
                $conditionInstance = "Darryldecode\\Cart\\CartCondition";
508
509
                if ($item['conditions'] instanceof $conditionInstance)
510
                {
511
                    if ($tempConditionsHolder->getName() == $conditionName)
512
                    {
513
                        $item['conditions'] = array();
514
                    }
515
                }
516
            }
517
        }
518
519
        $this->update($itemId, array(
520
            'conditions' => $item['conditions']
521
        ));
522
523
        return true;
524
    }
525
526
    /**
527
     * remove all conditions that has been applied on an item that is already on the cart
528
     *
529
     * @param $itemId
530
     * @return bool
531
     */
532
    public function clearItemConditions($itemId)
533
    {
534
        if( ! $item = $this->getContent()->get($itemId) )
535
        {
536
            return false;
537
        }
538
539
        $this->update($itemId, array(
540
            'conditions' => array()
541
        ));
542
543
        return true;
544
    }
545
546
    /**
547
     * clears all conditions on a cart,
548
     * this does not remove conditions that has been added specifically to an item/product.
549
     * If you wish to remove a specific condition to a product, you may use the method: removeItemCondition($itemId, $conditionName)
550
     *
551
     * @return void
552
     */
553
    public function clearCartConditions()
554
    {
555
        $this->session->put(
556
            $this->sessionKeyCartConditions,
557
            array()
558
        );
559
    }
560
561
    /**
562
     * get cart sub total
563
     *
564
     * @return float
565
     */
566
    public function getSubTotal()
567
    {
568
        $cart = $this->getContent();
569
570
        $sum = $cart->sum(function($item)
571
        {
572
            return $item->getPriceSumWithConditions();
573
        });
574
575
        return number_format(floatval($sum), $this->decimals, $this->dec_point, $this->thousands_sep);
576
    }
577
578
    /**
579
     * the new total in which conditions are already applied
580
     *
581
     * @return float
582
     */
583
    public function getTotal()
584
    {
585
        $subTotal = $this->getSubTotal();
586
587
        $newTotal = 0.00;
588
589
        $process = 0;
590
591
        $conditions = $this->getConditions();
592
593
        // if no conditions were added, just return the sub total
594
        if( ! $conditions->count() ) return $subTotal;
595
596
        $conditions->each(function($cond) use ($subTotal, &$newTotal, &$process)
597
        {
598
            if( $cond->getTarget() === 'subtotal' )
599
            {
600
                ( $process > 0 ) ? $toBeCalculated = $newTotal : $toBeCalculated = $subTotal;
601
602
                $newTotal = $cond->applyCondition($toBeCalculated);
603
604
                $process++;
605
            }
606
        });
607
608
        return number_format($newTotal, $this->decimals, $this->dec_point, $this->thousands_sep);
609
    }
610
611
    /**
612
     * get total quantity of items in the cart
613
     *
614
     * @return int
615
     */
616
    public function getTotalQuantity()
617
    {
618
        $items = $this->getContent();
619
620
        if( $items->isEmpty() ) return 0;
621
622
        $count = $items->sum(function($item)
623
        {
624
            return $item['quantity'];
625
        });
626
627
        return $count;
628
    }
629
630
    /**
631
     * get the cart
632
     *
633
     * @return CartCollection
634
     */
635
    public function getContent()
636
    {
637
        return (new CartCollection($this->session->get($this->sessionKeyCartItems)));
638
    }
639
640
    /**
641
     * check if cart is empty
642
     *
643
     * @return bool
644
     */
645
    public function isEmpty()
646
    {
647
        $cart = new CartCollection($this->session->get($this->sessionKeyCartItems));
648
649
        return $cart->isEmpty();
650
    }
651
652
    /**
653
     * validate Item data
654
     *
655
     * @param $item
656
     * @return array $item;
657
     * @throws InvalidItemException
658
     */
659 View Code Duplication
    protected function validate($item)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
660
    {
661
        $rules = array(
662
            'id' => 'required',
663
            'price' => 'required|numeric',
664
            'quantity' => 'required|numeric|min:1',
665
            'name' => 'required',
666
        );
667
668
        $validator = CartItemValidator::make($item, $rules);
669
670
        if( $validator->fails() )
671
        {
672
            throw new InvalidItemException($validator->messages()->first());
673
        }
674
675
        return $item;
676
    }
677
678
    /**
679
     * add row to cart collection
680
     *
681
     * @param $id
682
     * @param $item
683
     */
684 View Code Duplication
    protected function addRow($id, $item)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
685
    {
686
        $this->events->fire($this->getInstanceName().'.adding', array($item, $this));
687
688
        $cart = $this->getContent();
689
690
        $cart->put($id, new ItemCollection($item, $this->config));
691
692
        $this->save($cart);
693
694
        $this->events->fire($this->getInstanceName().'.added', array($item, $this));
695
    }
696
697
    /**
698
     * save the cart
699
     *
700
     * @param $cart CartCollection
701
     */
702
    protected function save($cart)
703
    {
704
        $this->session->put($this->sessionKeyCartItems, $cart);
705
    }
706
707
    /**
708
     * save the cart conditions
709
     *
710
     * @param $conditions
711
     */
712
    protected function saveConditions($conditions)
713
    {
714
        $this->session->put($this->sessionKeyCartConditions, $conditions);
715
    }
716
717
    /**
718
     * check if an item has condition
719
     *
720
     * @param $item
721
     * @return bool
722
     */
723
    protected function itemHasConditions($item)
724
    {
725
        if( ! isset($item['conditions']) ) return false;
726
727
        if( is_array($item['conditions']) )
728
        {
729
            return count($item['conditions']) > 0;
730
        }
731
732
        $conditionInstance = "Darryldecode\\Cart\\CartCondition";
733
734
        if( $item['conditions'] instanceof $conditionInstance ) return true;
735
736
        return false;
737
    }
738
739
    /**
740
     * update a cart item quantity relative to its current quantity
741
     *
742
     * @param $item
743
     * @param $key
744
     * @param $value
745
     * @return mixed
746
     */
747
    protected function updateQuantityRelative($item, $key, $value)
748
    {
749
        if( preg_match('/\-/', $value) == 1 )
750
        {
751
            $value = (int) str_replace('-','',$value);
752
753
            // we will not allowed to reduced quantity to 0, so if the given value
754
            // would result to item quantity of 0, we will not do it.
755
            if( ($item[$key] - $value) > 0 )
756
            {
757
                $item[$key] -= $value;
758
            }
759
        }
760
        elseif( preg_match('/\+/', $value) == 1 )
761
        {
762
            $item[$key] += (int) str_replace('+','',$value);
763
        }
764
        else
765
        {
766
            $item[$key] += (int) $value;
767
        }
768
769
        return $item;
770
    }
771
772
    /**
773
     * update cart item quantity not relative to its current quantity value
774
     *
775
     * @param $item
776
     * @param $key
777
     * @param $value
778
     * @return mixed
779
     */
780
    protected function updateQuantityNotRelative($item, $key, $value)
781
    {
782
        $item[$key] = (int) $value;
783
784
        return $item;
785
    }
786
787
    /**
788
     * Setter for decimals. Change value on demand.
789
     * @param $decimals
790
     */
791
    public function setDecimals($decimals)
792
    {
793
        $this->decimals = $decimals;
794
    }
795
796
    /**
797
     * Setter for decimals point. Change value on demand.
798
     * @param $dec_point
799
     */
800
    public function setDecPoint($dec_point)
801
    {
802
        $this->dec_point = $dec_point;
803
    }
804
805
    public function setThousandsSep($thousands_sep)
806
    {
807
        $this->thousands_sep = $thousands_sep;
808
    }
809
}
810