CartService   C
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 549
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Test Coverage

Coverage 95.28%

Importance

Changes 0
Metric Value
dl 0
loc 549
ccs 202
cts 212
cp 0.9528
rs 5.5244
c 0
b 0
f 0
wmc 74
lcom 2
cbo 7

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 2
A loadProductClassFromCart() 0 15 2
A loadProductClassFromCartItem() 0 13 3
A setCanAddProductType() 0 8 2
A save() 0 4 1
A unlock() 0 6 1
A lock() 0 6 1
A isLocked() 0 4 1
A setPreOrderId() 0 6 1
A getPreOrderId() 0 4 1
A clear() 0 9 1
A getCanAddProductType() 0 4 1
A addProduct() 0 7 1
A getProductQuantity() 0 9 2
F setProductQuantity() 0 101 21
A canAddProduct() 0 15 2
B canAddProductPayment() 0 36 6
D getCart() 0 38 10
B removeProduct() 0 26 3
A addError() 0 9 2
A upProductQuantity() 0 7 1
A downProductQuantity() 0 12 2
A getProductTypes() 0 12 2
A getErrors() 0 4 1
A getMessages() 0 4 1
A setMessage() 0 6 1
A getError() 0 4 1
A setError() 0 6 1

How to fix   Complexity   

Complex Class

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
 * This file is part of EC-CUBE
4
 *
5
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
6
 *
7
 * http://www.lockon.co.jp/
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
 */
