Completed
Pull Request — 4.0 (#3627)
by Kentaro
06:19
created

PointProcessor::commit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
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\Processor;
15
16
use Doctrine\ORM\EntityManagerInterface;
17
use Eccube\Entity\BaseInfo;
18
use Eccube\Entity\ItemHolderInterface;
19
use Eccube\Entity\ItemInterface;
20
use Eccube\Entity\Master\OrderItemType;
21
use Eccube\Entity\Master\TaxDisplayType;
22
use Eccube\Entity\Master\TaxType;
23
use Eccube\Entity\Order;
24
use Eccube\Entity\OrderItem;
25
use Eccube\Repository\BaseInfoRepository;
26
use Eccube\Service\PurchaseFlow\ItemHolderPreprocessor;
27
use Eccube\Service\PurchaseFlow\ItemHolderValidator;
28
use Eccube\Service\PurchaseFlow\PurchaseContext;
29
use Eccube\Service\PurchaseFlow\PurchaseProcessor;
30
31
/**
32
 * 購入フローにおけるポイント処理.
33
 */
34
class PointProcessor extends ItemHolderValidator implements ItemHolderPreprocessor, PurchaseProcessor
35
{
36
    /**
37
     * @var EntityManagerInterface
38
     */
39
    protected $entityManager;
40
41
    /**
42
     * @var BaseInfo
43
     */
44
    protected $BaseInfo;
45
46
    /**
47
     * AddPointProcessor constructor.
48
     *
49
     * @param EntityManagerInterface $entityManager
50
     * @param BaseInfoRepository $baseInfoRepository
51
     */
52 790
    public function __construct(EntityManagerInterface $entityManager, BaseInfoRepository $baseInfoRepository)
53
    {
54 790
        $this->entityManager = $entityManager;
55 790
        $this->BaseInfo = $baseInfoRepository->get();
56
    }
57
58
    /*
59
     * ItemHolderPreprocessor
60
     */
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 232
    public function process(ItemHolderInterface $itemHolder, PurchaseContext $context)
66
    {
67 232
        if (!$this->supports($itemHolder)) {
68 17
            return;
69
        }
70
71 215
        // 利用ポイントがある場合は割引明細を追加
72
        $this->removePointDiscountItem($itemHolder);
73
        if ($itemHolder->getUsePoint() > 0) {
74 215
            $discount = $this->pointToPrice($itemHolder->getUsePoint());
75 17
            $this->addPointDiscountItem($itemHolder, $discount);
76 17
        }
77
78
        // 付与ポイントを計算
79
        $addPoint = $this->calculateAddPoint($itemHolder);
80 215
        $itemHolder->setAddPoint($addPoint);
81 215
    }
82
83
    /*
84
     * ItemHolderValidator
85
     */
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    protected function validate(ItemHolderInterface $itemHolder, PurchaseContext $context)
91 61
    {
92
        if (!$this->supports($itemHolder)) {
93 61
            return;
94 17
        }
95
96
        // 所有ポイント < 利用ポイント
97
        $Customer = $itemHolder->getCustomer();
98 44
        if ($Customer->getPoint() < $itemHolder->getUsePoint()) {
99 44
            // 利用ポイントが所有ポイントを上回っていた場合は所有ポイントで上書き
100
            $itemHolder->setUsePoint($Customer->getPoint());
101 2
            $this->throwInvalidItemException('利用ポイントが所有ポイントを上回っています.');
102 2
        }
103
104
        // 支払い金額 < 利用ポイント
105
        if ($itemHolder->getTotal() < 0) {
106 42
            // 利用ポイントが支払い金額を上回っていた場合は支払い金額が0円以上となるようにポイントを調整
107
            $overPoint = floor($itemHolder->getTotal() / $this->BaseInfo->getPointConversionRate());
108 1
            $itemHolder->setUsePoint($itemHolder->getUsePoint() + $overPoint);
109 1
            $this->throwInvalidItemException('利用ポイントがお支払い金額を上回っています.');
110 1
        }
111
    }
112
113
    /*
114
     * PurchaseProcessor
115
     */
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 View Code Duplication
    public function prepare(ItemHolderInterface $itemHolder, PurchaseContext $context)
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...
121 12
    {
122
        if (!$this->supports($itemHolder)) {
123 12
            return;
124 3
        }
125
126
        // ユーザの保有ポイントを減算
127
        $Customer = $itemHolder->getCustomer();
128 9
        $Customer->setPoint($Customer->getPoint() - $itemHolder->getUsePoint());
129 9
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function commit(ItemHolderInterface $target, PurchaseContext $context)
135
    {
136
        // 何もしない
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 View Code Duplication
    public function rollback(ItemHolderInterface $itemHolder, PurchaseContext $context)
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...
143 2
    {
144
        // 利用したポイントをユーザに戻す.
145
        if (!$this->supports($itemHolder)) {
146 2
            return;
147
        }
148
149
        $Customer = $itemHolder->getCustomer();
150 2
        $Customer->setPoint($Customer->getPoint() + $itemHolder->getUsePoint());
151 2
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function getRoute()
157
    {
158
        return 'shopping';
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function getRouteParameters()
165
    {
166
        return [];
167
    }
168
169
    /*
170
     * Helper methods
171 239
     */
172
173 239
    /**
174
     * Processorが実行出来るかどうかを返す.
175
     *
176
     * 以下を満たす場合に実行できる.
177 239
     *
178
     * - ポイント設定が有効であること.
179
     * - $itemHolderがOrderエンティティであること.
180
     * - 会員のOrderであること.
181 239
     *
182 17
     * @param ItemHolderInterface $itemHolder
183
     *
184
     * @return bool
185 222
     */
186
    private function supports(ItemHolderInterface $itemHolder)
187
    {
188
        if (!$this->BaseInfo->isOptionPoint()) {
189
            return false;
190
        }
191
192
        if (!$itemHolder instanceof Order) {
193
            return false;
194
        }
195 215
196
        if (!$itemHolder->getCustomer()) {
197 215
            return false;
198
        }
199
200 215
        return true;
201 215
    }
202 215
203 215
    /**
204
     * 付与ポイントを計算.
205
     *
206
     * @param ItemHolderInterface $itemHolder
207 215
     *
208 17
     * @return int
209
     */
210
    private function calculateAddPoint(ItemHolderInterface $itemHolder)
211 214
    {
212
        $basicPointRate = $this->BaseInfo->getBasicPointRate();
213
214 215
        // 明細ごとのポイントを集計
215 215
        $totalPoint = array_reduce($itemHolder->getItems()->toArray(), function ($carry, ItemInterface $item) use ($basicPointRate) {
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...
216
            $pointRate = $item->getPointRate();
217 215
            if ($pointRate === null) {
218
                $pointRate = $basicPointRate;
219
            }
220
221
            // TODO: ポイントは税抜き分しか割引されない、ポイント明細は税抜きのままでいいのか?
222
            if ($item->isPoint()) {
223
                $point = round($item->getPrice() * ($pointRate / 100)) * $item->getQuantity();
224
            } else {
225
                // ポイント = 単価 * ポイント付与率 * 数量
226 17
                $point = round($item->getPriceIncTax() * ($pointRate / 100)) * $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...
227
            }
228 17
229 17
            return $carry + $point;
230 17
        }, 0);
231
232
        return $totalPoint < 0 ? 0 : $totalPoint;
233 17
    }
234 17
235 17
    /**
236 17
     * 明細追加処理.
237 17
     *
238 17
     * @param ItemHolderInterface $itemHolder
239 17
     * @param integer $discount
240 17
     */
241 17
    private function addPointDiscountItem(ItemHolderInterface $itemHolder, $discount)
242 17
    {
243 17
        $DiscountType = $this->entityManager->find(OrderItemType::class, OrderItemType::POINT);
244 17
        $TaxInclude = $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
245 17
        $Taxation = $this->entityManager->find(TaxType::class, TaxType::NON_TAXABLE);
246
247
        // TODO TaxProcessorが先行して実行されるため, 税額等の値は個別にセットする.
248
        $OrderItem = new OrderItem();
249
        $OrderItem->setProductName($DiscountType->getName())
250
            ->setPrice($discount)
251
            ->setQuantity(1)
252
            ->setTax(0)
253 215
            ->setTaxRate(0)
254
            ->setTaxRuleId(null)
255 215
            ->setRoundingType(null)
256 214
            ->setOrderItemType($DiscountType)
257
            ->setTaxDisplayType($TaxInclude)
258 214
            ->setTaxType($Taxation)
259
            ->setOrder($itemHolder);
0 ignored issues
show
Documentation introduced by
$itemHolder is of type object<Eccube\Entity\ItemHolderInterface>, but the function expects a null|object<Eccube\Entity\Order>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
260
        $itemHolder->addItem($OrderItem);
261
    }
262
263
    /**
264
     * 既存のポイント明細を削除する.
265
     *
266
     * @param ItemHolderInterface $itemHolder
267
     */
268
    private function removePointDiscountItem(ItemHolderInterface $itemHolder)
269
    {
270 17
        foreach ($itemHolder->getItems() as $item) {
271
            if ($item->isPoint()) {
272 17
                $itemHolder->removeOrderItem($item);
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 removeOrderItem() 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...
273
                $this->entityManager->remove($item);
274
            }
275
        }
276
    }
277
278
    /**
279
     * ポイントを金額に変換する.
280
     *
281
     * @param integer $point int ポイント
282
     *
283
     * @return int 金額
284
     */
285
    private function pointToPrice($point)
286
    {
287
        return intval($point * $this->BaseInfo->getPointConversionRate()) * -1;
288
    }
289
}
290