Failed Conditions
Pull Request — experimental/sf (#29)
by Kentaro
50:12 queued 39:05
created

PointProcessor::prepare()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 12
loc 12
ccs 6
cts 6
cp 1
crap 3
rs 9.8666
c 0
b 0
f 0
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\Service\PurchaseFlow\ItemHolderPreprocessor;
26
use Eccube\Service\PurchaseFlow\ItemHolderValidator;
27
use Eccube\Service\PurchaseFlow\PurchaseContext;
28
use Eccube\Service\PurchaseFlow\PurchaseProcessor;
29
30
/**
31
 * 購入フローにおけるポイント処理.
32
 */
33
class PointProcessor extends ItemHolderValidator implements ItemHolderPreprocessor, PurchaseProcessor
34
{
35
    /**
36
     * @var EntityManagerInterface
37
     */
38
    protected $entityManager;
39
40
    /**
41
     * @var BaseInfo
42
     */
43
    protected $BaseInfo;
44
45
    /**
46
     * AddPointProcessor constructor.
47
     *
48
     * @param EntityManagerInterface $entityManager
49
     * @param BaseInfo $BaseInfo
50
     */
51 744
    public function __construct(EntityManagerInterface $entityManager, BaseInfo $BaseInfo)
52
    {
53 744
        $this->entityManager = $entityManager;
54 744
        $this->BaseInfo = $BaseInfo;
55
    }
56
57
    /*
58
     * ItemHolderPreprocessor
59
     */
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 211
    public function process(ItemHolderInterface $itemHolder, PurchaseContext $context)
65
    {
66 211
        if (!$this->supports($itemHolder)) {
67 17
            return;
68
        }
69
70
        // 付与ポイントを計算
71 194
        $addPoint = $this->calculateAddPoint($itemHolder);
72 194
        $itemHolder->setAddPoint($addPoint);
73
74
        // 利用ポイントがある場合は割引明細を追加
75 194
        if ($itemHolder->getUsePoint() > 0) {
76 1
            $discount = $this->pointToPrice($itemHolder->getUsePoint());
77 1
            $this->removePointDiscountItem($itemHolder);
78 1
            $this->addPointDiscountItem($itemHolder, $discount);
79
        }
80
    }
81
82
    /*
83
     * ItemHolderValidator
84
     */
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 10
    protected function validate(ItemHolderInterface $itemHolder, PurchaseContext $context)
90
    {
91 10
        if (!$this->supports($itemHolder)) {
92
            return;
93
        }
94
95
        // 支払い金額 < 利用ポイント
96 10
        $discount = $this->pointToPrice($itemHolder->getUsePoint());
97 10
        if (($itemHolder->getTotal() + $discount) < 0) {
98 1
            $this->throwInvalidItemException('利用ポイントがお支払い金額を上回っています.');
99
        }
100
101
        // 所有ポイント < 利用ポイント
102 9
        $Customer = $itemHolder->getCustomer();
103 9
        if ($Customer->getPoint() < $itemHolder->getUsePoint()) {
104 2
            $this->throwInvalidItemException('利用ポイントが所有ポイントを上回っています.');
105
        }
106
    }
107
108
    /*
109
     * PurchaseProcessor
110
     */
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 9 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...
116
    {
117 9
        if (!$this->supports($itemHolder)) {
118 3
            return;
119
        }
120
121
        // ユーザの保有ポイントを減算
122 6
        $Customer = $itemHolder->getCustomer();
123 6
        if ($Customer) {
124 6
            $Customer->setPoint($Customer->getPoint() - $itemHolder->getUsePoint());
125
        }
126
    }
127
128
    /**
129
     * {@inheritdoc
130
     */
131
    public function commit(ItemHolderInterface $target, PurchaseContext $context)
132
    {
133
        // 何もしない
134
    }
135
136
    /**
137
     * {@inheritdoc
138
     */
139 2 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...
140
    {
141
        // 利用したポイントをユーザに戻す.
142 2
        if (!$this->supports($itemHolder)) {
143
            return;
144
        }
145
146 2
        $Customer = $itemHolder->getCustomer();
147 2
        $Customer->setPoint($Customer->getPoint() + $itemHolder->getUsePoint());
148
    }
149
150
    /*
151
     * Helper methods
152
     */
153
154
    /**
155
     * Processorが実行出来るかどうかを返す.
156
     *
157
     * 以下を満たす場合に実行できる.
158
     *
159
     * - ポイント設定が有効であること.
160
     * - $itemHolderがOrderエンティティであること.
161
     * - 会員のOrderであること.
162
     *
163
     * @param ItemHolderInterface $itemHolder
164
     *
165
     * @return bool
166
     */
167 222
    private function supports(ItemHolderInterface $itemHolder)
168
    {
169 222
        if (!$this->BaseInfo->isOptionPoint()) {
170
            return false;
171
        }
172
173 222
        if (!$itemHolder instanceof Order) {
174
            return false;
175
        }
176
177 222
        if (!$itemHolder->getCustomer()) {
178 17
            return false;
179
        }
180
181 205
        return true;
182
    }
183
184
    /**
185
     * 付与ポイントを計算.
186
     *
187
     * @param ItemHolderInterface $itemHolder
188
     *
189
     * @return int
190
     */
191 194
    private function calculateAddPoint(ItemHolderInterface $itemHolder)
192
    {
193 194
        $basicPointRate = $this->BaseInfo->getBasicPointRate();
194
195
        // 明細ごとのポイントを集計
196 194
        $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...
197 193
            $pointRate = $item->getPointRate();
198 193
            if ($pointRate === null) {
199 193
                $pointRate = $basicPointRate;
200
            }
201
202
            // ポイント = 単価 * ポイント付与率 * 数量
203 193
            $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...
204
205 193
            return $carry + $point;
206 194
        }, 0);
207
208
        // 利用したポイントの割合に対して付与するポイントを減算
209
        // 明細のポイント合計 - (利用ポイント * ポイント付与率)
210 194
        $totalPoint -= intval($itemHolder->getUsePoint() * $basicPointRate / 100);
211
212 194
        return $totalPoint < 0 ? 0 : $totalPoint;
213
    }