23
24
25
namespace Eccube\Service;
26
27
use Doctrine\ORM\EntityManager;
28
use Eccube\Common\Constant;
29
use Eccube\Entity\CartItem;
30
use Eccube\Entity\Master\Disp;
31
use Eccube\Entity\ProductClass;
32
use Eccube\Exception\CartException;
33
use Symfony\Component\HttpFoundation\Session\Session;
34
35
class CartService
0 ignored issues
show
introduced by
Missing class doc comment
Loading history...
36
{
37
    /** @var \Eccube\Application */
38
    public $app;
39
40
    /**
41
     * @var Session
42
     */
43
    private $session;
44
45
    /**
46
     * @var EntityManager
47
     */
48
    private $entityManager;
49
50
    /**
51
     * @var \Eccube\Entity\Cart
52
     */
53
    private $cart;
54
55
    /**
56
     * @var \Eccube\Entity\BaseInfo
57
     */
58
    private $BaseInfo;
59
60
    /**
61
     * @var array
62
     */
63
    private $errors = array();
64
65
    private $ProductType = null;
66
67
    /**
68
     * @var array
69
     */
70
    private $messages = array();
71
72
    /**
73
     * @var array
74
     */
75
    private $error;
76
77 182
    public function __construct(\Eccube\Application $app)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
78
    {
79 182
        $this->app = $app;
80 182
        $this->session = $app['session'];
81 182
        $this->entityManager = $app['orm.em'];
82
83 182
        if ($this->session->has('cart')) {
84
            $this->cart = $this->session->get('cart');
85
        } else {
86 182
            $this->cart = new \Eccube\Entity\Cart();
87
        }
88
89 182
        $this->loadProductClassFromCart();
90
91 182
        $this->BaseInfo = $app['eccube.repository.base_info']->get();
92
    }
93
94
    /**
95
     * カートに保存されている商品の ProductClass エンティティを読み込み、カートへ設定します。
96
     */
97 182
    protected function loadProductClassFromCart()
98
    {
99
        /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */
100 182
        $softDeleteFilter = $this->entityManager->getFilters()->getFilter('soft_delete');
101 182
        $excludes = $softDeleteFilter->getExcludes();
102 182
        $softDeleteFilter->setExcludes(array(
103 182
            'Eccube\Entity\ProductClass',
104
        ));
105
106 182
        foreach ($this->cart->getCartItems() as $CartItem) {
107 182
            $this->loadProductClassFromCartItem($CartItem);
108
        }
109
110 182
        $softDeleteFilter->setExcludes($excludes);
111
    }
112
113
    /**
114
     * CartItem に対応する ProductClass を設定します。
115
     *
116
     * @param CartItem $CartItem
117
     */
118 48
    protected function loadProductClassFromCartItem(CartItem $CartItem)
119
    {
120
        $ProductClass = $this
121 48
            ->entityManager
122 48
            ->getRepository($CartItem->getClassName())
123 48
            ->find($CartItem->getClassId());
124
125 48
        $CartItem->setObject($ProductClass);
126
127 48
        if (is_null($this->ProductType) && $ProductClass->getDelFlg() == Constant::DISABLED) {
128
            $this->setCanAddProductType($ProductClass->getProductType());
129
        }
130
    }
131
132 88
    public function setCanAddProductType(\Eccube\Entity\Master\ProductType $ProductType)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
introduced by
Missing function doc comment
Loading history...
133
    {
134 88
        if (is_null($this->ProductType)) {
135 88
            $this->ProductType = $ProductType;
136
        }
137
138 88
        return $this;
139
    }
140
141 82
    public function save()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
142
    {
143 82
        return $this->session->set('cart', $this->cart);
144
    }
145
146 4
    public function unlock()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
147
    {
148 4
        $this->cart
149 4
            ->setLock(false)
150 4
            ->setPreOrderId(null);
151
    }
152
153 38
    public function lock()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
154
    {
155 38
        $this->cart
156 38
            ->setLock(true)
157 38
            ->setPreOrderId(null);
158
    }
159
160
    /**
161
     * @return bool
162
     */
163 47
    public function isLocked()
164
    {
165 47
        return $this->cart->getLock();
166
    }
167
168
    /**
169
     * @param  string $pre_order_id
170
     * @return \Eccube\Service\CartService
171
     */
172 41
    public function setPreOrderId($pre_order_id)
173
    {
174 41
        $this->cart->setPreOrderId($pre_order_id);
175
176 41
        return $this;
177
    }
178
179
    /**
180
     * @return string
181
     */
182 39
    public function getPreOrderId()
183
    {
184 39
        return $this->cart->getPreOrderId();
185
    }
186
187
    /**
188
     * @return \Eccube\Service\CartService
189
     */
190 13
    public function clear()
191
    {
192 13
        $this->cart
193 13
            ->setPreOrderId(null)
194 13
            ->setLock(false)
195 13
            ->clearCartItems();
196
197 13
        return $this;
198
    }
199
200 1
    public function getCanAddProductType()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
201
    {
202 1
        return $this->ProductType;
203
    }
204
205
    /**
206
     *
207
     * @param  string $productClassId
0 ignored issues
show
introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
208
     * @param  integer $quantity
209
     * @return \Eccube\Service\CartService
210
     */
211 44
    public function addProduct($productClassId, $quantity = 1)
212
    {
213 44
        $quantity += $this->getProductQuantity($productClassId);
214 44
        $this->setProductQuantity($productClassId, $quantity);
215
216 43
        return $this;
217
    }
218
219
    /**
220
     * @param  string $productClassId
221
     * @return integer
222
     */
223 53
    public function getProductQuantity($productClassId)
224
    {
225 53
        $CartItem = $this->cart->getCartItemByIdentifier('Eccube\Entity\ProductClass', (string) $productClassId);
226 53
        if ($CartItem) {
227 7
            return $CartItem->getQuantity();
228
        } else {
229 51
            return 0;
230
        }
231
    }
232
233
    /**
234
     * @param  \Eccube\Entity\ProductClass|integer $ProductClass
235
     * @param  integer $quantity
0 ignored issues
show
introduced by
Expected 29 spaces after parameter type; 1 found
Loading history...
236
     * @return \Eccube\Service\CartService
237
     * @throws CartException
238
     */
239 91
    public function setProductQuantity($ProductClass, $quantity)
240
    {
241 91
        if (!$ProductClass instanceof ProductClass) {
0 ignored issues
show
Bug introduced by
The class Eccube\Entity\ProductClass does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
242 86
            $ProductClass = $this->entityManager
243 86
                ->getRepository('Eccube\Entity\ProductClass')
244 86
                ->find($ProductClass);
245 86
            if (!$ProductClass) {
246 3
                throw new CartException('cart.product.delete');
247
            }
248
        }
249 88
        if ($ProductClass->getProduct()->getStatus()->getId() !== Disp::DISPLAY_SHOW) {
250 1
            $this->removeProduct($ProductClass->getId());
251 1
            throw new CartException('cart.product.not.status');
252
        }
253
254 87
        $productName = $ProductClass->getProduct()->getName();
255 87
        if ($ProductClass->hasClassCategory1()) {
256 87
            $productName .= " - ".$ProductClass->getClassCategory1()->getName();
257
        }
258 87
        if ($ProductClass->hasClassCategory2()) {
259 82
            $productName .= " - ".$ProductClass->getClassCategory2()->getName();
260
        }
261
262
        // 商品種別に紐づく配送業者を取得
263 87
        $deliveries = $this->app['eccube.repository.delivery']->getDeliveries($ProductClass->getProductType());
264
265 87
        if (count($deliveries) == 0) {
266
            // 商品種別が存在しなければエラー
267
            $this->removeProduct($ProductClass->getId());
268
            $this->addError('cart.product.not.producttype', $productName);
269
            throw new CartException('cart.product.not.producttype');
270
        }
271
272 87
        $this->setCanAddProductType($ProductClass->getProductType());
273
274 87
        if ($this->BaseInfo->getOptionMultipleShipping() != Constant::ENABLED) {
275 79
            if (!$this->canAddProduct($ProductClass->getId())) {
276
                // 複数配送対応でなければ商品種別が異なればエラー
277 79
                throw new CartException('cart.product.type.kind');
278
            }
279
        } else {
280
            // 複数配送の場合、同一支払方法がなければエラー
281 8
            if (!$this->canAddProductPayment($ProductClass->getProductType())) {
282 1
                throw new CartException('cart.product.payment.kind');
283
            }
284
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
285
        }
286
287 87
        $tmp_subtotal = 0;
288 87
        $tmp_quantity = 0;
289 87
        foreach ($this->getCart()->getCartItems() as $cartitem) {
290 9
            $pc = $cartitem->getObject();
291 9
            if ($pc->getId() != $ProductClass->getId()) {
292
                // まず、追加された商品以外のtotal priceをセット
293 87
                $tmp_subtotal += $cartitem->getTotalPrice();
294
            }
295
        }
296 87
        for ($i = 0; $i < $quantity; $i++) {
297 87
            $tmp_subtotal += $ProductClass->getPrice02IncTax();
298 87
            if ($tmp_subtotal > $this->app['config']['max_total_fee']) {
299 1
                $this->setError('cart.over.price_limit');
300 1
                break;
301
            }
302 86
            $tmp_quantity++;
303
        }
304 87
        $quantity = $tmp_quantity;
305
306 87
        $tmp_quantity = 0;
307
308
        /*
309
         * 実際の在庫は ProductClass::ProductStock だが、購入時にロックがかかるため、
310
         * ここでは ProductClass::stock で在庫のチェックをする
311
         */
312 87
        if (!$ProductClass->getStockUnlimited() && $quantity > $ProductClass->getStock()) {
313 3
            if ($ProductClass->getSaleLimit() && $ProductClass->getStock() > $ProductClass->getSaleLimit()) {
314 1
                $tmp_quantity = $ProductClass->getSaleLimit();
315 1
                $this->addError('cart.over.sale_limit', $productName);
316
            } else {
317 2
                $tmp_quantity = $ProductClass->getStock();
318 2
                $this->addError('cart.over.stock', $productName);
319
            }
320
        }
321 87
        if ($ProductClass->getSaleLimit() && $quantity > $ProductClass->getSaleLimit()) {
322 2
            $tmp_quantity = $ProductClass->getSaleLimit();
323 2
            $this->addError('cart.over.sale_limit', $productName);
324
        }
325 87
        if ($tmp_quantity) {
326 4
            $quantity = $tmp_quantity;
327
        }
328
329 87
        $CartItem = new CartItem();
330
        $CartItem
331 87
            ->setClassName('Eccube\Entity\ProductClass')
332 87
            ->setClassId((string) $ProductClass->getId())
333 87
            ->setPrice($ProductClass->getPrice02IncTax())
334 87
            ->setQuantity($quantity);
335
336 87
        $this->cart->setCartItem($CartItem);
337
338 87
        return $this;
339
    }
340
341
    /**
342
     * @param  string $productClassId
343
     * @return boolean
344
     */
345 79
    public function canAddProduct($productClassId)
346
    {
347
        $ProductClass = $this
348 79
            ->entityManager
349 79
            ->getRepository('\Eccube\Entity\ProductClass')
350 79
            ->find($productClassId);
351
352 79
        if (!$ProductClass) {
353
            return false;
354
        }
355
356 79
        $ProductType = $ProductClass->getProductType();
357
358 79
        return $this->ProductType == $ProductType;
359
    }
360
361
    /**
362
     * @param \Eccube\Entity\Master\ProductType $ProductType
363
     * @return bool
364
     */
365 9
    public function canAddProductPayment(\Eccube\Entity\Master\ProductType $ProductType)
366
    {
367
        $deliveries = $this
368 9
            ->entityManager
369 9
            ->getRepository('\Eccube\Entity\Delivery')
370 9
            ->findBy(array('ProductType' => $ProductType));
371
372
        // 支払方法を取得
373 9
        $payments = $this->entityManager->getRepository('Eccube\Entity\Payment')->findAllowedPayments($deliveries);
374
375 9
        if ($this->getCart()->getTotalPrice() < 1) {
376
            // カートになければ支払方法を全て設定
377 9
            $this->getCart()->setPayments($payments);
378 9
            return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
379
        }
380
381
        // カートに存在している支払方法と追加された商品の支払方法チェック
382 7
        $arr = array();
383 7
        foreach ($payments as $payment) {
384 7
            foreach ($this->getCart()->getPayments() as $p) {
385 7
                if ($payment['id'] == $p['id']) {
386 6
                    $arr[] = $payment;
387 7
                    break;
388
                }
389
            }
390
        }
391
392 7
        if (count($arr) > 0) {
393 6
            $this->getCart()->setPayments($arr);
394 6
            return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
395
        }
396
397
        // 支払条件に一致しない
398 1
        return false;
399
400
    }
401
402
    /**
403
     * カートを取得します。
404
     *
405
     * @return \Eccube\Entity\Cart
406
     */
407 159
    public function getCart()
408
    {
409 159
        foreach ($this->cart->getCartItems() as $CartItem) {
410 48
            $ProductClass = $CartItem->getObject();
411 48
            if (!$ProductClass) {
412 48
                $this->loadProductClassFromCartItem($CartItem);
413
414 48
                $ProductClass = $CartItem->getObject();
415
            }
416
417 48
            if ($ProductClass->getDelFlg() == Constant::DISABLED) {
418
                // 商品情報が有効
419 48
                $stockUnlimited = $ProductClass->getStockUnlimited();
420 48
                if ($stockUnlimited == Constant::DISABLED && $ProductClass->getStock() < 1) {
421
                    // 在庫がなければカートから削除
422
                    $this->setError('cart.zero.stock');
423
                    $this->removeProduct($ProductClass->getId());
424
                } else {
425 48
                    $quantity = $CartItem->getQuantity();
426 48
                    $saleLimit = $ProductClass->getSaleLimit();
427 48
                    if ($stockUnlimited == Constant::DISABLED && $ProductClass->getStock() < $quantity) {
428
                        // 購入数が在庫数を超えている場合、メッセージを表示
429
                        $this->setError('cart.over.stock');
430 48
                    } elseif (!is_null($saleLimit) && $saleLimit < $quantity) {
431
                        // 購入数が販売制限数を超えている場合、メッセージを表示
432 48
                        $this->setError('cart.over.sale_limit');
433
                    }
434
                }
435
            } else {
436
                // 商品情報が削除されていたらエラー
437
                $this->setError('cart.product.delete');
438
                // カートから削除
439 159
                $this->removeProduct($ProductClass->getId());
440
            }
441
        }
442
443 159
        return $this->cart;
444
    }
445
446
    /**
447
     * @param  string $productClassId
448
     * @return \Eccube\Service\CartService
449
     */
450 9
    public function removeProduct($productClassId)
451
    {
452 9
        $this->cart->removeCartItemByIdentifier('Eccube\Entity\ProductClass', (string) $productClassId);
453
454
        // 支払方法の再設定
455 9
        if ($this->BaseInfo->getOptionMultipleShipping() == Constant::ENABLED) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
456
457
            // 複数配送対応
458 1
            $productTypes = array();
459 1
            foreach ($this->getCart()->getCartItems() as $item) {
460
                /* @var $ProductClass \Eccube\Entity\ProductClass */
461 1
                $ProductClass = $item->getObject();
462 1
                $productTypes[] = $ProductClass->getProductType();
463
            }
464
465
            // 配送業者を取得
466 1
            $deliveries = $this->entityManager->getRepository('Eccube\Entity\Delivery')->getDeliveries($productTypes);
467
468
            // 支払方法を取得
469 1
            $payments = $this->entityManager->getRepository('Eccube\Entity\Payment')->findAllowedPayments($deliveries);
470
471 1
            $this->getCart()->setPayments($payments);
472
        }
473
474 9
        return $this;
475
    }
