Completed
Push — 4.0 ( 87d096...bcc1be )
by Kiyotaka
05:44 queued 11s
created

src/Eccube/Service/CartService.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.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 (null !== $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]);
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
        if (empty($cartKeys)) {
185
            return [];
186
        }
187
188
        return $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
189
    }
190
191 65
    /**
192
     * 会員が保持する永続化されたカートと、非会員時のカートをマージする.
193 65
     */
194
    public function mergeFromPersistedCart()
195 65
    {
196 4
        $CartItems = [];
197
        foreach ($this->getPersistedCarts() as $Cart) {
198
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
199 63
        }
200
201
        // セッションにある非会員カートとDBから取得した会員カートをマージする.
202
        foreach ($this->getSessionCarts() as $Cart) {
203
            $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
204
        }
205
206
        $this->restoreCarts($CartItems);
207 87
    }
208
209
    /**
210 87
     * @return Cart|null
211
     */
212 87
    public function getCart()
213 39
    {
214
        $Carts = $this->getCarts();
215
216 87
        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 87
                    $Cart = $cart;
226
                    break;
227 87
                }
228 86
            }
229 86
        } else {
230
            $Cart = $Carts[0];
231 38
        }
232 36
233 36
        return $Cart;
234 38
    }
235
236
    /**
237 86
     * @param CartItem[] $cartItems
238 86
     *
239
     * @return CartItem[]
240
     */
241
    protected function mergeAllCartItems($cartItems = [])
242 87
    {
243
        /** @var CartItem[] $allCartItems */
244
        $allCartItems = [];
245 87
246
        foreach ($this->getCarts() as $Cart) {
247 87
            $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
248 2
        }
249 1
250 1
        return $this->mergeCartItems($cartItems, $allCartItems);
251 1
    }
252
253 1
    /**
254 1
     * @param $cartItems
255
     * @param $allCartItems
256 2
     *
257
     * @return array
258
     */
259
    protected function mergeCartItems($cartItems, $allCartItems)
260 87
    {
261
        foreach ($cartItems as $item) {
262 87
            $itemExists = false;
263 86
            foreach ($allCartItems as $itemInArray) {
264 86
                // 同じ明細があればマージする
265
                if ($this->cartItemComparator->compare($item, $itemInArray)) {
266 86
                    $itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
267 16
                    $itemExists = true;
268 16
                    break;
269 16
                }
270
            }
271
            if (!$itemExists) {
272 86
                $allCartItems[] = $item;
273 86
            }
274 34
        }
275 34
276 34
        return $allCartItems;
277
    }
278 34
279 34
    protected function restoreCarts($cartItems)
280
    {
281 86
        foreach ($this->getCarts() as $Cart) {
282 86
            foreach ($Cart->getCartItems() as $i) {
283 86
                $this->entityManager->remove($i);
284 86
                $this->entityManager->flush($i);
0 ignored issues
show
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 86
            }
286
            $this->entityManager->remove($Cart);
287
            $this->entityManager->flush($Cart);
0 ignored issues
show
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 87
        $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 86
                $Cart->addCartItem($item);
301
                $item->setCart($Cart);
302 86
            } else {
303 45
                /** @var Cart $Cart */
304 45
                $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
305 45
                if ($Cart) {
306 45
                    foreach ($Cart->getCartItems() as $i) {
307 45
                        $this->entityManager->remove($i);
308
                        $this->entityManager->flush($i);
0 ignored issues
show
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
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 86
                }
313 86
                $Cart = new Cart();
314
                $Cart->setCartKey($cartKey);
315
                $Cart->addCartItem($item);
316 86
                $item->setCart($Cart);
317 86
                $Carts[$cartKey] = $Cart;
318
            }
319
        }
320
321 86
        $this->carts = array_values($Carts);
322 86
    }
323 86
324 86
    /**
325
     * カートに商品を追加します.
326 86
     *
327 86
     * @param $ProductClass ProductClass 商品規格
328
     * @param $quantity int 数量
329 86
     *
330
     * @return bool 商品を追加できた場合はtrue
331
     */
332 2
    public function addProduct($ProductClass, $quantity = 1)
