Completed
Push — 4.0 ( 701914...f1dd4a )
by Ryo
55:07 queued 36:19
created

CartService   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 502
Duplicated Lines 3.59 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 93.3%

Importance

Changes 0
Metric Value
dl 18
loc 502
ccs 167
cts 179
cp 0.933
rs 3.12
c 0
b 0
f 0
wmc 66
lcom 1
cbo 14

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 1
B getCarts() 0 30 6
A getPersistedCarts() 0 4 1
A getSessionCarts() 0 6 1
A mergeFromPersistedCart() 0 14 3
A getCart() 0 23 5
A mergeAllCartItems() 0 11 2
A mergeCartItems() 0 19 5
B restoreCarts() 0 44 7
B addProduct() 9 31 7
A removeProduct() 9 30 5
A save() 0 18 3
A setPreOrderId() 0 6 1
A getPreOrderId() 0 9 2
B clear() 0 26 6
A setCartItemComparator() 0 4 1
A setPrimary() 0 18 3
A getUser() 0 13 3
A createCartKey() 0 20 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CartService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CartService, and based on these observations, apply Extract Interface, too.

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 OrderRepository
79
     */
80
    protected $orderRepository;
81
82
    /**
83
     * @var TokenStorageInterface
84
     */
85
    protected $tokenStorage;
86
87
    /**
88
     * @var AuthorizationCheckerInterface
89
     */
90
    protected $authorizationChecker;
91
92
    /**
93
     * CartService constructor.
94
     *
95
     * @param SessionInterface $session
96
     * @param EntityManagerInterface $entityManager
97
     * @param ProductClassRepository $productClassRepository
98
     * @param CartItemComparator $cartItemComparator
99
     * @param CartItemAllocator $cartItemAllocator
100
     * @param TokenStorageInterface $tokenStorage
101
     * @param AuthorizationCheckerInterface $authorizationChecker
102
     */
103
    public function __construct(
104
        SessionInterface $session,
105
        EntityManagerInterface $entityManager,
106
        ProductClassRepository $productClassRepository,
107
        CartRepository $cartRepository,
108
        CartItemComparator $cartItemComparator,
109 173
        CartItemAllocator $cartItemAllocator,
110
        OrderRepository $orderRepository,
111
        TokenStorageInterface $tokenStorage,
112
        AuthorizationCheckerInterface $authorizationChecker
113
    ) {
114
        $this->session = $session;
115
        $this->entityManager = $entityManager;
116
        $this->productClassRepository = $productClassRepository;
117
        $this->cartRepository = $cartRepository;
118
        $this->cartItemComparator = $cartItemComparator;
119
        $this->cartItemAllocator = $cartItemAllocator;
120
        $this->orderRepository = $orderRepository;
121 173
        $this->tokenStorage = $tokenStorage;
122 173
        $this->authorizationChecker = $authorizationChecker;
123 173
    }
124 173
125 173
    /**
126 173
     * 現在のカートの配列を取得する.
127 173
     *
128 173
     * 本サービスのインスタンスのメンバーが空の場合は、DBまたはセッションからカートを取得する
129 173
     *
130 173
     * @param bool $empty_delete true の場合、商品明細が空のカートが存在した場合は削除する
131
     *
132
     * @return Cart[]
133 132
     */
134
    public function getCarts($empty_delete = false)
135 132
    {
136 86
        if (!empty($this->carts)) {
137
            if ($empty_delete) {
138
                $cartKeys = [];
139 132
                foreach (array_keys($this->carts) as $index) {
140 65
                    $Cart = $this->carts[$index];
141
                    if ($Cart->getItems()->count() > 0) {
142 67
                        $cartKeys[] = $Cart->getCartKey();
143
                    } else {
144
                        $this->entityManager->remove($this->carts[$index]);
145 132
                        $this->entityManager->flush($this->carts[$index]);
0 ignored issues
show
Unused Code introduced by
The call to EntityManagerInterface::flush() has too many arguments starting with $this->carts[$index].

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...
146
                        unset($this->carts[$index]);
147
                    }
148
                }
149
150
                $this->session->set('cart_keys', $cartKeys);
151
            }
152
153 65
            return $this->carts;
154
        }
155 65
156
        if ($this->getUser()) {
157
            $this->carts = $this->getPersistedCarts();
158
        } else {
159
            $this->carts = $this->getSessionCarts();
160
        }
161
162
        return $this->carts;
163 67
    }
