Failed Conditions
Push — dev/plugin-misc ( f22d99...066a4d )
by Kiyotaka
10:54 queued 03:43
created

CartService::getCarts()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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