Completed
Pull Request — experimental/sf (#3243)
by Kentaro
49:28 queued 40:44
created

CartService   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 428
Duplicated Lines 4.21 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 93.06%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 18
loc 428
ccs 161
cts 173
cp 0.9306
rs 6.96
wmc 53
lcom 1
cbo 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A mergeFromPersistedCart() 0 13 2
A mergeCartItems() 0 19 5
B addProduct() 9 31 7
A setPreOrderId() 0 6 1
A getPreOrderId() 0 4 1
A __construct() 0 23 1
A getCarts() 0 11 2
A save() 0 18 3
A getCart() 0 10 2
A mergeAllCartItems() 0 11 2
B restoreCarts() 0 46 8
A removeProduct() 9 30 5
A clear() 0 20 5
A setCartItemComparator() 0 4 1
A setPrimary() 0 10 1
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\UnitOfWork;
17
use Eccube\Entity\Cart;
18
use Eccube\Entity\CartItem;
19
use Eccube\Entity\Customer;
20
use Eccube\Entity\ItemHolderInterface;
21
use Eccube\Entity\ProductClass;
22
use Eccube\Repository\CartRepository;
23
use Eccube\Repository\ProductClassRepository;
24
use Eccube\Repository\OrderRepository;
25
use Eccube\Service\Cart\CartItemAllocator;
26
use Eccube\Service\Cart\CartItemComparator;
27
use Eccube\Util\StringUtil;
28
use Symfony\Component\HttpFoundation\Session\SessionInterface;
29
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
30
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
31
use Doctrine\ORM\EntityManagerInterface;
32
33
class CartService
0 ignored issues
show
introduced by
Missing class doc comment
Loading history...
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
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$cartRepository" missing
Loading history...
introduced by
Doc comment for parameter "$orderRepository" missing
Loading history...
98
     * CartService constructor.
99
     *
100
     * @param SessionInterface $session
0 ignored issues
show
introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
101
     * @param EntityManagerInterface $entityManager
0 ignored issues
show
introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
102
     * @param ProductClassRepository $productClassRepository
0 ignored issues
show
introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
103
     * @param CartItemComparator $cartItemComparator
0 ignored issues
show
introduced by
Expected 12 spaces after parameter type; 1 found
Loading history...
introduced by
Doc comment for parameter $cartItemComparator does not match actual variable name $cartRepository
Loading history...
104
     * @param CartItemAllocator $cartItemAllocator
0 ignored issues
show
introduced by
Expected 13 spaces after parameter type; 1 found
Loading history...
introduced by
Doc comment for parameter $cartItemAllocator does not match actual variable name $cartItemComparator
Loading history...
105
     * @param OrderHelper $orderHelper
0 ignored issues
show
introduced by
Expected 19 spaces after parameter type; 1 found
Loading history...
introduced by
Doc comment for parameter $orderHelper does not match actual variable name $cartItemAllocator
Loading history...
106
     * @param TokenStorageInterface $tokenStorage
0 ignored issues
show
introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
introduced by
Doc comment for parameter $tokenStorage does not match actual variable name $orderHelper
Loading history...
107
     * @param AuthorizationCheckerInterface $authorizationChecker
0 ignored issues
show
introduced by
Doc comment for parameter $authorizationChecker does not match actual variable name $orderRepository
Loading history...
108
     */
109 171
    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 171
        $this->session = $session;
122 171
        $this->entityManager = $entityManager;
123 171
        $this->productClassRepository = $productClassRepository;
124 171
        $this->cartRepository = $cartRepository;
125 171
        $this->cartItemComparator = $cartItemComparator;
126 171
        $this->cartItemAllocator = $cartItemAllocator;
127 171
        $this->orderHelper = $orderHelper;
128 171
        $this->orderRepository = $orderRepository;
129 171
        $this->tokenStorage = $tokenStorage;
130 171
        $this->authorizationChecker = $authorizationChecker;
131
    }
132
133 131
    public function getCarts()
