Completed
Pull Request — 4.0 (#4400)
by Kiyotaka
04:40
created

StockDiffProcessor::validate()   C

Complexity

Conditions 15
Paths 9

Size

Total Lines 45

Duplication

Lines 8
Ratio 17.78 %

Code Coverage

Tests 18
CRAP Score 27.278

Importance

Changes 0
Metric Value
cc 15
nc 9
nop 2
dl 8
loc 45
ccs 18
cts 29
cp 0.6207
crap 27.278
rs 5.9166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Processor;
15
16
use Eccube\Entity\ItemHolderInterface;
17
use Eccube\Entity\Master\OrderStatus;
18
use Eccube\Entity\ProductClass;
19
use Eccube\Entity\ProductStock;
20
use Eccube\Repository\ProductClassRepository;
21
use Eccube\Service\PurchaseFlow\ItemHolderValidator;
22
use Eccube\Service\PurchaseFlow\PurchaseContext;
23
use Eccube\Service\PurchaseFlow\PurchaseException;
24
use Eccube\Service\PurchaseFlow\PurchaseProcessor;
25
26
/**
27
 * 編集前/編集後の個数の差分にもとづいて在庫を更新します.
28
 */
29
class StockDiffProcessor extends ItemHolderValidator implements PurchaseProcessor
30
{
31
    /**
32
     * @var ProductClassRepository
33
     */
34
    protected $productClassRepository;
35
36
    /**
37
     * StockProcessor constructor.
38
     *
39
     * @param ProductClassRepository $productClassRepository
40
     */
41 713
    public function __construct(ProductClassRepository $productClassRepository)
42
    {
43 713
        $this->productClassRepository = $productClassRepository;
44
    }
45
46
    /**
47
     * @param ItemHolderInterface $itemHolder
48
     * @param PurchaseContext $context
49
     *
50
     * @throws \Eccube\Service\PurchaseFlow\InvalidItemException
51
     */
52 156
    public function validate(ItemHolderInterface $itemHolder, PurchaseContext $context)
53
    {
54 156
        if (is_null($context->getOriginHolder())) {
55
            return;
56
        }
57
58 156
        $From = $context->getOriginHolder();
59 156
        $To = $itemHolder;
60 156
        $diff = $this->getDiffOfQuantities($From, $To);
61
62 156
        foreach ($diff as $id => $quantity) {
63
            /** @var ProductClass $ProductClass */
64 156
            $ProductClass = $this->productClassRepository->find($id);
65 156
            if ($ProductClass->isStockUnlimited()) {
66
                continue;
67
            }
68 156
69
            $stock = $ProductClass->getStock();
70 156
            $Items = $To->getProductOrderItems();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Eccube\Entity\ItemHolderInterface as the method getProductOrderItems() does only exist in the following implementations of said interface: Eccube\Entity\Order.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
71
            $Items = array_filter($Items, function ($Item) use ($id) {
72
                return $Item->getProductClass()->getId() == $id;
73
            });
74
            $toQuantity = array_reduce($Items, function ($quantity, $Item) {
75
                return $quantity += $Item->getQuantity();
76
            }, 0);
77
            
78
            // ステータスをキャンセルに変更した場合
79
            if ($To->getOrderStatus() && $To->getOrderStatus()->getId() == OrderStatus::CANCEL
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Eccube\Entity\ItemHolderInterface as the method getOrderStatus() does only exist in the following implementations of said interface: Eccube\Entity\Order.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
80
                && $From->getOrderStatus() && $From->getOrderStatus()->getId() != OrderStatus::CANCEL) {
81 View Code Duplication
                if ($stock + $toQuantity < 0) {
82 156
                    $this->throwInvalidItemException(trans('purchase_flow.over_stock', ['%name%' => $ProductClass->formattedProductName()]));
83 156
                }
84
            // ステータスをキャンセルから対応中に変更した場合
85
            } elseif ($To->getOrderStatus() && $To->getOrderStatus()->getId() == OrderStatus::IN_PROGRESS
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Eccube\Entity\ItemHolderInterface as the method getOrderStatus() does only exist in the following implementations of said interface: Eccube\Entity\Order.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
86
                && $From->getOrderStatus() && $From->getOrderStatus()->getId() == OrderStatus::CANCEL) {
87
                if ($stock - $toQuantity < 0) {
88
                    $this->throwInvalidItemException(trans('purchase_flow.over_stock', ['%name%' => $ProductClass->formattedProductName()]));
89 156
                }
90 View Code Duplication
            } else {
91 156
                if ($stock - $quantity < 0) {
92 156
                    $this->throwInvalidItemException(trans('purchase_flow.over_stock', ['%name%' => $ProductClass->formattedProductName()]));
93 156
                }
94
            }
95 156
        }
96 156
    }
97
98 156
    protected function getDiffOfQuantities(ItemHolderInterface $From, ItemHolderInterface $To)
99
    {
100
        $FromItems = $this->getQuantityByProductClass($From);
101 154
        $ToItems = $this->getQuantityByProductClass($To);
102
        $ids = array_unique(array_merge(array_keys($FromItems), array_keys($ToItems)));
103 5
104
        $diff = [];
105 3
        foreach ($ids as $id) {
106
            // 更新された明細
107 5
            if (isset($FromItems[$id]) && isset($ToItems[$id])) {
108
                // 2 -> 3 = +1
109 156
                // 2 -> 1 = -1
110
                $diff[$id] = $ToItems[$id] - $FromItems[$id];
111
            } // 削除された明細
112 View Code Duplication
            elseif (isset($FromItems[$id]) && empty($ToItems[$id])) {
113 156
                // 2 -> 0 = -2
114
                $diff[$id] = $FromItems[$id] * -1;
115
            } // 追加された明細
116 156 View Code Duplication
            elseif (!isset($FromItems[$id]) && isset($ToItems[$id])) {
117
                // 0 -> 2 = +2
118 156
                $diff[$id] = $ToItems[$id];
119 156
            }
120 156
        }
121 156
122 156
        return $diff;
123
    }
124
125 156
    protected function getQuantityByProductClass(ItemHolderInterface $ItemHolder)
126
    {
127
        $ItemsByProductClass = [];
128
        foreach ($ItemHolder->getItems() as $Item) {
129
            if ($Item->isProduct()) {
130 156
                $id = $Item->getProductClass()->getId();
131
                if (isset($ItemsByProductClass[$id])) {
132
                    $ItemsByProductClass[$id] += $Item->getQuantity();
133
                } else {
134
                    $ItemsByProductClass[$id] = $Item->getQuantity();
135
                }
136
            }
137
        }
138
139
        return $ItemsByProductClass;
140
    }
141 5
142
    /**
143 5
     * 受注の仮確定処理を行います。
144
     *
145
     * @param ItemHolderInterface $target
146
     * @param PurchaseContext $context
147 5
     *
148
     * @throws PurchaseException
149 5
     */
150
    public function prepare(ItemHolderInterface $target, PurchaseContext $context)
151 5
    {
152 5
        if (is_null($context->getOriginHolder())) {
153
            return;
154
        }
155
156 5
        $diff = $this->getDiffOfQuantities($context->getOriginHolder(), $target);
157 5
158 5
        foreach ($diff as $id => $quantity) {
159
            /** @var ProductClass $ProductClass */
160
            $ProductClass = $this->productClassRepository->find($id);
161
            if ($ProductClass->isStockUnlimited()) {
162
                continue;
163 5
            }
164 5
165
            $stock = $ProductClass->getStock() - $quantity;
166
            $ProductStock = $ProductClass->getProductStock();
167
            if (!$ProductStock) {
168
                $ProductStock = new ProductStock();
169
                $ProductStock->setProductClass($ProductClass);
170
                $ProductClass->setProductStock($ProductStock);
171
            }
172
            $ProductClass->setStock($stock);
173
            $ProductStock->setStock($stock);
174
        }
175
    }
176 5
177
    /**
178
     * 受注の確定処理を行います。
179 5
     *
180
     * @param ItemHolderInterface $target
181
     * @param PurchaseContext $context
182
     *
183
     * @throws PurchaseException
184
     */
185
    public function commit(ItemHolderInterface $target, PurchaseContext $context)
186
    {
187
        // 何もしない.
188
        return;
189
    }
190
191
    /**
192
     * 仮確定した受注データの取り消し処理を行います。
193
     *
194
     * @param ItemHolderInterface $itemHolder
195
     * @param PurchaseContext $context
196
     */
197
    public function rollback(ItemHolderInterface $itemHolder, PurchaseContext $context)
198
    {
199
        // 何もしない.
200
        return;
201
    }
202
}
203