Failed Conditions
Push — pr/3408 ( 278c84 )
by Kiyotaka
06:25
created

CartService::clear()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 3
nop 0
dl 0
loc 26
rs 8.8817
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
32
class CartService
33
{
34
    /**
35
     * @var Cart[]
36
     */
37
    protected $carts;
38
39
    /**
40
     * @var SessionInterface
41
     */
42
    protected $session;
43
44
    /**
45
     * @var \Doctrine\ORM\EntityManagerInterface
46
     */
47
    protected $entityManager;
48
49
    /**
50
     * @var ItemHolderInterface
51
     *
52
     * @deprecated
53
     */
54
    protected $cart;
55
56
    /**
57
     * @var ProductClassRepository
58
     */
59
    protected $productClassRepository;
60
61
    /**
62
     * @var CartRepository
63
     */
64
    protected $cartRepository;
65
66
    /**
67
     * @var CartItemComparator
68
     */
69
    protected $cartItemComparator;
70
71
    /**
72
     * @var CartItemAllocator
73
     */
74
    protected $cartItemAllocator;
75
76
    /**
77
     * @var OrderHelper
78
     */
79
    protected $orderHelper;
80
81
    /**
82
     * @var OrderRepository
83
     */
84
    protected $orderRepository;
85
86
    /**
87
     * @var TokenStorageInterface
88
     */
89
    protected $tokenStorage;
90
91
    /**
92
     * CartService constructor.
93
     *
94
     * @param SessionInterface $session
95
     * @param EntityManagerInterface $entityManager
96
     * @param ProductClassRepository $productClassRepository
97
     * @param CartRepository $cartRepository
98
     * @param CartItemComparator $cartItemComparator
99
     * @param CartItemAllocator $cartItemAllocator
100
     * @param OrderHelper $orderHelper
101
     * @param OrderRepository $orderRepository
102
     * @param TokenStorageInterface $tokenStorage
103
     */
104
    public function __construct(
105
        SessionInterface $session,
106
        EntityManagerInterface $entityManager,
107
        ProductClassRepository $productClassRepository,
108
        CartRepository $cartRepository,
109
        CartItemComparator $cartItemComparator,
110
        CartItemAllocator $cartItemAllocator,
111
        OrderHelper $orderHelper,
112
        OrderRepository $orderRepository,
113
        TokenStorageInterface $tokenStorage
114
    ) {
115
        $this->session = $session;
116
        $this->entityManager = $entityManager;
117
        $this->productClassRepository = $productClassRepository;
118
        $this->cartRepository = $cartRepository;
119
        $this->cartItemComparator = $cartItemComparator;
120
        $this->cartItemAllocator = $cartItemAllocator;
121
        $this->orderHelper = $orderHelper;
122
        $this->orderRepository = $orderRepository;
123
        $this->tokenStorage = $tokenStorage;
124
    }
125
126
    public function getCarts()
127
    {
128
        if (!empty($this->carts)) {
129
            return $this->carts;
130
        }
131
132
        if ($this->getUser()) {
133
            $this->carts = $this->getPersistedCarts();
134
        } else {
135
            $this->carts = $this->getSessionCarts();
136
        }
137
138
        return $this->carts;
139
    }
140
141
    /**
142
     * 永続化されたカートを返す
143
     *
144
     * @return Cart[]
145
     */
146
    public function getPersistedCarts()
147
    {
148
        return $this->cartRepository->findBy(['Customer' => $this->getUser()]);
149
    }
150
151
    /**
152
     * セッションにあるカートを返す
153
     *
154
     * @return Cart[]
155
     */
156
    public function getSessionCarts()
157
    {
158
        $cartKeys = $this->session->get('cart_keys', []);
159
160
        return $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
161
    }
162
163
    /**
164
     * 会員が保持する永続化されたカートと、非会員時のカートをマージする.
165
     */
166
    public function mergeFromPersistedCart()
167
    {
168
        $CartItems = [];
169
        foreach ($this->getPersistedCarts() as $Cart) {
170
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
171
        }
172
173
        // セッションにある非会員カートとDBから取得した会員カートをマージする.
174
        foreach ($this->getSessionCarts() as $Cart) {
175
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
176
        }
177
178
        $this->restoreCarts($CartItems);
179
    }
180
181
    /**
182
     * @return ItemHolderInterface|Cart
183
     */
184
    public function getCart()
185
    {
186
        $Carts = $this->getCarts();
187
188
        if (empty($Carts)) {
189
            return null;
190
        }
191
192
        $cartKeys = $this->session->get('cart_keys', []);
193
        $Cart = null;
194
        if (count($cartKeys) > 0) {
195
            foreach ($Carts as $cart) {
196
                if ($cart->getCartKey() === current($cartKeys)) {
197
                    $Cart = $cart;
198
                    break;
199
                }
200
            }
201
        } else {
202
            $Cart = current($Carts);
203
        }
204
205
        return $Cart;
206
    }
207
208
    /**
209
     * @param CartItem[] $cartItems
210
     *
211
     * @return CartItem[]
212
     */
213
    protected function mergeAllCartItems($cartItems = [])
214
    {
215
        /** @var CartItem[] $allCartItems */
216
        $allCartItems = [];
217
218
        foreach ($this->getCarts() as $Cart) {
219
            $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
220
        }
221
222
        return $this->mergeCartItems($cartItems, $allCartItems);
223
    }
224
225
    /**
226
     * @param $cartItems
227
     * @param $allCartItems
228
     *
229
     * @return array
230
     */
231
    protected function mergeCartItems($cartItems, $allCartItems)
232
    {
233
        foreach ($cartItems as $item) {
234
            $itemExists = false;
235
            foreach ($allCartItems as $itemInArray) {
236
                // 同じ明細があればマージする
237
                if ($this->cartItemComparator->compare($item, $itemInArray)) {
238
                    $itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
239
                    $itemExists = true;
240
                    break;
241
                }
242
            }
243
            if (!$itemExists) {
244
                $allCartItems[] = $item;
245
            }
246
        }
247
248
        return $allCartItems;
249
    }
250
251
    protected function restoreCarts($cartItems)
252
    {
253
        foreach ($this->getCarts() as $Cart) {
254
            foreach ($Cart->getCartItems() as $i) {
255
                $this->entityManager->remove($i);
256
                $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...
257
            }
258
            $this->entityManager->remove($Cart);
259
            $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...
260
        }
261
        $this->carts = [];
262
263
        /** @var Cart[] $Carts */
264
        $Carts = [];
265
266
        foreach ($cartItems as $item) {
267
            $allocatedId = $this->cartItemAllocator->allocate($item);
268
            $cartKey = $this->createCartKey($allocatedId, $this->getUser());
269
270
            if (isset($Carts[$cartKey])) {
271
                $Cart = $Carts[$cartKey];
272
                $Cart->addCartItem($item);
273
                $item->setCart($Cart);
274
            } else {
275
                /** @var Cart $Cart */
276
                $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
277
                if ($Cart) {
278
                    foreach ($Cart->getCartItems() as $i) {
279
                        $this->entityManager->remove($i);
280
                        $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...
281
                    }
282
                    $this->entityManager->remove($Cart);
283
                    $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...
284
                }
285
                $Cart = new Cart();
286
                $Cart->setCartKey($cartKey);
287
                $Cart->addCartItem($item);
288
                $item->setCart($Cart);
289
                $Carts[$cartKey] = $Cart;
290
            }
291
        }
292
293
        $this->carts = array_values($Carts);
294
    }
295
296
    /**
297
     * カートに商品を追加します.
298
     *
299
     * @param $ProductClass ProductClass 商品規格
300
     * @param $quantity int 数量
301
     *
302
     * @return bool 商品を追加できた場合はtrue
303
     */
304
    public function addProduct($ProductClass, $quantity = 1)
305
    {
306 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...
307
            $ProductClassId = $ProductClass;
308
            $ProductClass = $this->entityManager
309
                ->getRepository(ProductClass::class)
310
                ->find($ProductClassId);
311
            if (is_null($ProductClass)) {
312
                return false;
313
            }
314
        }
315
316
        $ClassCategory1 = $ProductClass->getClassCategory1();
317
        if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
318
            return false;
319
        }