476
477
    /**
478
     * @param  string $error
479
     * @param  string $productName
480
     * @return \Eccube\Service\CartService
481
     */
482 5
    public function addError($error = null, $productName = null)
483
    {
484 5
        $this->errors[] = $error;
485 5
        $this->session->getFlashBag()->add('eccube.front.request.error', $error);
486 5
        if (!is_null($productName)) {
487 4
            $this->session->getFlashBag()->add('eccube.front.request.product', $productName);
488
        }
489 5
        return $this;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
490
    }
491
492
    /**
493
     * @param  string $productClassId
494
     * @return \Eccube\Service\CartService
495
     */
496 4
    public function upProductQuantity($productClassId)
497
    {
498 4
        $quantity = $this->getProductQuantity($productClassId) + 1;
499 4
        $this->setProductQuantity($productClassId, $quantity);
500
501 3
        return $this;
502
    }
503
504
    /**
505
     * @param  string $productClassId
506
     * @return \Eccube\Service\CartService
507
     */
508 5
    public function downProductQuantity($productClassId)
509
    {
510 5
        $quantity = $this->getProductQuantity($productClassId) - 1;
511
512 5
        if ($quantity > 0) {
513 1
            $this->setProductQuantity($productClassId, $quantity);
514
        } else {
515 4
            $this->removeProduct($productClassId);
516
        }
517
518 5
        return $this;
519
    }