214
215
    /**
216
     * 明細追加処理.
217
     *
218
     * @param ItemHolderInterface $itemHolder
219
     * @param integer $discount
220
     */
221 1
    private function addPointDiscountItem(ItemHolderInterface $itemHolder, $discount)
222
    {
223 1
        $DiscountType = $this->entityManager->find(OrderItemType::class, OrderItemType::DISCOUNT);
224 1
        $TaxInclude = $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
225 1
        $Taxation = $this->entityManager->find(TaxType::class, TaxType::TAXATION);
226
227 1
        $OrderItem = new OrderItem();
228 1
        $OrderItem->setProductName('ポイント値引')
229 1
            ->setPrice($discount)
230 1
            ->setPriceIncTax($discount)
231 1
            ->setTaxRate(8)
232 1
            ->setQuantity(1)
233 1
            ->setOrderItemType($DiscountType)
234 1
            ->setTaxDisplayType($TaxInclude)
235 1
            ->setTaxType($Taxation)
236 1
            ->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...
237 1
        $itemHolder->addItem($OrderItem);
238
    }
239
240
    /**
241
     * 既存のポイント明細を削除する.
242
     *
243
     * @param ItemHolderInterface $itemHolder
244
     */
245 1 View Code Duplication
    private function removePointDiscountItem(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...
246
    {
247 1
        foreach ($itemHolder->getItems() as $item) {
248
            if ($item->isDiscount() && $item->getProductName() == 'ポイント値引') {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Eccube\Entity\ItemInterface as the method getProductName() does only exist in the following implementations of said interface: Eccube\Entity\OrderItem.

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...
249
                $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...
250
                $this->entityManager->remove($item);
251
            }
252
        }
253
    }
254
255
    /**
256
     * ポイントを金額に変換する.
257
     *
258
     * @param integer $point int ポイント
259
     *
260
     * @return int 金額
261
     */
262 11
    private function pointToPrice($point)
263
    {
264 11
        return intval($point * $this->BaseInfo->getPointConversionRate()) * -1;
265
    }
266
}
267