333
    {
334 2 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
335 1
            $ProductClassId = $ProductClass;
336 1
            $ProductClass = $this->entityManager
337 1
                ->getRepository(ProductClass::class)
338 1
                ->find($ProductClassId);
339 1
            if (is_null($ProductClass)) {
340
                return false;
341
            }
342
        }
343
344 2
        $ClassCategory1 = $ProductClass->getClassCategory1();
345 2
        if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
346 2
            return false;
347
        }
348 2
        $ClassCategory2 = $ProductClass->getClassCategory2();
349 2
        if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
350 2
            return false;
351 1
        }
352 1
353 1
        $newItem = new CartItem();
354
        $newItem->setQuantity($quantity);
355
        $newItem->setPrice($ProductClass->getPrice02IncTax());
356
        $newItem->setProductClass($ProductClass);
357 2
358 2
        $allCartItems = $this->mergeAllCartItems([$newItem]);
359
        $this->restoreCarts($allCartItems);
360 2
361
        return true;
362
    }
363 80
364
    public function removeProduct($ProductClass)
365 80
    {
366 80 View Code Duplication
        if (!$ProductClass instanceof ProductClass) {
367 78
            $ProductClassId = $ProductClass;
368 78
            $ProductClass = $this->entityManager
369 78
                ->getRepository(ProductClass::class)
370 78
                ->find($ProductClassId);
371 78
            if (is_null($ProductClass)) {
372
                return false;
373 78
            }
374 78
        }
375
376
        $removeItem = new CartItem();
377 80
        $removeItem->setPrice($ProductClass->getPrice02IncTax());
378
        $removeItem->setProductClass($ProductClass);
379 80
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 52
        }
388
389 52
        array_splice($allCartItems, $foundIndex, 1);
390
        $this->restoreCarts($allCartItems);
391 52
392
        return true;
393
    }
394
395
    public function save()
396
    {
397 51
        $cartKeys = [];
398
        foreach ($this->carts as $Cart) {
399 51
            $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
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 10
            $this->entityManager->flush($Cart);
406
            $cartKeys[] = $Cart->getCartKey();
407 10
        }
408 10
409 8
        $this->session->set('cart_keys', $cartKeys);
410 8
411 7
        return;
412 7
    }
413
414 7
    /**
415 7
     * @param  string $pre_order_id
416
     *
417
     * @return \Eccube\Service\CartService
418 7
     */
419 7
    public function setPreOrderId($pre_order_id)
420
    {
421
        $this->getCart()->setPreOrderId($pre_order_id);
422
423 10
        return $this;
424
    }
425
426
    /**
427
     * @return null|string
428
     */
429 1
    public function getPreOrderId()
430
    {
431 1
        $Cart = $this->getCart();
432
        if (!empty($Cart)) {
433
            return $Cart->getPreOrderId();
434
        }
435
436
        return null;
437
    }
438
439 53
    /**
440
     * @return \Eccube\Service\CartService
441 53
     */
442 53
    public function clear()
443 53
    {
444 53
        $Carts = $this->getCarts();
445 53
        if (!empty($Carts)) {
446 53
            $removed = $this->getCart();
447 53
            if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
448
                $this->entityManager->remove($removed);
449
                $this->entityManager->flush($removed);
450 132
451
                $cartKeys = [];
452 132
                foreach ($Carts as $key => $Cart) {
453 10
                    // テーブルから削除されたカートを除外する
454
                    if ($Cart == $removed) {
455
                        unset($Carts[$key]);
456 122
                    }
457
                    $cartKeys[] = $Cart->getCartKey();
458 57
                }
459
                $this->session->set('cart_keys', $cartKeys);
460
                // 注文完了のカートキーをセッションから削除する
461 65
                $this->session->remove('cart_key');
462
                $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'DESC']);
463
            }
464
        }
465
466
        return $this;
467 86
    }
468
469 86
    /**
470 51
     * @param CartItemComparator $cartItemComparator
471
     */
472
    public function setCartItemComparator($cartItemComparator)
473 35
    {
474 17
        $this->cartItemComparator = $cartItemComparator;
475
    }
476
477
    /**
478 35
     * カートキーで指定したインデックスにあるカートを優先にする
479 35
     *
480 35
     * @param string $cartKey カートキー
481 35
     */
482
    public function setPrimary($cartKey)
483 35
    {
484
        $Carts = $this->getCarts();
485 35
        $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