Completed
Push — 4.0 ( b06cd9...9f28ed )
by
unknown
04:55 queued 10s
created

PurchaseFlow   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 397
Duplicated Lines 12.34 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 95.5%

Importance

Changes 0
Metric Value
dl 49
loc 397
ccs 106
cts 111
cp 0.955
rs 8.5599
c 0
b 0
f 0
wmc 48
lcom 1
cbo 7

29 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 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
A calculateTotal() 0 14 2
A dump() 0 30 3
A __toString() 0 4 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) 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\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 759
    /**
50
     * @var ArrayCollection|ItemHolderPostValidator[]
51 759
     */
52 759
    protected $itemHolderPostValidators;
53 759
54 759
    /**
55 759
     * @var ArrayCollection|DiscountProcessor[]
56
     */
57
    protected $discountProcessors;
58 728
59
    /**
60 728
     * @var ArrayCollection|PurchaseProcessor[]
61
     */
62
    protected $purchaseProcessors;
63 753
64
    public function __construct()
65 753
    {
66
        $this->purchaseProcessors = new ArrayCollection();
67
        $this->itemValidators = new ArrayCollection();
68 753
        $this->itemHolderValidators = new ArrayCollection();
69
        $this->itemPreprocessors = new ArrayCollection();
70 753
        $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 753
    }
79
80 753
    public function setPurchaseProcessors(ArrayCollection $processors)
81
    {
82
        $this->purchaseProcessors = $processors;
83 295
    }
84
85 295
    public function setItemValidators(ArrayCollection $itemValidators)
86
    {
87 295
        $this->itemValidators = $itemValidators;
88
    }
89 295
90 291
    public function setItemHolderValidators(ArrayCollection $itemHolderValidators)
91 291
    {
92
        $this->itemHolderValidators = $itemHolderValidators;
93
    }
94
95 295
    public function setItemPreprocessors(ArrayCollection $itemPreprocessors)
96
    {
97 295
        $this->itemPreprocessors = $itemPreprocessors;
98 259
    }
99
100
    public function setItemHolderPreprocessors(ArrayCollection $itemHolderPreprocessors)
101 295
    {
102
        $this->itemHolderPreprocessors = $itemHolderPreprocessors;
103 295
    }
104 291
105 81
    public function setItemHolderPostValidators(ArrayCollection $itemHolderPostValidators)
106 291
    {
107
        $this->itemHolderPostValidators = $itemHolderPostValidators;
108
    }
109
110 295
    public function setDiscountProcessors(ArrayCollection $discountProcessors)
111
    {
112 295
        $this->discountProcessors = $discountProcessors;
113 266
    }
114 266
115
    public function validate(ItemHolderInterface $itemHolder, PurchaseContext $context)
116
    {
117 295
        $context->setFlowType($this->flowType);
118
119 295
        $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 34
        $this->calculateAll($itemHolder);
131
132 34
        foreach ($this->itemHolderValidators as $itemHolderValidator) {
133 27
            $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 26
        $this->calculateAll($itemHolder);
146
147 26
        foreach ($this->itemHolderPreprocessors as $holderPreprocessor) {
148 26
            $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 16
                $flowResult->addProcessResult($result);
166
            }
167 16
            $this->calculateAll($itemHolder);
168
        }
169
170 26
        foreach ($this->itemHolderPostValidators as $itemHolderPostValidator) {
171
            $result = $itemHolderPostValidator->execute($itemHolder, $context);
172 26
            $flowResult->addProcessResult($result);
173
174
            $this->calculateAll($itemHolder);
175 2
        }
176
177 2
        return $flowResult;
178
    }
179
180 3
    /**
181
     * 購入フロー仮確定処理.
182 3
     *
183
     * @param ItemHolderInterface $target
184
     * @param PurchaseContext $context
185 36
     *
186
     * @throws PurchaseException
187 36
     */
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 295
    }
196 291
197
    /**
198 291
     * 購入フロー確定処理.
199 295
     *
200 295
     * @param ItemHolderInterface $target
201
     * @param PurchaseContext $context
202 295
     *
203
     * @throws PurchaseException
204 264
     */
205
    public function commit(ItemHolderInterface $target, PurchaseContext $context)
206
    {
207
        $context->setFlowType($this->flowType);
208 295
209
        foreach ($this->purchaseProcessors as $processor) {
210 295
            $processor->commit($target, $context);
211 295
        }
212 295
    }
213 234
214
    /**
215 234
     * 購入フロー仮確定取り消し処理.
216 295
     *
217
     * @param ItemHolderInterface $target
218 295
     * @param PurchaseContext $context
219
     */
