Completed
Push — sf/improvement-coverage ( b3937e...01a837 )
by Kiyotaka
51:20 queued 45:08
created

CartService::createCartKey()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 2
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
ccs 11
cts 11
cp 1
crap 4
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 173
    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 173
        $this->session = $session;
122 173
        $this->entityManager = $entityManager;
123 173
        $this->productClassRepository = $productClassRepository;
124 173
        $this->cartRepository = $cartRepository;
125 173
        $this->cartItemComparator = $cartItemComparator;
126 173
        $this->cartItemAllocator = $cartItemAllocator;
127 173
        $this->orderHelper = $orderHelper;
128 173
        $this->orderRepository = $orderRepository;
129 173
        $this->tokenStorage = $tokenStorage;
130 173
        $this->authorizationChecker = $authorizationChecker;
131
    }
132
133 132
    public function getCarts()
134
    {
135 132
        if (!empty($this->carts)) {
136 86
            return $this->carts;
137
        }
138
139 132
        $cartKeys = $this->session->get('cart_keys', []);
140 132
        $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
141
142 132
        return $this->carts;
143
    }
144
145
    /**
146
     * 会員が保持する永続化されたカートと、非会員時のカートをマージする.
147
     *
148
     * @param Customer $Customer
149
     */
150
    public function mergeFromPersistedCart(Customer $Customer)
151
    {
152
        $Carts = $this->cartRepository->findBy(['Customer' => $Customer]);
153
154
        $CartItems = [];
155
        foreach ($Carts as $Cart) {
156
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
157
        }
158
159
        // セッションにある非会員カートとDBから取得した会員カートをマージする.
160
        $CartItems = $this->mergeAllCartItems($CartItems);
161
        $this->restoreCarts($CartItems);
162
    }
163
164
    /**
165
     * @return ItemHolderInterface|Cart
166
     */
167 65
    public function getCart()
168
    {
169 65
        $Carts = $this->getCarts();
170
171 65
        if (empty($Carts)) {
172 4
            return null;
173
        }
174
175 63
        return current($Carts);
176
    }
177
178
    /**
179
     * @param CartItem[] $cartItems
180
     *
181
     * @return CartItem[]
182
     */
183 87
    protected function mergeAllCartItems($cartItems = [])
184
    {
185
        /** @var CartItem[] $allCartItems */
186 87
        $allCartItems = [];
187
188 87
        foreach ($this->getCarts() as $Cart) {
189 39
            $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
190
        }
191
192 87
        return $this->mergeCartItems($cartItems, $allCartItems);
193
    }
194
195
    /**
196
     * @param $cartItems
197
     * @param $allCartItems
198
     *
199
     * @return array
200
     */
201 87
    protected function mergeCartItems($cartItems, $allCartItems)
202
    {
203 87
        foreach ($cartItems as $item) {
204 86
            $itemExists = false;
205 86
            foreach ($allCartItems as $itemInArray) {
206
                // 同じ明細があればマージする
207 38
                if ($this->cartItemComparator->compare($item, $itemInArray)) {
208 36
                    $itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
209 36
                    $itemExists = true;
210 38
                    break;
211
                }
212
            }
213 86
            if (!$itemExists) {
214 86
                $allCartItems[] = $item;
215
            }
216
        }
217
218 87
        return $allCartItems;
219
    }
220
221 87
    protected function restoreCarts($cartItems)
222
    {
223 87
        if (empty($cartItems)) {
224 2
            foreach ($this->getCarts() as $Cart) {
225 1
                foreach ($Cart->getCartItems() as $i) {
226 1
                    $this->entityManager->remove($i);
227 1
                    $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...
228
                }
229 1
                $this->entityManager->remove($Cart);
230 1
                $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...
231
            }
232 2
            $this->carts = [];
233
        }
234
235
        /** @var Cart[] $Carts */
236 87
        $Carts = [];
237
238 87
        foreach ($cartItems as $item) {
239 86
            $allocatedId = $this->cartItemAllocator->allocate($item);
240 86
            $cartKey = $this->createCartKey($allocatedId, $this->getUser());
241
242 86
            if (isset($Carts[$cartKey])) {
243 16
                $Cart = $Carts[$cartKey];
244 16
                $Cart->addCartItem($item);
245 16
                $item->setCart($Cart);
246
            } else {
247
                /** @var Cart $Cart */
248 86
                $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
249 86
                if ($Cart) {
250 34
                    foreach ($Cart->getCartItems() as $i) {
251 34
                        $this->entityManager->remove($i);
252 34
                        $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 34
                    $this->entityManager->remove($Cart);
255 34
                    $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 86
                $Cart = new Cart();
258 86
                $Cart->setCartKey($cartKey);
259 86
                $Cart->addCartItem($item);
260 86
                $item->setCart($Cart);
261 86
                $Carts[$cartKey] = $Cart;
262
            }
263
        }
264
265 87
        $this->carts = array_values($Carts);
266
    }
267
268
    /**
269
     * カートに商品を追加します.
270
     *
271
     * @param $ProductClass ProductClass 商品規格
272
     * @param $quantity int 数量
273
     *
274
     * @return bool 商品を追加できた場合はtrue
275
     */
276 86
    public function addProduct($ProductClass, $quantity = 1)
277
    {
278 86 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...
279 45
            $ProductClassId = $ProductClass;
280 45
            $ProductClass = $this->entityManager
281 45
                ->getRepository(ProductClass::class)
282 45
                ->find($ProductClassId);
283 45
            if (is_null($ProductClass)) {
284
                return false;
285
            }
286
        }
287
288 86
        $ClassCategory1 = $ProductClass->getClassCategory1();
289 86
        if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
290
            return false;
291
        }
292 86
        $ClassCategory2 = $ProductClass->getClassCategory2();
293 86
        if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
294
            return false;
295
        }