520
521
    /**
522
     * @return array
523
     */
524 41
    public function getProductTypes()
525
    {
526
527 41
        $productTypes = array();
528 41
        foreach ($this->getCart()->getCartItems() as $item) {
529
            /* @var $ProductClass \Eccube\Entity\ProductClass */
530 41
            $ProductClass = $item->getObject();
531 41
            $productTypes[] = $ProductClass->getProductType();
532
        }
533 41
        return array_unique($productTypes);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
534
535
    }
536
537
    /**
538
     * @return string[]
539
     */
540 3
    public function getErrors()
541
    {
542 3
        return $this->errors;
543
    }
544
545
    /**
546
     * @return string[]
547
     */
548 1
    public function getMessages()
549
    {
550 1
        return $this->messages;
551
    }
552
553
    /**
554
     * @param  string $message
555
     * @return \Eccube\Service\CartService
556
     */
557 1
    public function setMessage($message)
558
    {
559 1
        $this->messages[] = $message;
560
561 1
        return $this;
562
    }
563
564
    /**
565
     * @return string
566
     */
567 1
    public function getError()
568
    {
569 1
        return $this->error;
570
    }
571
572
    /**
573
     * @param  string $error
574
     * @return \Eccube\Service\CartService
575
     */
576 1
    public function setError($error = null)
577
    {
578 1
        $this->error = $error;
0 ignored issues
show
Documentation Bug introduced by
It seems like $error of type string or null is incompatible with the declared type array of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
579 1
        $this->session->getFlashBag()->set('eccube.front.request.error', $error);
580 1
        return $this;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
581
    }
582
583
}
584