164
165 67
    /**
166
     * 永続化されたカートを返す
167 67
     *
168
     * @return Cart[]
169
     */
170
    public function getPersistedCarts()
171
    {
172
        return $this->cartRepository->findBy(['Customer' => $this->getUser()]);
173
    }
174
175
    /**
176
     * セッションにあるカートを返す
177
     *
178
     * @return Cart[]
179
     */
180
    public function getSessionCarts()
181
    {
182
        $cartKeys = $this->session->get('cart_keys', []);
183
184
        return $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
185
    }
186
187
    /**
188
     * 会員が保持する永続化されたカートと、非会員時のカートをマージする.
189
     */
190
    public function mergeFromPersistedCart()
191 65
    {
192
        $CartItems = [];
193 65
        foreach ($this->getPersistedCarts() as $Cart) {
194
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
195 65
        }
196 4
197
        // セッションにある非会員カートとDBから取得した会員カートをマージする.
198
        foreach ($this->getSessionCarts() as $Cart) {
199 63
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
200
        }
201
202
        $this->restoreCarts($CartItems);
203
    }
204
205
    /**
206
     * @return Cart|null
207 87
     */
208
    public function getCart()
209
    {
210 87
        $Carts = $this->getCarts();
211
212 87
        if (empty($Carts)) {
213 39
            return null;
214
        }
215
216 87
        $cartKeys = $this->session->get('cart_keys', []);
217
        $Cart = null;
218
        if (count($cartKeys) > 0) {
219
            foreach ($Carts as $cart) {
220
                if ($cart->getCartKey() === current($cartKeys)) {
221
                    $Cart = $cart;
222
                    break;
223
                }
224
            }
225 87
        } else {
226
            $Cart = $Carts[0];
227 87
        }
228 86
229 86
        return $Cart;
230
    }
231 38
232 36
    /**
233 36
     * @param CartItem[] $cartItems
234 38
     *
235
     * @return CartItem[]
236
     */
237 86
    protected function mergeAllCartItems($cartItems = [])
238 86
    {
239
        /** @var CartItem[] $allCartItems */
240
        $allCartItems = [];
241
242 87
        foreach ($this->getCarts() as $Cart) {
243
            $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
244
        }
245 87
246
        return $this->mergeCartItems($cartItems, $allCartItems);
247 87
    }
248 2
249 1
    /**
250 1
     * @param $cartItems
251 1
     * @param $allCartItems
252
     *
253 1
     * @return array
254 1
     */
255
    protected function mergeCartItems($cartItems, $allCartItems)
256 2
    {
257
        foreach ($cartItems as $item) {
258
            $itemExists = false;
259
            foreach ($allCartItems as $itemInArray) {
260 87
                // 同じ明細があればマージする
261
                if ($this->cartItemComparator->compare($item, $itemInArray)) {
262 87
                    $itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
263 86
                    $itemExists = true;
264 86
                    break;
265
                }
266 86
            }
267 16
            if (!$itemExists) {
268 16
                $allCartItems[] = $item;
269 16
            }
270
        }
271
272 86
        return $allCartItems;
273 86
    }
274 34
275 34
    protected function restoreCarts($cartItems)
