Completed
Push — sf/fix-multi-browser-carts ( 96a945 )
by Kiyotaka
167:03 queued 103:04
created

CartService::clear()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 3
nop 0
dl 0
loc 20
rs 9.2888
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Service;
15
16
use Doctrine\ORM\EntityManagerInterface;
17
use Doctrine\ORM\UnitOfWork;
18
use Eccube\Entity\Cart;
19
use Eccube\Entity\CartItem;
20
use Eccube\Entity\Customer;
21
use Eccube\Entity\ItemHolderInterface;
22
use Eccube\Entity\ProductClass;
23
use Eccube\Repository\CartRepository;
24
use Eccube\Repository\OrderRepository;
25
use Eccube\Repository\ProductClassRepository;
26
use Eccube\Service\Cart\CartItemAllocator;
27
use Eccube\Service\Cart\CartItemComparator;
28
use Eccube\Util\StringUtil;
29
use Symfony\Component\HttpFoundation\Session\SessionInterface;
30
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
31
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
32
33
class CartService
34
{
35
    /**
36
     * @var Cart[]
37
     */
38
    protected $carts;
39
40
    /**
41
     * @var SessionInterface
42
     */
43
    protected $session;
44
45
    /**
46
     * @var \Doctrine\ORM\EntityManagerInterface
47
     */
48
    protected $entityManager;
49
50
    /**
51
     * @var ItemHolderInterface
52
     *
53
     * @deprecated
54
     */
55
    protected $cart;
56
57
    /**
58
     * @var ProductClassRepository
59
     */
60
    protected $productClassRepository;
61
62
    /**
63
     * @var CartRepository
64
     */
65
    protected $cartRepository;
66
67
    /**
68
     * @var CartItemComparator
69
     */
70
    protected $cartItemComparator;
71
72
    /**
73
     * @var CartItemAllocator
74
     */
75
    protected $cartItemAllocator;
76
77
    /**
78
     * @var OrderHelper
79
     */
80
    protected $orderHelper;
81
82
    /**
83
     * @var OrderRepository
84
     */
85
    protected $orderRepository;
86
87
    /**
88
     * @var TokenStorageInterface
89
     */
90
    protected $tokenStorage;
91
92
    /**
93
     * @var AuthorizationCheckerInterface
94
     */
95
    protected $authorizationChecker;
96
97
    /**
98
     * CartService constructor.
99
     *
100
     * @param SessionInterface $session
101
     * @param EntityManagerInterface $entityManager
102
     * @param ProductClassRepository $productClassRepository
103
     * @param CartItemComparator $cartItemComparator
104
     * @param CartItemAllocator $cartItemAllocator
105
     * @param OrderHelper $orderHelper
106
     * @param TokenStorageInterface $tokenStorage
107
     * @param AuthorizationCheckerInterface $authorizationChecker
108
     */
109
    public function __construct(
110
        SessionInterface $session,
111
        EntityManagerInterface $entityManager,
112
        ProductClassRepository $productClassRepository,
113
        CartRepository $cartRepository,
114
        CartItemComparator $cartItemComparator,
115
        CartItemAllocator $cartItemAllocator,
116
        OrderHelper $orderHelper,
117
        OrderRepository $orderRepository,
118
        TokenStorageInterface $tokenStorage,
119
        AuthorizationCheckerInterface $authorizationChecker
120
    ) {
121
        $this->session = $session;
122
        $this->entityManager = $entityManager;
123
        $this->productClassRepository = $productClassRepository;
124
        $this->cartRepository = $cartRepository;
125
        $this->cartItemComparator = $cartItemComparator;
126
        $this->cartItemAllocator = $cartItemAllocator;
127
        $this->orderHelper = $orderHelper;
128
        $this->orderRepository = $orderRepository;
129
        $this->tokenStorage = $tokenStorage;
130
        $this->authorizationChecker = $authorizationChecker;
131
    }
132
133
    public function getCarts()
134
    {
135
        if (!empty($this->carts)) {
136
            return $this->carts;
137
        }
138
139
        if ($this->getUser()) {
140
            $this->carts = $this->getPersistedCarts();
141
        } else {
142
            $this->carts = $this->getSessionCarts();
143
        }
144
145
        return $this->carts;
146
    }
147
148
    /**
149
     * 永続化されたカートを返す
150
     *
151
     * @return Cart[]
152
     */
153
    public function getPersistedCarts()
154
    {
155
        return $this->cartRepository->findBy(['Customer' => $this->getUser()]);
156
    }
157
158
    /**
159
     * セッションにあるカートを返す
160
     *
161
     * @return Cart[]
162
     */
163
    public function getSessionCarts()
164
    {
165
        $cartKeys = $this->session->get('cart_keys', []);
166
        return $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
167
    }
168
169
    /**
170
     * 会員が保持する永続化されたカートと、非会員時のカートをマージする.
171
     *
172
     * @param Customer $Customer
173
     */
174
    public function mergeFromPersistedCart(Customer $Customer)
0 ignored issues
show
Unused Code introduced by
The parameter $Customer 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...
175
    {
176
        $CartItems = [];
177
        foreach ($this->getPersistedCarts() as $Cart) {
178
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
179
        }
180
181
        // セッションにある非会員カートとDBから取得した会員カートをマージする.
182
        foreach ($this->getSessionCarts() as $Cart) {
183
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
184
        }
185
186
        $this->restoreCarts($CartItems);
187
    }
188
189
    /**
190
     * @return ItemHolderInterface|Cart
191
     */
192
    public function getCart()
193
    {
194
        $Carts = $this->getCarts();
195
196
        if (empty($Carts)) {
197
            return null;
198
        }
199
200
        return current($Carts);
201
    }
202
203
    /**
204
     * @param CartItem[] $cartItems
205
     *
206
     * @return CartItem[]
207
     */
208
    protected function mergeAllCartItems($cartItems = [])
209
    {
210
        /** @var CartItem[] $allCartItems */
211
        $allCartItems = [];
212
213
        foreach ($this->getCarts() as $Cart) {
214
            $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
215
        }
216
217
        return $this->mergeCartItems($cartItems, $allCartItems);
218
    }
219
220
    /**
221
     * @param $cartItems
222
     * @param $allCartItems
223
     *
224
     * @return array
225
     */
226
    protected function mergeCartItems($cartItems, $allCartItems)
227
    {
228
        foreach ($cartItems as $item) {
229
            $itemExists = false;
230
            foreach ($allCartItems as $itemInArray) {
231
                // 同じ明細があればマージする
232
                if ($this->cartItemComparator->compare($item, $itemInArray)) {
233
                    $itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
234
                    $itemExists = true;
235
                    break;
236
                }
237
            }
238
            if (!$itemExists) {
239
                $allCartItems[] = $item;
240
            }
241
        }
242
243
        return $allCartItems;
244
    }
245
246
    protected function restoreCarts($cartItems)
247
    {
248
        if (empty($cartItems)) {
249
            foreach ($this->getCarts() as $Cart) {
250
                foreach ($Cart->getCartItems() as $i) {
251
                    $this->entityManager->remove($i);
252
                    $this->entityManager->flush($i);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $i.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
253
                }
254
                $this->entityManager->remove($Cart);
255
                $this->entityManager->flush($Cart);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $Cart.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
256
            }
257
            $this->carts = [];
258
        }
259
260
        /** @var Cart[] $Carts */
261
        $Carts = [];
262
263
        foreach ($cartItems as $item) {
264
            $allocatedId = $this->cartItemAllocator->allocate($item);
265
            $cartKey = $this->createCartKey($allocatedId, $this->getUser());
266
267
            if (isset($Carts[$cartKey])) {
268
                $Cart = $Carts[$cartKey];
269
                $Cart->addCartItem($item);
270
                $item->setCart($Cart);
271
            } else {
272
                /** @var Cart $Cart */
273
                $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
274
                if ($Cart) {
275
                    foreach ($Cart->getCartItems() as $i) {
276
                        $this->entityManager->remove($i);
277
                        $this->entityManager->flush($i);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $i.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
278
                    }
279
                    $this->entityManager->remove($Cart);
280
                    $this->entityManager->flush($Cart);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $Cart.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
281
                }
282
                $Cart = new Cart();
283
                $Cart->setCartKey($cartKey);
284
                $Cart->addCartItem($item);
285
                $item->setCart($Cart);
286
                $Carts[$cartKey] = $Cart;
287
            }
288
        }
289
290
        $this->carts = array_values($Carts);
291
    }
292
293
    /**
294
     * カートに商品を追加します.
295
     *
296
     * @param $ProductClass ProductClass 商品規格
297
     * @param $quantity int 数量
298
     *
299
     * @return bool 商品を追加できた場合はtrue
300
     */
301
    public function addProduct($ProductClass, $quantity = 1)
302
    {
303 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
304
            $ProductClassId = $ProductClass;
305
            $ProductClass = $this->entityManager
306
                ->getRepository(ProductClass::class)
307
                ->find($ProductClassId);
308
            if (is_null($ProductClass)) {
309
                return false;
310
            }
311
        }
312
313
        $ClassCategory1 = $ProductClass->getClassCategory1();
314
        if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
315
            return false;
316
        }
