src/Eccube/Service/CartService.php line 174

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of EC-CUBE
  4. *
  5. * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6. *
  7. * http://www.ec-cube.co.jp/
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. namespace Eccube\Service;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Doctrine\ORM\UnitOfWork;
  15. use Eccube\Entity\Cart;
  16. use Eccube\Entity\CartItem;
  17. use Eccube\Entity\Customer;
  18. use Eccube\Entity\ItemHolderInterface;
  19. use Eccube\Entity\ProductClass;
  20. use Eccube\Repository\CartRepository;
  21. use Eccube\Repository\OrderRepository;
  22. use Eccube\Repository\ProductClassRepository;
  23. use Eccube\Service\Cart\CartItemAllocator;
  24. use Eccube\Service\Cart\CartItemComparator;
  25. use Eccube\Util\StringUtil;
  26. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  27. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  28. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  29. class CartService
  30. {
  31. /**
  32. * @var Cart[]
  33. */
  34. protected $carts;
  35. /**
  36. * @var SessionInterface
  37. */
  38. protected $session;
  39. /**
  40. * @var \Doctrine\ORM\EntityManagerInterface
  41. */
  42. protected $entityManager;
  43. /**
  44. * @var ItemHolderInterface
  45. *
  46. * @deprecated
  47. */
  48. protected $cart;
  49. /**
  50. * @var ProductClassRepository
  51. */
  52. protected $productClassRepository;
  53. /**
  54. * @var CartRepository
  55. */
  56. protected $cartRepository;
  57. /**
  58. * @var CartItemComparator
  59. */
  60. protected $cartItemComparator;
  61. /**
  62. * @var CartItemAllocator
  63. */
  64. protected $cartItemAllocator;
  65. /**
  66. * @var OrderRepository
  67. */
  68. protected $orderRepository;
  69. /**
  70. * @var TokenStorageInterface
  71. */
  72. protected $tokenStorage;
  73. /**
  74. * @var AuthorizationCheckerInterface
  75. */
  76. protected $authorizationChecker;
  77. /**
  78. * CartService constructor.
  79. */
  80. public function __construct(
  81. SessionInterface $session,
  82. EntityManagerInterface $entityManager,
  83. ProductClassRepository $productClassRepository,
  84. CartRepository $cartRepository,
  85. CartItemComparator $cartItemComparator,
  86. CartItemAllocator $cartItemAllocator,
  87. OrderRepository $orderRepository,
  88. TokenStorageInterface $tokenStorage,
  89. AuthorizationCheckerInterface $authorizationChecker
  90. ) {
  91. $this->session = $session;
  92. $this->entityManager = $entityManager;
  93. $this->productClassRepository = $productClassRepository;
  94. $this->cartRepository = $cartRepository;
  95. $this->cartItemComparator = $cartItemComparator;
  96. $this->cartItemAllocator = $cartItemAllocator;
  97. $this->orderRepository = $orderRepository;
  98. $this->tokenStorage = $tokenStorage;
  99. $this->authorizationChecker = $authorizationChecker;
  100. }
  101. /**
  102. * 現在のカートの配列を取得する.
  103. *
  104. * 本サービスのインスタンスのメンバーが空の場合は、DBまたはセッションからカートを取得する
  105. *
  106. * @param bool $empty_delete true の場合、商品明細が空のカートが存在した場合は削除する
  107. *
  108. * @return Cart[]
  109. */
  110. public function getCarts($empty_delete = false)
  111. {
  112. if (null !== $this->carts) {
  113. if ($empty_delete) {
  114. $cartKeys = [];
  115. foreach (array_keys($this->carts) as $index) {
  116. $Cart = $this->carts[$index];
  117. if ($Cart->getItems()->count() > 0) {
  118. $cartKeys[] = $Cart->getCartKey();
  119. } else {
  120. $this->entityManager->remove($this->carts[$index]);
  121. $this->entityManager->flush();
  122. unset($this->carts[$index]);
  123. }
  124. }
  125. $this->session->set('cart_keys', $cartKeys);
  126. }
  127. return $this->carts;
  128. }
  129. if ($this->getUser()) {
  130. $this->carts = $this->getPersistedCarts();
  131. } else {
  132. $this->carts = $this->getSessionCarts();
  133. }
  134. return $this->carts;
  135. }
  136. /**
  137. * 永続化されたカートを返す
  138. *
  139. * @return Cart[]
  140. */
  141. public function getPersistedCarts()
  142. {
  143. return $this->cartRepository->findBy(['Customer' => $this->getUser()]);
  144. }
  145. /**
  146. * セッションにあるカートを返す
  147. *
  148. * @return Cart[]
  149. */
  150. public function getSessionCarts()
  151. {
  152. $cartKeys = $this->session->get('cart_keys', []);
  153. if (empty($cartKeys)) {
  154. return [];
  155. }
  156. return $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'ASC']);
  157. }
  158. /**
  159. * 会員が保持する永続化されたカートと、非会員時のカートをマージする.
  160. */
  161. public function mergeFromPersistedCart()
  162. {
  163. $persistedCarts = $this->getPersistedCarts();
  164. $sessionCarts = $this->getSessionCarts();
  165. $CartItems = [];
  166. // 永続化されたカートとセッションのカートが同一の場合はマージしない #4574
  167. $cartKeys = $this->session->get('cart_keys', []);
  168. if ((count($persistedCarts) > 0) && !in_array($persistedCarts[0]->getCartKey(), $cartKeys, true)) {
  169. foreach ($persistedCarts as $Cart) {
  170. $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
  171. }
  172. }
  173. // セッションにある非会員カートとDBから取得した会員カートをマージする.
  174. foreach ($sessionCarts as $Cart) {
  175. $CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
  176. }
  177. $this->restoreCarts($CartItems);
  178. }
  179. /**
  180. * @return Cart|null
  181. */
  182. public function getCart()
  183. {
  184. $Carts = $this->getCarts();
  185. if (empty($Carts)) {
  186. return null;
  187. }
  188. $cartKeys = $this->session->get('cart_keys', []);
  189. $Cart = null;
  190. if (count($cartKeys) > 0) {
  191. foreach ($Carts as $cart) {
  192. if ($cart->getCartKey() === current($cartKeys)) {
  193. $Cart = $cart;
  194. break;
  195. }
  196. }
  197. } else {
  198. $Cart = $Carts[0];
  199. }
  200. return $Cart;
  201. }
  202. /**
  203. * @param CartItem[] $cartItems
  204. *
  205. * @return CartItem[]
  206. */
  207. protected function mergeAllCartItems($cartItems = [])
  208. {
  209. /** @var CartItem[] $allCartItems */
  210. $allCartItems = [];
  211. foreach ($this->getCarts() as $Cart) {
  212. $allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
  213. }
  214. return $this->mergeCartItems($cartItems, $allCartItems);
  215. }
  216. /**
  217. * @param $cartItems
  218. * @param $allCartItems
  219. *
  220. * @return array
  221. */
  222. protected function mergeCartItems($cartItems, $allCartItems)
  223. {
  224. foreach ($cartItems as $item) {
  225. $itemExists = false;
  226. foreach ($allCartItems as $itemInArray) {
  227. // 同じ明細があればマージする
  228. if ($this->cartItemComparator->compare($item, $itemInArray)) {
  229. $itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
  230. $itemExists = true;
  231. break;
  232. }
  233. }
  234. if (!$itemExists) {
  235. $allCartItems[] = $item;
  236. }
  237. }
  238. return $allCartItems;
  239. }
  240. protected function restoreCarts($cartItems)
  241. {
  242. foreach ($this->getCarts() as $Cart) {
  243. foreach ($Cart->getCartItems() as $i) {
  244. $this->entityManager->remove($i);
  245. $this->entityManager->flush();
  246. }
  247. $this->entityManager->remove($Cart);
  248. $this->entityManager->flush();
  249. }
  250. $this->carts = [];
  251. /** @var Cart[] $Carts */
  252. $Carts = [];
  253. foreach ($cartItems as $item) {
  254. $allocatedId = $this->cartItemAllocator->allocate($item);
  255. $cartKey = $this->createCartKey($allocatedId, $this->getUser());
  256. if (isset($Carts[$cartKey])) {
  257. $Cart = $Carts[$cartKey];
  258. $Cart->addCartItem($item);
  259. $item->setCart($Cart);
  260. } else {
  261. /** @var Cart $Cart */
  262. $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
  263. if ($Cart) {
  264. foreach ($Cart->getCartItems() as $i) {
  265. $this->entityManager->remove($i);
  266. $this->entityManager->flush();
  267. }
  268. $this->entityManager->remove($Cart);
  269. $this->entityManager->flush();
  270. }
  271. $Cart = new Cart();
  272. $Cart->setCartKey($cartKey);
  273. $Cart->addCartItem($item);
  274. $item->setCart($Cart);
  275. $Carts[$cartKey] = $Cart;
  276. }
  277. }
  278. $this->carts = array_values($Carts);
  279. }
  280. /**
  281. * カートに商品を追加します.
  282. *
  283. * @param $ProductClass ProductClass 商品規格
  284. * @param $quantity int 数量
  285. *
  286. * @return bool 商品を追加できた場合はtrue
  287. */
  288. public function addProduct($ProductClass, $quantity = 1)
  289. {
  290. if (!$ProductClass instanceof ProductClass) {
  291. $ProductClassId = $ProductClass;
  292. $ProductClass = $this->entityManager
  293. ->getRepository(ProductClass::class)
  294. ->find($ProductClassId);
  295. if (is_null($ProductClass)) {
  296. return false;
  297. }
  298. }
  299. $ClassCategory1 = $ProductClass->getClassCategory1();
  300. if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
  301. return false;
  302. }
  303. $ClassCategory2 = $ProductClass->getClassCategory2();
  304. if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
  305. return false;
  306. }
  307. $newItem = new CartItem();
  308. $newItem->setQuantity($quantity);
  309. $newItem->setPrice($ProductClass->getPrice02IncTax());
  310. $newItem->setProductClass($ProductClass);
  311. $allCartItems = $this->mergeAllCartItems([$newItem]);
  312. $this->restoreCarts($allCartItems);
  313. return true;
  314. }
  315. public function removeProduct($ProductClass)
  316. {
  317. if (!$ProductClass instanceof ProductClass) {
  318. $ProductClassId = $ProductClass;
  319. $ProductClass = $this->entityManager
  320. ->getRepository(ProductClass::class)
  321. ->find($ProductClassId);
  322. if (is_null($ProductClass)) {
  323. return false;
  324. }
  325. }
  326. $removeItem = new CartItem();
  327. $removeItem->setPrice($ProductClass->getPrice02IncTax());
  328. $removeItem->setProductClass($ProductClass);
  329. $allCartItems = $this->mergeAllCartItems();
  330. $foundIndex = -1;
  331. foreach ($allCartItems as $index => $itemInCart) {
  332. if ($this->cartItemComparator->compare($itemInCart, $removeItem)) {
  333. $foundIndex = $index;
  334. break;
  335. }
  336. }
  337. array_splice($allCartItems, $foundIndex, 1);
  338. $this->restoreCarts($allCartItems);
  339. return true;
  340. }
  341. public function save()
  342. {
  343. $cartKeys = [];
  344. foreach ($this->carts as $Cart) {
  345. $Cart->setCustomer($this->getUser());
  346. $this->entityManager->persist($Cart);
  347. foreach ($Cart->getCartItems() as $item) {
  348. $this->entityManager->persist($item);
  349. }
  350. $this->entityManager->flush();
  351. $cartKeys[] = $Cart->getCartKey();
  352. }
  353. $this->session->set('cart_keys', $cartKeys);
  354. return;
  355. }
  356. /**
  357. * @param string $pre_order_id
  358. *
  359. * @return \Eccube\Service\CartService
  360. */
  361. public function setPreOrderId($pre_order_id)
  362. {
  363. $this->getCart()->setPreOrderId($pre_order_id);
  364. return $this;
  365. }
  366. /**
  367. * @return string|null
  368. */
  369. public function getPreOrderId()
  370. {
  371. $Cart = $this->getCart();
  372. if (!empty($Cart)) {
  373. return $Cart->getPreOrderId();
  374. }
  375. return null;
  376. }
  377. /**
  378. * @return \Eccube\Service\CartService
  379. */
  380. public function clear()
  381. {
  382. $Carts = $this->getCarts();
  383. if (!empty($Carts)) {
  384. $removed = $this->getCart();
  385. if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
  386. $this->entityManager->remove($removed);
  387. $this->entityManager->flush();
  388. $cartKeys = [];
  389. foreach ($Carts as $key => $Cart) {
  390. // テーブルから削除されたカートを除外する
  391. if ($Cart == $removed) {
  392. unset($Carts[$key]);
  393. }
  394. $cartKeys[] = $Cart->getCartKey();
  395. }
  396. $this->session->set('cart_keys', $cartKeys);
  397. // 注文完了のカートキーをセッションから削除する
  398. $this->session->remove('cart_key');
  399. $this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'ASC']);
  400. }
  401. }
  402. return $this;
  403. }
  404. /**
  405. * @param CartItemComparator $cartItemComparator
  406. */
  407. public function setCartItemComparator($cartItemComparator)
  408. {
  409. $this->cartItemComparator = $cartItemComparator;
  410. }
  411. /**
  412. * カートキーで指定したインデックスにあるカートを優先にする
  413. *
  414. * @param string $cartKey カートキー
  415. */
  416. public function setPrimary($cartKey)
  417. {
  418. $Carts = $this->getCarts();
  419. $primary = $Carts[0];
  420. $index = 0;
  421. foreach ($Carts as $key => $Cart) {
  422. if ($Cart->getCartKey() === $cartKey) {
  423. $index = $key;
  424. $primary = $Carts[$index];
  425. break;
  426. }
  427. }
  428. $prev = $Carts[0];
  429. array_splice($Carts, 0, 1, [$primary]);
  430. array_splice($Carts, $index, 1, [$prev]);
  431. $this->carts = $Carts;
  432. $this->save();
  433. }
  434. protected function getUser()
  435. {
  436. if (null === $token = $this->tokenStorage->getToken()) {
  437. return;
  438. }
  439. if (!is_object($user = $token->getUser())) {
  440. // e.g. anonymous authentication
  441. return;
  442. }
  443. return $user;
  444. }
  445. /**
  446. * @param string $allocatedId
  447. */
  448. protected function createCartKey($allocatedId, Customer $Customer = null)
  449. {
  450. if ($Customer instanceof Customer) {
  451. return $Customer->getId().'_'.$allocatedId;
  452. }
  453. if ($this->session->has('cart_key_prefix')) {
  454. return $this->session->get('cart_key_prefix').'_'.$allocatedId;
  455. }
  456. do {
  457. $random = StringUtil::random(32);
  458. $cartKey = $random.'_'.$allocatedId;
  459. $Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
  460. } while ($Cart);
  461. $this->session->set('cart_key_prefix', $random);
  462. return $cartKey;
  463. }
  464. }