276 34
    {
277
        foreach ($this->getCarts() as $Cart) {
278 34
            foreach ($Cart->getCartItems() as $i) {
279 34
                $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 86
            }
282 86
            $this->entityManager->remove($Cart);
283 86
            $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 86
        }
285 86
        $this->carts = [];
286
287
        /** @var Cart[] $Carts */
288
        $Carts = [];
289 87
290
        foreach ($cartItems as $item) {
291
            $allocatedId = $this->cartItemAllocator->allocate($item);
292
            $cartKey = $this->createCartKey($allocatedId, $this->getUser());
293
294
            if (isset($Carts[$cartKey])) {
295
                $Cart = $Carts[$cartKey];
296
                $Cart->addCartItem($item);
297
                $item->setCart($Cart);
298
            } else {
299
                /** @var Cart $Cart */
300 86
                $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
301
                if ($Cart) {
302 86
                    foreach ($Cart->getCartItems() as $i) {
303 45
                        $this->entityManager->remove($i);
304 45
                        $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...
305 45
                    }
306 45
                    $this->entityManager->remove($Cart);
307 45
                    $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...
308
                }
309
                $Cart = new Cart();
310
                $Cart->setCartKey($cartKey);
311
                $Cart->addCartItem($item);
312 86
                $item->setCart($Cart);
313 86
                $Carts[$cartKey] = $Cart;
314
            }
315
        }
316 86
317 86
        $this->carts = array_values($Carts);
318
    }
319
320
    /**
321 86
     * カートに商品を追加します.
322 86
     *
323 86
     * @param $ProductClass ProductClass 商品規格
324 86
     * @param $quantity int 数量
325
     *
326 86
     * @return bool 商品を追加できた場合はtrue
327 86
     */
328
    public function addProduct($ProductClass, $quantity = 1)
329 86
    {
330 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
331
            $ProductClassId = $ProductClass;
332 2
            $ProductClass = $this->entityManager
333
                ->getRepository(ProductClass::class)
334 2
                ->find($ProductClassId);
335 1
            if (is_null($ProductClass)) {
336 1
                return false;
337 1
            }
338 1
        }
339 1
340
        $ClassCategory1 = $ProductClass->getClassCategory1();
341
        if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
342
            return false;
343
        }
344 2
        $ClassCategory2 = $ProductClass->getClassCategory2();
345 2
        if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
346 2
            return false;
347
        }
348 2
349 2
        $newItem = new CartItem();
350 2
        $newItem->setQuantity($quantity);
351 1
        $newItem->setPrice($ProductClass->getPrice02IncTax());
352 1
        $newItem->setProductClass($ProductClass);
353 1
354
        $allCartItems = $this->mergeAllCartItems([$newItem]);
355
        $this->restoreCarts($allCartItems);
356
357 2
        return true;
358 2
    }
359
360 2
    public function removeProduct($ProductClass)
361
    {
362 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
363 80
            $ProductClassId = $ProductClass;
364
            $ProductClass = $this->entityManager
365 80
                ->getRepository(ProductClass::class)
366 80
                ->find($ProductClassId);
367 78
            if (is_null($ProductClass)) {
368 78
                return false;
369 78
            }
370 78
        }
371 78
372
        $removeItem = new CartItem();
373 78
        $removeItem->setPrice($ProductClass->getPrice02IncTax());
374 78
        $removeItem->setProductClass($ProductClass);
375
376
        $allCartItems = $this->mergeAllCartItems();
377 80
        $foundIndex = -1;
378
        foreach ($allCartItems as $index => $itemInCart) {
379 80
            if ($this->cartItemComparator->compare($itemInCart, $removeItem)) {
380
                $foundIndex = $index;
381
                break;
382
            }
383
        }
384
385
        array_splice($allCartItems, $foundIndex, 1);
386
        $this->restoreCarts($allCartItems);
387 52
388
        return true;
389 52
    }
390
391 52
    public function save()
392
    {
393
        $cartKeys = [];
394
        foreach ($this->carts as $Cart) {
395
            $Cart->setCustomer($this->getUser());
396
            $this->entityManager->persist($Cart);
397 51
            foreach ($Cart->getCartItems() as $item) {
398
                $this->entityManager->persist($item);
399 51
                $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...
400
            }
401
            $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...
402
            $cartKeys[] = $Cart->getCartKey();
403
        }
404
405 10
        $this->session->set('cart_keys', $cartKeys);
406
407 10
        return;
408 10
    }