317
        $ClassCategory2 = $ProductClass->getClassCategory2();
318
        if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
319
            return false;
320
        }
321
322
        $newItem = new CartItem();
323
        $newItem->setQuantity($quantity);
324
        $newItem->setPrice($ProductClass->getPrice02IncTax());
325
        $newItem->setProductClass($ProductClass);
326
327
        $allCartItems = $this->mergeAllCartItems([$newItem]);
328
        $this->restoreCarts($allCartItems);
329
330
        return true;
331
    }
332
333
    public function removeProduct($ProductClass)
334
    {
335 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
336
            $ProductClassId = $ProductClass;
337
            $ProductClass = $this->entityManager
338
                ->getRepository(ProductClass::class)
339
                ->find($ProductClassId);
340
            if (is_null($ProductClass)) {
341
                return false;
342
            }
343
        }
344
345
        $removeItem = new CartItem();
346
        $removeItem->setPrice($ProductClass->getPrice02IncTax());
347
        $removeItem->setProductClass($ProductClass);
348
349
        $allCartItems = $this->mergeAllCartItems();
350
        $foundIndex = -1;
351
        foreach ($allCartItems as $index => $itemInCart) {
352
            if ($this->cartItemComparator->compare($itemInCart, $removeItem)) {
353
                $foundIndex = $index;
354
                break;
355
            }
356
        }
