Failed Conditions
Push — sf/last-boss ( 53d963...58156b )
by Kiyotaka
09:54 queued 04:25
created

PurchaseFlow   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 353
Duplicated Lines 17.85 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 63
loc 353
rs 8.8798
c 0
b 0
f 0
wmc 44
lcom 1
cbo 7

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A setFlowType() 0 4 1
A setPurchaseProcessors() 0 4 1
A setItemValidators() 0 4 1
A setItemHolderValidators() 0 4 1
A setItemPreprocessors() 0 4 1
A setItemHolderPreprocessors() 0 4 1
A setItemHolderPostValidators() 0 4 1
A setDiscountProcessors() 0 4 1
F validate() 0 64 12
A prepare() 0 8 2
A commit() 0 8 2
A rollback() 0 8 2
A addPurchaseProcessor() 0 4 1
A addItemHolderPreprocessor() 0 4 1
A addItemPreprocessor() 0 4 1
A addItemValidator() 0 4 1
A addItemHolderValidator() 0 4 1
A addItemHolderPostValidator() 0 4 1
A addDiscountProcessor() 0 4 1
A calculateTotal() 14 14 2
A calculateSubTotal() 15 15 2
A calculateDeliveryFeeTotal() 11 11 1
A calculateDiscount() 12 12 1
A calculateCharge() 11 11 1
A calculateTax() 0 14 2
A calculateAll() 0 9 1

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 PurchaseFlow 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 PurchaseFlow, 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\PurchaseFlow;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Eccube\Entity\ItemHolderInterface;
18
use Eccube\Entity\ItemInterface;
19
use Eccube\Entity\Order;
20
use Eccube\Entity\OrderItem;
21
22
class PurchaseFlow
23
{
24
    /**
25
     * @var string
26
     */
27
    protected $flowType;
28
29
    /**
30
     * @var ArrayCollection|ItemPreprocessor[]
31
     */
32
    protected $itemPreprocessors;
33
34
    /**
35
     * @var ArrayCollection|ItemHolderPreprocessor[]
36
     */
37
    protected $itemHolderPreprocessors;
38
39
    /**
40
     * @var ArrayCollection|ItemValidator[]
41
     */
42
    protected $itemValidators;
43
44
    /**
45
     * @var ArrayCollection|ItemHolderValidator[]
46
     */
47
    protected $itemHolderValidators;
48
49
    /**
50
     * @var ArrayCollection|ItemHolderPostValidator[]
51
     */
52
    protected $itemHolderPostValidators;
53
54
    /**
55
     * @var ArrayCollection|DiscountProcessor[]
56
     */
57
    protected $discountProcessors;
58
59
    /**
60
     * @var ArrayCollection|PurchaseProcessor[]
61
     */
62
    protected $purchaseProcessors;
63
64
    public function __construct()
65
    {
66
        $this->purchaseProcessors = new ArrayCollection();
67
        $this->itemValidators = new ArrayCollection();
68
        $this->itemHolderValidators = new ArrayCollection();
69
        $this->itemPreprocessors = new ArrayCollection();
70
        $this->itemHolderPreprocessors = new ArrayCollection();
71
        $this->itemHolderPostValidators = new ArrayCollection();
72
        $this->discountProcessors = new ArrayCollection();
73
    }
74
75
    public function setFlowType($flowType)
76
    {
77
        $this->flowType = $flowType;
78
    }
79
80
    public function setPurchaseProcessors(ArrayCollection $processors)
81
    {
82
        $this->purchaseProcessors = $processors;
83
    }
84
85
    public function setItemValidators(ArrayCollection $itemValidators)
86
    {
87
        $this->itemValidators = $itemValidators;
88
    }
89
90
    public function setItemHolderValidators(ArrayCollection $itemHolderValidators)
91
    {
92
        $this->itemHolderValidators = $itemHolderValidators;
93
    }
94
95
    public function setItemPreprocessors(ArrayCollection $itemPreprocessors)
96
    {
97
        $this->itemPreprocessors = $itemPreprocessors;
98
    }
99
100
    public function setItemHolderPreprocessors(ArrayCollection $itemHolderPreprocessors)
101
    {
102
        $this->itemHolderPreprocessors = $itemHolderPreprocessors;
103
    }
104
105
    public function setItemHolderPostValidators(ArrayCollection $itemHolderPostValidators)
106
    {
107
        $this->itemHolderPostValidators = $itemHolderPostValidators;
108
    }
109
110
    public function setDiscountProcessors(ArrayCollection $discountProcessors)
111
    {
112
        $this->discountProcessors = $discountProcessors;
113
    }
114
115
    public function validate(ItemHolderInterface $itemHolder, PurchaseContext $context)
116
    {
117
        $context->setFlowType($this->flowType);
118
119
        $this->calculateAll($itemHolder);
120
121
        $flowResult = new PurchaseFlowResult($itemHolder);
122
123
        foreach ($itemHolder->getItems() as $item) {
124
            foreach ($this->itemValidators as $itemValidator) {
125
                $result = $itemValidator->execute($item, $context);
126
                $flowResult->addProcessResult($result);
127
            }
128
        }
129
130
        $this->calculateAll($itemHolder);
131
132
        foreach ($this->itemHolderValidators as $itemHolderValidator) {
133
            $result = $itemHolderValidator->execute($itemHolder, $context);
134
            $flowResult->addProcessResult($result);
135
        }
136
137
        $this->calculateAll($itemHolder);
138
139
        foreach ($itemHolder->getItems() as $item) {
140
            foreach ($this->itemPreprocessors as $itemPreprocessor) {
141
                $itemPreprocessor->process($item, $context);
142
            }
143
        }
144
145
        $this->calculateAll($itemHolder);
146
147
        foreach ($this->itemHolderPreprocessors as $holderPreprocessor) {
148
            $result = $holderPreprocessor->process($itemHolder, $context);
149
            if ($result) {
150
                $flowResult->addProcessResult($result);
151
            }
152
153
            $this->calculateAll($itemHolder);
154
        }
155
156
        foreach ($this->discountProcessors as $discountProcessor) {
157
            $discountProcessor->removeDiscountItem($itemHolder, $context);
158
        }
159
160
        $this->calculateAll($itemHolder);
161
162
        foreach ($this->discountProcessors as $discountProcessor) {
163
            $result = $discountProcessor->addDiscountItem($itemHolder, $context);
164
            if ($result) {
165
                $flowResult->addProcessResult($result);
166
            }
167
            $this->calculateAll($itemHolder);
168
        }
169
170
        foreach ($this->itemHolderPostValidators as $itemHolderPostValidator) {
171
            $result = $itemHolderPostValidator->execute($itemHolder, $context);
172
            $flowResult->addProcessResult($result);
173
174
            $this->calculateAll($itemHolder);
175
        }
176
177
        return $flowResult;
178
    }
179
180
    /**
181
     * 購入フロー仮確定処理.
182
     *
183
     * @param ItemHolderInterface $target
184
     * @param PurchaseContext $context
185
     *
186
     * @throws PurchaseException
187
     */
188
    public function prepare(ItemHolderInterface $target, PurchaseContext $context)
189
    {
190
        $context->setFlowType($this->flowType);
191
192
        foreach ($this->purchaseProcessors as $processor) {
193
            $processor->prepare($target, $context);
194
        }
195
    }
196
197
    /**
198
     * 購入フロー確定処理.
199
     *
200
     * @param ItemHolderInterface $target
201
     * @param PurchaseContext $context
202
     *
203
     * @throws PurchaseException
204
     */
205
    public function commit(ItemHolderInterface $target, PurchaseContext $context)
206
    {
207
        $context->setFlowType($this->flowType);
208
209
        foreach ($this->purchaseProcessors as $processor) {
210
            $processor->commit($target, $context);
211
        }
212
    }
213
214
    /**
215
     * 購入フロー仮確定取り消し処理.
216
     *
217
     * @param ItemHolderInterface $target
218
     * @param PurchaseContext $context
219
     */
220
    public function rollback(ItemHolderInterface $target, PurchaseContext $context)
221
    {
222
        $context->setFlowType($this->flowType);
223
224
        foreach ($this->purchaseProcessors as $processor) {
225
            $processor->rollback($target, $context);
226
        }
227
    }
228
229
    public function addPurchaseProcessor(PurchaseProcessor $processor)
230
    {
231
        $this->purchaseProcessors[] = $processor;
232
    }
233
234
    public function addItemHolderPreprocessor(ItemHolderPreprocessor $holderPreprocessor)
235
    {
236
        $this->itemHolderPreprocessors[] = $holderPreprocessor;
237
    }
238
239
    public function addItemPreprocessor(ItemPreprocessor $itemPreprocessor)
240
    {
241
        $this->itemPreprocessors[] = $itemPreprocessor;
242
    }
243
244
    public function addItemValidator(ItemValidator $itemValidator)
245
    {
246
        $this->itemValidators[] = $itemValidator;
247
    }
248
249
    public function addItemHolderValidator(ItemHolderValidator $itemHolderValidator)
250
    {
251
        $this->itemHolderValidators[] = $itemHolderValidator;
252
    }
253
254
    public function addItemHolderPostValidator(ItemHolderPostValidator $itemHolderValidator)
255
    {
256
        $this->itemHolderPostValidators[] = $itemHolderValidator;
257
    }
258
259
    public function addDiscountProcessor(DiscountProcessor $discountProcessor)
260
    {
261
        $this->discountProcessors[] = $discountProcessor;
262
    }
263
264
    /**
265
     * @param ItemHolderInterface $itemHolder
266
     */
267 View Code Duplication
    protected function calculateTotal(ItemHolderInterface $itemHolder)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
268
    {
269
        $total = array_reduce($itemHolder->getItems()->toArray(), function ($sum, ItemInterface $item) {
0 ignored issues
show
Bug introduced by
The method toArray cannot be called on $itemHolder->getItems() (of type array<integer,object<Ecc...\Entity\ItemInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
270
            $sum += $item->getPriceIncTax() * $item->getQuantity();
0 ignored issues
show
Bug introduced by
The method getPriceIncTax() does not exist on Eccube\Entity\ItemInterface. Did you maybe mean getPrice()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
271
272
            return $sum;
273
        }, 0);
274
        $itemHolder->setTotal($total);
275
        // TODO
276
        if ($itemHolder instanceof Order) {
277
            // Order には PaymentTotal もセットする
278
            $itemHolder->setPaymentTotal($total);
279
        }
280
    }
281
282 View Code Duplication
    protected function calculateSubTotal(ItemHolderInterface $itemHolder)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
283
    {
284
        $total = $itemHolder->getItems()
0 ignored issues
show
Bug introduced by
The method getProductClasses cannot be called on $itemHolder->getItems() (of type array<integer,object<Ecc...\Entity\ItemInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
285
            ->getProductClasses()
286
            ->reduce(function ($sum, ItemInterface $item) {
287
                $sum += $item->getPriceIncTax() * $item->getQuantity();
0 ignored issues
show
Bug introduced by
The method getPriceIncTax() does not exist on Eccube\Entity\ItemInterface. Did you maybe mean getPrice()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
288
289
                return $sum;
290
            }, 0);
291
        // TODO
292
        if ($itemHolder instanceof Order) {
293
            // Order の場合は SubTotal をセットする
294
            $itemHolder->setSubTotal($total);
295
        }
296
    }
297
298
    /**
299
     * @param ItemHolderInterface $itemHolder
300
     */
301 View Code Duplication
    protected function calculateDeliveryFeeTotal(ItemHolderInterface $itemHolder)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
302
    {
303
        $total = $itemHolder->getItems()
0 ignored issues
show
Bug introduced by
The method getDeliveryFees cannot be called on $itemHolder->getItems() (of type array<integer,object<Ecc...\Entity\ItemInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
304
            ->getDeliveryFees()
305
            ->reduce(function ($sum, ItemInterface $item) {
306
                $sum += $item->getPriceIncTax() * $item->getQuantity();
0 ignored issues
show
Bug introduced by
The method getPriceIncTax() does not exist on Eccube\Entity\ItemInterface. Did you maybe mean getPrice()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
307
308
                return $sum;
309
            }, 0);
310
        $itemHolder->setDeliveryFeeTotal($total);
311
    }
312
313
    /**
314
     * @param ItemHolderInterface $itemHolder
315
     */
316 View Code Duplication
    protected function calculateDiscount(ItemHolderInterface $itemHolder)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
317
    {
318
        $total = $itemHolder->getItems()
0 ignored issues
show
Bug introduced by
The method getDiscounts cannot be called on $itemHolder->getItems() (of type array<integer,object<Ecc...\Entity\ItemInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
319
            ->getDiscounts()
320
            ->reduce(function ($sum, ItemInterface $item) {
321
                $sum += $item->getPriceIncTax() * $item->getQuantity();
0 ignored issues
show
Bug introduced by
The method getPriceIncTax() does not exist on Eccube\Entity\ItemInterface. Did you maybe mean getPrice()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
322
323
                return $sum;
324
            }, 0);
325
        // TODO 後方互換のため discount には正の整数を代入する
326
        $itemHolder->setDiscount($total * -1);
327
    }
328
329
    /**
330
     * @param ItemHolderInterface $itemHolder
331
     */
332 View Code Duplication
    protected function calculateCharge(ItemHolderInterface $itemHolder)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
333
    {
334
        $total = $itemHolder->getItems()
0 ignored issues
show
Bug introduced by
The method getCharges cannot be called on $itemHolder->getItems() (of type array<integer,object<Ecc...\Entity\ItemInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
335
            ->getCharges()
336
            ->reduce(function ($sum, ItemInterface $item) {
337
                $sum += $item->getPriceIncTax() * $item->getQuantity();
0 ignored issues
show
Bug introduced by
The method getPriceIncTax() does not exist on Eccube\Entity\ItemInterface. Did you maybe mean getPrice()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
338
339
                return $sum;
340
            }, 0);
341
        $itemHolder->setCharge($total);
342
    }
343
344
    /**
345
     * @param ItemHolderInterface $itemHolder
346
     */
347
    protected function calculateTax(ItemHolderInterface $itemHolder)
348
    {
349
        $total = $itemHolder->getItems()
0 ignored issues
show
Bug introduced by
The method reduce cannot be called on $itemHolder->getItems() (of type array<integer,object<Ecc...\Entity\ItemInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
350
            ->reduce(function ($sum, ItemInterface $item) {
351
                if ($item instanceof OrderItem) {
352
                    $sum += $item->getTax() * $item->getQuantity();
353
                } else {
354
                    $sum += ($item->getPriceIncTax() - $item->getPrice()) * $item->getQuantity();
0 ignored issues
show
Bug introduced by
The method getPriceIncTax() does not exist on Eccube\Entity\ItemInterface. Did you maybe mean getPrice()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
355
                }
356
357
                return $sum;
358
            }, 0);
359
        $itemHolder->setTax($total);
360
    }
361
362
    /**
363
     * @param ItemHolderInterface $itemHolder
364
     */
365
    protected function calculateAll(ItemHolderInterface $itemHolder)
366
    {
367
        $this->calculateDeliveryFeeTotal($itemHolder);
368
        $this->calculateCharge($itemHolder);
369
        $this->calculateDiscount($itemHolder);
370
        $this->calculateSubTotal($itemHolder); // Order の場合のみ
371
        $this->calculateTax($itemHolder);
372
        $this->calculateTotal($itemHolder);
373
    }
374
}
375