409 8
410 8
    /**
411 7
     * @param  string $pre_order_id
412 7
     *
413
     * @return \Eccube\Service\CartService
414 7
     */
415 7
    public function setPreOrderId($pre_order_id)
416
    {
417
        $this->getCart()->setPreOrderId($pre_order_id);
418 7
419 7
        return $this;
420
    }
421
422
    /**
423 10
     * @return null|string
424
     */
425
    public function getPreOrderId()
426
    {
427
        $Cart = $this->getCart();
428
        if (!empty($Cart)) {
429 1
            return $Cart->getPreOrderId();
430
        }
431 1
432
        return null;
433
    }
434
435
    /**
436
     * @return \Eccube\Service\CartService
437
     */
438
    public function clear()
439 53
    {
440
        $Carts = $this->getCarts();
441 53
        if (!empty($Carts)) {
442 53
            $removed = $this->getCart();
443 53
            if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
444 53
                $this->entityManager->remove($removed);
445 53
                $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...
446 53
447 53
                $cartKeys = [];
448
                foreach ($Carts as $key => $Cart) {
449
                    // テーブルから削除されたカートを除外する
450 132
                    if ($Cart == $removed) {
451
                        unset($Carts[$key]);
452 132
                    }
453 10
                    $cartKeys[] = $Cart->getCartKey();
454
                }
455
                $this->session->set('cart_keys', $cartKeys);
456 122
                // 注文完了のカートキーをセッションから削除する
457
                $this->session->remove('cart_key');
458 57
                $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
459
            }
460
        }
461 65
462
        return $this;
463
    }
464
465
    /**
466
     * @param CartItemComparator $cartItemComparator
467 86
     */
468
    public function setCartItemComparator($cartItemComparator)
469 86
    {
470 51
        $this->cartItemComparator = $cartItemComparator;
471
    }
472
473 35
    /**
474 17
     * カートキーで指定したインデックスにあるカートを優先にする
475
     *
476
     * @param string $cartKey カートキー
477
     */
478 35
    public function setPrimary($cartKey)
479 35
    {
480 35
        $Carts = $this->getCarts();
481 35
        $primary = $Carts[0];
482
        $index = 0;
483 35
        foreach ($Carts as $key => $Cart) {
484
            if ($Cart->getCartKey() === $cartKey) {
485 35
                $index = $key;
486
                $primary = $Carts[$index];
487
                break;
488
            }
489
        }
490
        $prev = $Carts[0];
491
        array_splice($Carts, 0, 1, [$primary]);
492
        array_splice($Carts, $index, 1, [$prev]);
493
        $this->carts = $Carts;
494
        $this->save();
495
    }
496
497
    protected function getUser()
498
    {
499
        if (null === $token = $this->tokenStorage->getToken()) {
500
            return;
501
        }
502
503
        if (!is_object($user = $token->getUser())) {
504
            // e.g. anonymous authentication
505
            return;
506
        }
507
508
        return $user;
509
    }
510
511
    /**
512
     * @param string $allocatedId
513
     */
514
    protected function createCartKey($allocatedId, Customer $Customer = null)
515
    {
516
        if ($Customer instanceof Customer) {
517
            return $Customer->getId().'_'.$allocatedId;
518
        }
519
520
        if ($this->session->has('cart_key_prefix')) {
521
            return $this->session->get('cart_key_prefix').'_'.$allocatedId;
522
        }
523
524
        do {
525
            $random = StringUtil::random(32);
526
            $cartKey = $random.'_'.$allocatedId;
527
            $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
528
        } while ($Cart);
529
530
        $this->session->set('cart_key_prefix', $random);
531
532
        return $cartKey;
533
    }
534
}
535