296
297 86
        $newItem = new CartItem();
298 86
        $newItem->setQuantity($quantity);
299 86
        $newItem->setPrice($ProductClass->getPrice02IncTax());
300 86
        $newItem->setProductClass($ProductClass);
301
302 86
        $allCartItems = $this->mergeAllCartItems([$newItem]);
303 86
        $this->restoreCarts($allCartItems);
304
305 86
        return true;
306
    }
307
308 2
    public function removeProduct($ProductClass)
309
    {
310 2 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...
311 1
            $ProductClassId = $ProductClass;
312 1
            $ProductClass = $this->entityManager
313 1
                ->getRepository(ProductClass::class)
314 1
                ->find($ProductClassId);
315 1
            if (is_null($ProductClass)) {
316
                return false;
317
            }
318
        }
319
320 2
        $removeItem = new CartItem();
321 2
        $removeItem->setPrice($ProductClass->getPrice02IncTax());
322 2
        $removeItem->setProductClass($ProductClass);
323
324 2
        $allCartItems = $this->mergeAllCartItems();
325 2
        $foundIndex = -1;
326 2
        foreach ($allCartItems as $index => $itemInCart) {
327 1
            if ($this->cartItemComparator->compare($itemInCart, $removeItem)) {
328 1
                $foundIndex = $index;
329 1
                break;
330
            }
331
        }
332
333 2
        array_splice($allCartItems, $foundIndex, 1);
334 2
        $this->restoreCarts($allCartItems);
335
336 2
        return true;
337
    }
338
339 80
    public function save()
340
    {
341 80
        $cartKeys = [];
342 80
        foreach ($this->carts as $Cart) {
343 78
            $Cart->setCustomer($this->getUser());
344 78
            $this->entityManager->persist($Cart);
345 78
            foreach ($Cart->getCartItems() as $item) {
346 78
                $this->entityManager->persist($item);
347 78
                $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...
348
            }
349 78
            $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...
350 78
            $cartKeys[] = $Cart->getCartKey();
351
        }
352
353 80
        $this->session->set('cart_keys', $cartKeys);
354
355 80
        return;
356
    }
357
358
    /**
359
     * @param  string $pre_order_id
360
     *
361
     * @return \Eccube\Service\CartService
362
     */
363 52
    public function setPreOrderId($pre_order_id)
364
    {
365 52
        $this->getCart()->setPreOrderId($pre_order_id);
366
367 52
        return $this;
368
    }
369
370
    /**
371
     * @return string
372
     */
373 51
    public function getPreOrderId()
374
    {
375 51
        return $this->getCart()->getPreOrderId();
376
    }
377
378
    /**
379
     * @return \Eccube\Service\CartService
380
     */
381 10
    public function clear()
382
    {
383 10
        $Carts = $this->getCarts();
384 10
        if (!empty($Carts)) {
385 8
            $removed = array_shift($Carts);
386 8
            if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
387 7
                $this->entityManager->remove($removed);
388 7
                $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...
389
390 7
                $cartKeys = [];
391 7
                foreach ($Carts as $Cart) {
392
                    $cartKeys[] = $Cart->getCartKey();
393
                }
394 7
                $this->session->set('cart_keys', $cartKeys);
395 7
                $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
396
            }
397
        }
398
399 10
        return $this;
400
    }
401
402
    /**
403
     * @param CartItemComparator $cartItemComparator
404
     */
405 1
    public function setCartItemComparator($cartItemComparator)
406
    {
407 1
        $this->cartItemComparator = $cartItemComparator;
408
    }
409
410
    /**
411
     * 指定したインデックスにあるカートを優先にする
412
     *
413
     * @param int $index カートのインデックス
414
     */
415 53
    public function setPrimary($index = 0)
416
    {
417 53
        $Carts = $this->getCarts();
418 53
        $primary = $Carts[$index];
419 53
        $prev = $Carts[0];
420 53
        array_splice($Carts, 0, 1, [$primary]);
421 53
        array_splice($Carts, $index, 1, [$prev]);
422 53
        $this->carts = $Carts;
423 53
        $this->save();
424
    }
425
426 86
    protected function getUser()
427
    {
428 86
        if (null === $token = $this->tokenStorage->getToken()) {
429 10
            return;
430
        }
431
432 76
        if (!is_object($user = $token->getUser())) {
433
            // e.g. anonymous authentication
434 25
            return;
435
        }
436
437 51
        return $user;
438
    }
439
440
    /**
441
     * @param string $allocatedId
442
     */
443 86
    protected function createCartKey($allocatedId, Customer $Customer = null)
444
    {
445 86
        if ($Customer instanceof Customer) {
446 51
            return $Customer->getId().'_'.$allocatedId;
447
        }
448
449 35
        if ($this->session->has('cart_key_prefix')) {
450 17
            return $this->session->get('cart_key_prefix').'_'.$allocatedId;
451
        }
452
453
        do {
454 35
            $random = StringUtil::random(32);
455 35
            $cartKey = $random.'_'.$allocatedId;
456 35
            $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
457 35
        } while ($Cart);
458
459 35
        $this->session->set('cart_key_prefix', $random);
460
461 35
        return $cartKey;
462
    }
463
}
464