357
358
        array_splice($allCartItems, $foundIndex, 1);
359
        $this->restoreCarts($allCartItems);
360
361
        return true;
362
    }
363
364
    public function save()
365
    {
366
        $cartKeys = [];
367
        foreach ($this->carts as $Cart) {
368
            $Cart->setCustomer($this->getUser());
369
            $this->entityManager->persist($Cart);
370
            foreach ($Cart->getCartItems() as $item) {
371
                $this->entityManager->persist($item);
372
                $this->entityManager->flush($item);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $item.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
373
            }
374
            $this->entityManager->flush($Cart);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $Cart.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
375
            $cartKeys[] = $Cart->getCartKey();
376
        }
377
378
        $this->session->set('cart_keys', $cartKeys);
379
380
        return;
381
    }
382
383
    /**
384
     * @param  string $pre_order_id
385
     *
386
     * @return \Eccube\Service\CartService
387
     */
388
    public function setPreOrderId($pre_order_id)
389
    {
390
        $this->getCart()->setPreOrderId($pre_order_id);
391
392
        return $this;
393
    }
394
395
    /**
396
     * @return string
397
     */
398
    public function getPreOrderId()
399
    {
400
        return $this->getCart()->getPreOrderId();
401
    }
402
403
    /**
404
     * @return \Eccube\Service\CartService
405
     */
406
    public function clear()
407
    {
408
        $Carts = $this->getCarts();
409
        if (!empty($Carts)) {
410
            $removed = array_shift($Carts);
411
            if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
412
                $this->entityManager->remove($removed);
413
                $this->entityManager->flush($removed);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $removed.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
414
415
                $cartKeys = [];
416
                foreach ($Carts as $Cart) {
417
                    $cartKeys[] = $Cart->getCartKey();
418
                }
419
                $this->session->set('cart_keys', $cartKeys);
420
                $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
421
            }
422
        }
423
424
        return $this;
425
    }
426
427
    /**
428
     * @param CartItemComparator $cartItemComparator
429
     */
430
    public function setCartItemComparator($cartItemComparator)
431
    {
432
        $this->cartItemComparator = $cartItemComparator;
433
    }
434
435
    /**
436
     * 指定したインデックスにあるカートを優先にする
437
     *
438
     * @param int $index カートのインデックス
439
     */
440
    public function setPrimary($index = 0)
441
    {
442
        $Carts = $this->getCarts();
443
        $primary = $Carts[$index];
444
        $prev = $Carts[0];
445
        array_splice($Carts, 0, 1, [$primary]);
446
        array_splice($Carts, $index, 1, [$prev]);
447
        $this->carts = $Carts;
448
        $this->save();
449
    }
450
451
    protected function getUser()
452
    {
453
        if (null === $token = $this->tokenStorage->getToken()) {
454
            return;
455
        }
456
457
        if (!is_object($user = $token->getUser())) {
458
            // e.g. anonymous authentication
459
            return;
460
        }
461
462
        return $user;
463
    }
464
465
    /**
466
     * @param string $allocatedId
467
     */
468
    protected function createCartKey($allocatedId, Customer $Customer = null)
469
    {
470
        if ($Customer instanceof Customer) {
471
            return $Customer->getId().'_'.$allocatedId;
472
        }
473
474
        if ($this->session->has('cart_key_prefix')) {
475
            return $this->session->get('cart_key_prefix').'_'.$allocatedId;
476
        }
477
478
        do {
479
            $random = StringUtil::random(32);
480
            $cartKey = $random.'_'.$allocatedId;
481
            $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
482
        } while ($Cart);
483
484
        $this->session->set('cart_key_prefix', $random);
485
486
        return $cartKey;
487
    }
488
}
489