220 264
    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 295
    }
228
229 295
    public function addPurchaseProcessor(PurchaseProcessor $processor)
230 295
    {
231 295
        $this->purchaseProcessors[] = $processor;
232 205
    }
233
234 205
    public function addItemHolderPreprocessor(ItemHolderPreprocessor $holderPreprocessor)
235 295
    {
236 295
        $this->itemHolderPreprocessors[] = $holderPreprocessor;
237
    }
238
239
    public function addItemPreprocessor(ItemPreprocessor $itemPreprocessor)
240
    {
241
        $this->itemPreprocessors[] = $itemPreprocessor;
242 295
    }
243
244 295
    public function addItemValidator(ItemValidator $itemValidator)
245 295
    {
246 295
        $this->itemValidators[] = $itemValidator;
247 170
    }
248
249 170
    public function addItemHolderValidator(ItemHolderValidator $itemHolderValidator)
250 295
    {
251
        $this->itemHolderValidators[] = $itemHolderValidator;
252 295
    }
253
254
    public function addItemHolderPostValidator(ItemHolderPostValidator $itemHolderValidator)
255
    {
256
        $this->itemHolderPostValidators[] = $itemHolderValidator;
257
    }
258 295
259
    public function addDiscountProcessor(DiscountProcessor $discountProcessor)
260 295
    {
261 295
        $this->discountProcessors[] = $discountProcessor;
262 295
    }
263 154
264
    /**
265 154
     * @param ItemHolderInterface $itemHolder
266 295
     */
267 295
    protected function calculateTotal(ItemHolderInterface $itemHolder)
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 295
        }, 0);
274
        $itemHolder->setTotal($total);
275 295
        // TODO
276 295
        if ($itemHolder instanceof Order) {
277 291
            // Order には PaymentTotal もセットする
278 264
            $itemHolder->setPaymentTotal($total);
279
        }
280 79
    }
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 291
    {
284 295
        $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 295
            ->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 295
        // TODO
292
        if ($itemHolder instanceof Order) {
293 295
            // Order の場合は SubTotal をセットする
294 295
            $itemHolder->setSubTotal($total);
295 295
        }
296 295
    }
297 295
298 295
    /**
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);
0 ignored issues
show
Deprecated Code introduced by
The method Eccube\Entity\ItemHolderInterface::setTax() has been deprecated with message: 明細ごとに集計した税額と差異が発生する場合があるため非推奨

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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
    /**
376
     * PurchaseFlow をツリー表示します.
377
     *
378
     * @return string
379
     */
380
    public function dump()
381
    {
382
        $callback = function ($processor) {
383
            return get_class($processor);
384
        };
385
        $flows = [
386
            0 => $this->flowType.' flow',
387
            'ItemValidator' => $this->itemValidators->map($callback)->toArray(),
388
            'ItemHolderValidator' => $this->itemHolderValidators->map($callback)->toArray(),
389
            'ItemPreprocessor' => $this->itemPreprocessors->map($callback)->toArray(),
390
            'ItemHolderPreprocessor' => $this->itemHolderPreprocessors->map($callback)->toArray(),
391
            'DiscountProcessor' => $this->discountProcessors->map($callback)->toArray(),
392
            'ItemHolderPostValidator' => $this->itemHolderPostValidators->map($callback)->toArray()
393
        ];
394
        $tree  = new \RecursiveTreeIterator(new \RecursiveArrayIterator($flows));
395
        $tree->setPrefixPart(\RecursiveTreeIterator::PREFIX_RIGHT, ' ');
396
        $tree->setPrefixPart(\RecursiveTreeIterator::PREFIX_MID_LAST, ' ');
397
        $tree->setPrefixPart(\RecursiveTreeIterator::PREFIX_MID_HAS_NEXT, '│');
398
        $tree->setPrefixPart(\RecursiveTreeIterator::PREFIX_END_HAS_NEXT, '├');
399
        $tree->setPrefixPart(\RecursiveTreeIterator::PREFIX_END_LAST, '└');
400
        $out = '';
401
        foreach ($tree as $key => $value) {
402
            if (is_numeric($key)) {
403
                $out .= $value.PHP_EOL;
404
            } else {
405
                $out .= $key.PHP_EOL;
406
            }
407
        }
408
        return $out;
409
    }
410
411
    /**
412
     * @return string
413
     */
414
    public function __toString()
415
    {
416
        return $this->dump();
417
    }
418
}
419