320
        $ClassCategory2 = $ProductClass->getClassCategory2();
321
        if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
322
            return false;
323
        }
324
325
        $newItem = new CartItem();
326
        $newItem->setQuantity($quantity);
327
        $newItem->setPrice($ProductClass->getPrice02IncTax());
328
        $newItem->setProductClass($ProductClass);
329
330
        $allCartItems = $this->mergeAllCartItems([$newItem]);
331
        $this->restoreCarts($allCartItems);
332
333
        return true;
334
    }
335
336
    public function removeProduct($ProductClass)
337
    {
338 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...
339
            $ProductClassId = $ProductClass;
340
            $ProductClass = $this->entityManager
341
                ->getRepository(ProductClass::class)
342
                ->find($ProductClassId);
343
            if (is_null($ProductClass)) {
344
                return false;
345
            }
346
        }
347
348
        $removeItem = new CartItem();
349
        $removeItem->setPrice($ProductClass->getPrice02IncTax());
350
        $removeItem->setProductClass($ProductClass);
351
352
        $allCartItems = $this->mergeAllCartItems();
353
        $foundIndex = -1;
354
        foreach ($allCartItems as $index => $itemInCart) {
355
            if ($this->cartItemComparator->compare($itemInCart, $removeItem)) {
356
                $foundIndex = $index;
357
                break;
358
            }
359
        }
