Test Setup Failed
Push — master ( 6454fe...8607a7 )
by
unknown
11:50
created

Cart::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 5
1
<?php
2
3
/*
4
 * This file is part of ibrand/laravel-shopping-cart.
5
 *
6
 * (c) iBrand <https://www.ibrand.cc>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace iBrand\Shoppingcart;
13
14
use iBrand\Shoppingcart\Storage\Storage;
15
use Illuminate\Contracts\Events\Dispatcher;
16
use Illuminate\Support\Collection;
17
18
/**
19
 * Main class of Overtrue\LaravelShoppingCart package.
20
 */
21
class Cart
22
{
23
    /**
24
     * Session manager.
25
     *
26
     * @var \iBrand\Shoppingcart\Storage\Storage
27
     */
28
    protected $storage;
29
30
    /**
31
     * Event dispatcher.
32
     *
33
     * @var \Illuminate\Contracts\Events\Dispatcher
34
     */
35
    protected $event;
36
37
    /**
38
     * Current cart name.
39
     *
40
     * @var string
41
     */
42
    protected $name = 'cart.default';
43
44
    /**
45
     * Associated model name.
46
     *
47
     * @var string
48
     */
49
    protected $model;
50
51
    /**
52
     * Constructor.
53
     *
54
     * @param \iBrand\Shoppingcart\Storage\Storage    $storage $storage class name
55
     * @param \Illuminate\Contracts\Events\Dispatcher $event   Event class name
56
     */
57
    public function __construct(Storage $storage, Dispatcher $event)
58
    {
59
        $this->storage = $storage;
60
        $this->event = $event;
61
    }
62
63
    public function setStorage(Storage $storage)
64
    {
65
        $this->storage = $storage;
66
    }
67
68
    /**
69
     * Set the current cart name.
70
     *
71
     * @param string $name Cart name name
72
     *
73
     * @return Cart
74
     */
75
    public function name($name)
76
    {
77
        $this->name = 'cart.'.$name;
78
79
        return $this;
80
    }
81
82
    /**
83
     * Associated model.
84
     *
85
     * @param string $model The name of the model
86
     *
87
     * @return Cart
88
     */
89
    public function associate($model)
90
    {
91
        if (!class_exists($model)) {
92
            throw new Exception("Invalid model name '$model'.");
93
        }
94
        $this->model = $model;
95
96
        return $this;
97
    }
98
99
    /**
100
     * Get all items.
101
     *
102
     * @return \Illuminate\Support\Collection
103
     */
104
    public function all()
105
    {
106
        return $this->getCart();
107
    }
108
109
    /**
110
     * Add a row to the cart.
111
     *
112
     * @param int|string $id         Unique ID of the item
113
     * @param string     $name       Name of the item
114
     * @param int        $qty        Item qty to add to the cart
115
     * @param float      $price      Price of one item
116
     * @param array      $attributes Array of additional attributes, such as 'size' or 'color'...
117
     *
118
     * @return string
119
     */
120
    public function add($id, $name = null, $qty = null, $price = null, array $attributes = [])
121
    {
122
        $cart = $this->getCart();
123
124
        $this->event->fire('cart.adding', [$attributes, $cart]);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
125
126
        $row = $this->addRow($id, $name, $qty, $price, $attributes);
127
128
        $this->event->fire('cart.added', [$attributes, $cart]);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
129
130
        return $row;
131
    }
132
133
    /**
134
     * Update the quantity of one row of the cart.
135
     *
136
     * @param string    $rawId     The __raw_id of the item you want to update
137
     * @param int|array $attribute New quantity of the item|Array of attributes to update
138
     *
139
     * @return Item|bool
140
     */
141
    public function update($rawId, $attribute)
142
    {
143
        if (!$row = $this->get($rawId)) {
144
            throw new Exception('Item not found.');
145
        }
146
        $cart = $this->getCart();
147
148
        $this->event->fire('cart.updating', [$row, $cart]);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
149
150
        if (is_array($attribute)) {
151
            $raw = $this->updateAttribute($rawId, $attribute);
152
        } else {
153
            $raw = $this->updateQty($rawId, $attribute);
154
        }
155
156
        $this->event->fire('cart.updated', [$row, $cart]);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
157
158
        return $raw;
159
    }
160
161
    /**
162
     * Remove a row from the cart.
163
     *
164
     * @param string $rawId The __raw_id of the item
165
     *
166
     * @return bool
167
     */
168
    public function remove($rawId)
169
    {
170
        if (!$row = $this->get($rawId)) {
171
            return true;
172
        }
173
174
        $cart = $this->getCart();
175
176
        $this->event->fire('cart.removing', [$row, $cart]);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
177
178
        $cart->forget($rawId);
179
180
        $this->event->fire('cart.removed', [$row, $cart]);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
181
182
        $this->save($cart);
183
184
        return true;
185
    }
186
187
    /**
188
     * Get a row of the cart by its ID.
189
     *
190
     * @param string $rawId The ID of the row to fetch
191
     *
192
     * @return Item
193
     */
194
    public function get($rawId)
195
    {
196
        $row = $this->getCart()->get($rawId);
197
198
        return is_null($row) ? null : new Item($row);
199
    }
200
201
    /**
202
     * Clean the cart.
203
     *
204
     * @return bool
205
     */
206
    public function destroy()
207
    {
208
        $cart = $this->getCart();
209
210
        $this->event->fire('cart.destroying', $cart);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
211
212
        $this->save(null);
213
214
        $this->event->fire('cart.destroyed', $cart);
0 ignored issues
show
Bug introduced by
The method fire() does not seem to exist on object<Illuminate\Contracts\Events\Dispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
215
216
        return true;
217
    }
218
219
    /**
220
     * Alias of destory().
221
     *
222
     * @return bool
223
     */
224
    public function clean()
225
    {
226
        $this->destroy();
227
    }
228
229
    /**
230
     * Get the price total.
231
     *
232
     * @return float
233
     */
234
    public function total()
235
    {
236
        return $this->totalPrice();
237
    }
238
239
    /**
240
     * Return total price of cart.
241
     *
242
     * @return
243
     */
244
    public function totalPrice()
245
    {
246
        $total = 0;
247
248
        $cart = $this->getCart();
249
250
        if ($cart->isEmpty()) {
251
            return $total;
252
        }
253
254
        foreach ($cart as $row) {
255
            $total += $row->qty * $row->price;
256
        }
257
258
        return $total;
259
    }
260
261
    /**
262
     * Get the number of items in the cart.
263
     *
264
     * @param bool $totalItems Get all the items (when false, will return the number of rows)
265
     *
266
     * @return int
267
     */
268
    public function count($totalItems = true)
269
    {
270
        $items = $this->getCart();
271
272
        if (!$totalItems) {
273
            return $items->count();
274
        }
275
276
        $count = 0;
277
278
        foreach ($items as $row) {
279
            $count += $row->qty;
280
        }
281
282
        return $count;
283
    }
284
285
    /**
286
     * Get rows count.
287
     *
288
     * @return int
289
     */
290
    public function countRows()
291
    {
292
        return $this->count(false);
293
    }
294
295
    /**
296
     * Search if the cart has a item.
297
     *
298
     * @param array $search An array with the item ID and optional options
299
     *
300
     * @return array
301
     */
302
    public function search(array $search)
303
    {
304
        $rows = new Collection();
305
306
        if (empty($search)) {
307
            return $rows;
308
        }
309
310
        foreach ($this->getCart() as $item) {
311
            if (array_intersect_assoc($item->intersect($search)->toArray(), $search)) {
312
                $rows->put($item->__raw_id, $item);
313
            }
314
        }
315
316
        return $rows;
317
    }
318
319
    /**
320
     * Get current cart name.
321
     *
322
     * @return string
323
     */
324
    public function getName()
325
    {
326
        return $this->name;
327
    }
328
329
    /**
330
     * Get current associated model.
331
     *
332
     * @return string
333
     */
334
    public function getModel()
335
    {
336
        return $this->model;
337
    }
338
339
    /**
340
     * Return whether the shopping cart is empty.
341
     *
342
     * @return bool
343
     */
344
    public function isEmpty()
345
    {
346
        return $this->count() <= 0;
347
    }
348
349
    /**
350
     * Add row to the cart.
351
     *
352
     * @param string $id         Unique ID of the item
353
     * @param string $name       Name of the item
354
     * @param int    $qty        Item qty to add to the cart
355
     * @param float  $price      Price of one item
356
     * @param array  $attributes Array of additional options, such as 'size' or 'color'
357
     *
358
     * @return string
359
     */
360
    protected function addRow($id, $name, $qty, $price, array $attributes = [])
361
    {
362
        if (!is_numeric($qty) || $qty < 1) {
363
            throw new Exception('Invalid quantity.');
364
        }
365
366
        if (!is_numeric($price) || $price < 0) {
367
            throw new Exception('Invalid price.');
368
        }
369
370
        $cart = $this->getCart();
371
372
        $rawId = $this->generateRawId($id, $attributes);
373
374
        if ($row = $cart->get($rawId)) {
375
            $row = $this->updateQty($rawId, $row->qty + $qty);
376
        } else {
377
            $row = $this->insertRow($rawId, $id, $name, $qty, $price, $attributes);
378
        }
379
380
        return $row;
381
    }
382
383
    /**
384
     * Generate a unique id for the new row.
385
     *
386
     * @param string $id         Unique ID of the item
387
     * @param array  $attributes Array of additional options, such as 'size' or 'color'
388
     *
389
     * @return string
390
     */
391
    protected function generateRawId($id, $attributes)
392
    {
393
        ksort($attributes);
394
395
        return md5($id.serialize($attributes));
396
    }
397
398
    /**
399
     * Sync the cart to strage.
400
     *
401
     * @param \Illuminate\Support\Collection|null $cart The new cart content
402
     *
403
     * @return \Illuminate\Support\Collection
404
     */
405
    protected function save($cart)
406
    {
407
        $this->storage->set($this->name, $cart);
408
409
        return $cart;
410
    }
411
412
    public function saveFromSession()
413
    {
414
        $session = session('cart.default');
415
        $session = $session instanceof Collection ? $session : new Collection();
416
        $cart = $this->getCart();
417
        $cart = $cart->merge($session);
418
        session()->forget('cart.default');
419
        $this->save($cart);
420
    }
421
422
    /**
423
     * Get the carts content.
424
     *
425
     * @return \Illuminate\Support\Collection
426
     */
427
    protected function getCart()
428
    {
429
        $cart = $this->storage->get($this->name);
430
431
        return $cart instanceof Collection ? $cart : new Collection();
432
    }
433
434
    /**
435
     * Update a row if the rawId already exists.
436
     *
437
     * @param string $rawId      The ID of the row to update
438
     * @param array  $attributes The quantity to add to the row
439
     *
440
     * @return Item
441
     */
442
    protected function updateRow($rawId, array $attributes)
443
    {
444
        $cart = $this->getCart();
445
446
        $row = $cart->get($rawId);
447
448
        foreach ($attributes as $key => $value) {
449
            $row->put($key, $value);
450
        }
451
452
        if (count(array_intersect(array_keys($attributes), ['qty', 'price']))) {
453
            $row->put('total', $row->qty * $row->price);
454
        }
455
456
        $cart->put($rawId, $row);
457
458
        $this->save($cart);
459
460
        return $row;
461
    }
462
463
    /**
464
     * Create a new row Object.
465
     *
466
     * @param string $rawId      The ID of the new row
467
     * @param string $id         Unique ID of the item
468
     * @param string $name       Name of the item
469
     * @param int    $qty        Item qty to add to the cart
470
     * @param float  $price      Price of one item
471
     * @param array  $attributes Array of additional options, such as 'size' or 'color'
472
     *
473
     * @return Item
474
     */
475
    protected function insertRow($rawId, $id, $name, $qty, $price, $attributes = [])
476
    {
477
        $newRow = $this->makeRow($rawId, $id, $name, $qty, $price, $attributes);
478
479
        $cart = $this->getCart();
480
481
        $cart->put($rawId, $newRow);
482
483
        $this->save($cart);
484
485
        return $newRow;
486
    }
487
488
    /**
489
     * Make a row item.
490
     *
491
     * @param string $rawId      raw id
492
     * @param mixed  $id         item id
493
     * @param string $name       item name
494
     * @param int    $qty        quantity
495
     * @param float  $price      price
496
     * @param array  $attributes other attributes
497
     *
498
     * @return Item
499
     */
500
    protected function makeRow($rawId, $id, $name, $qty, $price, array $attributes = [])
501
    {
502
        return new Item(array_merge([
503
            '__raw_id' => $rawId,
504
            'id' => $id,
505
            'name' => $name,
506
            'qty' => $qty,
507
            'price' => $price,
508
            'total' => $qty * $price,
509
            '__model' => $this->model,
510
        ], $attributes));
511
    }
512
513
    /**
514
     * Update the quantity of a row.
515
     *
516
     * @param string $rawId The ID of the row
517
     * @param int    $qty   The qty to add
518
     *
519
     * @return Item|bool
520
     */
521
    protected function updateQty($rawId, $qty)
522
    {
523
        if ($qty <= 0) {
524
            return $this->remove($rawId);
525
        }
526
527
        return $this->updateRow($rawId, ['qty' => $qty]);
528
    }
529
530
    /**
531
     * Update an attribute of the row.
532
     *
533
     * @param string $rawId      The ID of the row
534
     * @param array  $attributes An array of attributes to update
535
     *
536
     * @return Item
537
     */
538
    protected function updateAttribute($rawId, $attributes)
539
    {
540
        return $this->updateRow($rawId, $attributes);
541
    }
542
}
543