134
    {
135 131
        if (!empty($this->carts)) {
136 85
            return $this->carts;
137
        }
138
139 131
        $cartKeys = $this->session->get('cart_keys', []);
140 131
        $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
141
142 131
        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 86
    protected function mergeAllCartItems($cartItems = [])
184
    {
185
        /** @var CartItem[] $allCartItems */
186 86
        $allCartItems = [];
187
188 86
        foreach ($this->getCarts() as $Cart) {
189 39
            $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
190
        }
191
192 86
        return $this->mergeCartItems($cartItems, $allCartItems);
193
    }
194
195
    /**
196
     * @param $cartItems
197
     * @param $allCartItems
198
     *
199
     * @return array
200
     */
201 86
    protected function mergeCartItems($cartItems, $allCartItems)
202
    {
203 86
        foreach ($cartItems as $item) {
204 85
            $itemExists = false;
205 85
            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 85
            if (!$itemExists) {
214 85
                $allCartItems[] = $item;
215
            }
216
        }
217
218 86
        return $allCartItems;
219
    }
220
221 86
    protected function restoreCarts($cartItems)
222
    {
223 86
        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 86
        $Carts = [];
237
238 86
        foreach ($cartItems as $item) {
239 85
            $allocatedId = $this->cartItemAllocator->allocate($item);
240 85
            $cartKey = $this->createCartKey($allocatedId, $this->getUser());
241
242 85
            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 85
                $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
249 85
                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 85
                $Cart = new Cart();
258 85
                $Cart->setCartKey($cartKey);
259 85
                $Cart->addCartItem($item);
260 85
                $item->setCart($Cart);
261 85
                $Carts[$cartKey] = $Cart;
262
            }
263
        }
264
265 86
        $this->carts = array_values($Carts);
266
    }
267
268
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$ProductClass" missing
Loading history...
introduced by
Doc comment for parameter "$quantity" missing
Loading history...
269
     * カートに商品を追加します.
270
     *
271
     * @param $ProductClass ProductClass 商品規格
0 ignored issues
show
introduced by
Missing parameter name
Loading history...
272
     * @param $quantity int 数量
0 ignored issues
show
introduced by
Missing parameter name
Loading history...
273
     *
274
     * @return bool 商品を追加できた場合はtrue
275
     */
276 85
    public function addProduct($ProductClass, $quantity = 1)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
277
    {
278 85 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
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 85
        $ClassCategory1 = $ProductClass->getClassCategory1();
289 85
        if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
290
            return false;
291
        }
292 85
        $ClassCategory2 = $ProductClass->getClassCategory2();
293 85
        if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
294
            return false;
295
        }
296
297 85
        $newItem = new CartItem();
298 85
        $newItem->setQuantity($quantity);
299 85
        $newItem->setPrice($ProductClass->getPrice02IncTax());
300 85
        $newItem->setProductClass($ProductClass);
301
302 85
        $allCartItems = $this->mergeAllCartItems([$newItem]);
303 85
        $this->restoreCarts($allCartItems);
304
305 85
        return true;
306
    }
307
308 2
    public function removeProduct($ProductClass)
309
    {
310 2 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
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;
0 ignored issues
show
Coding Style introduced by
Empty return statement not required here
Loading history...
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 85
    protected function getUser()
427
    {
428 85
        if (null === $token = $this->tokenStorage->getToken()) {
429 10
            return;
430
        }
431
432 75
        if (!is_object($user = $token->getUser())) {
433
            // e.g. anonymous authentication
434 25
            return;
435
        }
436
437 50
        return $user;
438
    }
439
440 85
    protected function createCartKey($allocatedId, Customer $Customer = null)
441
    {
442 85
        if ($Customer instanceof Customer) {
443 50
            return $Customer->getId().'_'.$allocatedId;
444
        }
445
446 35
        if ($this->session->has('cart_key_prefix')) {
447 17
            return $this->session->get('cart_key_prefix').'_'.$allocatedId;
448
        }
449
450
        do {
451 35
            $random = StringUtil::random(32);
452 35
            $cartKey = $random.'_'.$allocatedId;
453 35
            $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
454 35
        } while ($Cart);
455
456 35
        $this->session->set('cart_key_prefix', $random);
457
458 35
        return $cartKey;
459
    }
460
}
461