360
361
        array_splice($allCartItems, $foundIndex, 1);
362
        $this->restoreCarts($allCartItems);
363
364
        return true;
365
    }
366
367
    public function save()
368
    {
369
        $cartKeys = [];
370
        foreach ($this->carts as $Cart) {
371
            $Cart->setCustomer($this->getUser());
372
            $this->entityManager->persist($Cart);
373
            foreach ($Cart->getCartItems() as $item) {
374
                $this->entityManager->persist($item);
375
                $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...
376
            }
377
            $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...
378
            $cartKeys[] = $Cart->getCartKey();
379
        }
380
381
        $this->session->set('cart_keys', $cartKeys);
382
383
        return;
384
    }
385
386
    /**
387
     * @param  string $pre_order_id
388
     *
389
     * @return \Eccube\Service\CartService
390
     */
391
    public function setPreOrderId($pre_order_id)
392
    {
393
        $this->getCart()->setPreOrderId($pre_order_id);
394
395
        return $this;
396
    }
397
398
    /**
399
     * @return string
400
     */
401
    public function getPreOrderId()
402
    {
403
        return $this->getCart()->getPreOrderId();
404
    }
405
406
    /**
407
     * @return \Eccube\Service\CartService
408
     */
409
    public function clear()
410
    {
411
        $Carts = $this->getCarts();
412
        if (!empty($Carts)) {
413
            $removed = $this->getCart();
414
            if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
415
                $this->entityManager->remove($removed);
416
                $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...
417
418
                $cartKeys = [];
419
                foreach ($Carts as $key => $Cart) {
420
                    // テーブルから削除されたカートを除外する
421
                    if ($Cart == $removed) {
422
                        unset($Carts[$key]);
423
                    }
424
                    $cartKeys[] = $Cart->getCartKey();
425
                }
426
                $this->session->set('cart_keys', $cartKeys);
427
                // 注文完了のカートキーをセッションから削除する
428
                $this->session->remove('cart_key');
429
                $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
430
            }
431
        }
432
433
        return $this;
434
    }
435
436
    /**
437
     * @param CartItemComparator $cartItemComparator
438
     */
439
    public function setCartItemComparator($cartItemComparator)
440
    {
441
        $this->cartItemComparator = $cartItemComparator;
442
    }
443
444
    /**
445
     * カートキーで指定したインデックスにあるカートを優先にする
446
     *
447
     * @param string $cartKey カートキー
448
     */
449
    public function setPrimary($cartKey)
450
    {
451
        $Carts = $this->getCarts();
452
        $primary = $Carts[0];
453
        $index = 0;
454
        foreach ($Carts as $key => $Cart) {
455
            if ($Cart->getCartKey() === $cartKey) {
456
                $index = $key;
457
                $primary = $Carts[$index];
458
                break;
459
            }
460
        }
461
        $prev = $Carts[0];
462
        array_splice($Carts, 0, 1, [$primary]);
463
        array_splice($Carts, $index, 1, [$prev]);
464
        $this->carts = $Carts;
465
        $this->save();
466
    }
467
468
    protected function getUser()
469
    {
470
        if (null === $token = $this->tokenStorage->getToken()) {
471
            return;
472
        }
473
474
        if (!is_object($user = $token->getUser())) {
475
            // e.g. anonymous authentication
476
            return;
477
        }
478
479
        return $user;
480
    }
481
482
    /**
483
     * @param string $allocatedId
484
     *
485
     * @return string
486
     */
487
    protected function createCartKey($allocatedId, Customer $Customer = null)
488
    {
489
        if ($Customer instanceof Customer) {
490
            return $Customer->getId().'_'.$allocatedId;
491
        }
492
493
        if ($this->session->has('cart_key_prefix')) {
494
            return $this->session->get('cart_key_prefix').'_'.$allocatedId;
495
        }
496
497
        do {
498
            $random = StringUtil::random(32);
499
            $cartKey = $random.'_'.$allocatedId;
500
            $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
501
        } while ($Cart);
502
503
        $this->session->set('cart_key_prefix', $random);
504
505
        return $cartKey;
506
    }
507
}
508