Failed Conditions
Branch modify-scrutinizeryml (08dddf)
by Kentaro
10:04
created

ProductClassController::saveProductClasses()   C

Complexity

Conditions 14
Paths 35

Size

Total Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 14.0142

Importance

Changes 0
Metric Value
cc 14
nc 35
nop 2
dl 0
loc 89
ccs 46
cts 48
cp 0.9583
crap 14.0142
rs 5.2533
c 0
b 0
f 0

How to fix   Long Method    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) 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\Controller\Admin\Product;
15
16
use Doctrine\ORM\NoResultException;
17
use Eccube\Controller\AbstractController;
18
use Eccube\Entity\ClassName;
19
use Eccube\Entity\Product;
20
use Eccube\Entity\ProductClass;
21
use Eccube\Entity\ProductStock;
22
use Eccube\Entity\TaxRule;
23
use Eccube\Form\Type\Admin\ProductClassMatrixType;
24
use Eccube\Repository\BaseInfoRepository;
25
use Eccube\Repository\ClassCategoryRepository;
26
use Eccube\Repository\ProductClassRepository;
27
use Eccube\Repository\ProductRepository;
28
use Eccube\Repository\TaxRuleRepository;
29
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
30
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
31
use Symfony\Component\Form\Extension\Core\Type\FormType;
32
use Symfony\Component\HttpFoundation\Request;
33
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
34
35
class ProductClassController extends AbstractController
36
{
37
    /**
38
     * @var ProductRepository
39
     */
40
    protected $productRepository;
41
42
    /**
43
     * @var ProductClassRepository
44
     */
45
    protected $productClassRepository;
46
47
    /**
48
     * @var ClassCategoryRepository
49
     */
50
    protected $classCategoryRepository;
51
52
    /**
53
     * @var BaseInfoRepository
54
     */
55
    protected $baseInfoRepository;
56
57
    /**
58
     * @var TaxRuleRepository
59
     */
60
    protected $taxRuleRepository;
61
62
    /**
63
     * ProductClassController constructor.
64
     *
65
     * @param ProductClassRepository $productClassRepository
66
     * @param ClassCategoryRepository $classCategoryRepository
67
     */
68 13
    public function __construct(
69
        ProductRepository $productRepository,
70
        ProductClassRepository $productClassRepository,
71
        ClassCategoryRepository $classCategoryRepository,
72
        BaseInfoRepository $baseInfoRepository,
73
        TaxRuleRepository $taxRuleRepository
74
    ) {
75 13
        $this->productRepository = $productRepository;
76 13
        $this->productClassRepository = $productClassRepository;
77 13
        $this->classCategoryRepository = $classCategoryRepository;
78 13
        $this->baseInfoRepository = $baseInfoRepository;
79 13
        $this->taxRuleRepository = $taxRuleRepository;
80
    }
81
82
    /**
83
     * 商品規格が登録されていなければ新規登録, 登録されていれば更新画面を表示する
84
     *
85
     * @Route("/%eccube_admin_route%/product/product/class/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class")
86
     * @Template("@admin/Product/product_class.twig")
87
     */
88 13
    public function index(Request $request, $id)
89
    {
90 13
        $Product = $this->findProduct($id);
91 13
        if (!$Product) {
92
            throw new NotFoundHttpException();
93
        }
94
95 13
        $ClassName1 = null;
96 13
        $ClassName2 = null;
97
98 13
        if ($Product->hasProductClass()) {
99
            // 規格ありの商品は編集画面を表示する.
100 11
            $ProductClasses = $Product->getProductClasses()
101 11
                ->filter(function ($pc) {
102 11
                    return $pc->getClassCategory1() !== null;
103 11
                });
104
105
            // 設定されている規格名1, 2を取得(商品規格の規格分類には必ず同じ値がセットされている)
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
106 11
            $FirstProductClass = $ProductClasses->first();
107 11
            $ClassName1 = $FirstProductClass->getClassCategory1()->getClassName();
108 11
            $ClassCategory2 = $FirstProductClass->getClassCategory2();
109 11
            $ClassName2 = $ClassCategory2 ? $ClassCategory2->getClassName() : null;
110
111
            // 規格名1/2から組み合わせを生成し, DBから取得した商品規格とマージする.
112 11
            $ProductClasses = $this->mergeProductClassess(
113 11
                $this->createProductClasses($ClassName1, $ClassName2),
114 11
                $ProductClasses);
115
116
            // 組み合わせのフォームを生成する.
117 11
            $form = $this->createMatrixForm($ProductClasses, $ClassName1, $ClassName2,
118 11
                ['product_classes_exist' => true]);
119 11
            $form->handleRequest($request);
120
121 11
            if ($form->isSubmitted() && $form->isValid()) {
122
                // フォームではtokenを無効化しているのでここで確認する.
123 5
                $this->isTokenValid();
124
125 5
                $this->saveProductClasses($Product, $form['product_classes']->getData());
126
127 5
                $this->addSuccess('admin.product.product_class.update.complete', 'admin');
128
129 11
                return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
130
            }
131
        } else {
132
            // 規格なし商品
133 5
            $form = $this->createMatrixForm();
134 5
            $form->handleRequest($request);
135
136 5
            if ($form->isSubmitted() && $form->isValid()) {
137
                // フォームではtokenを無効化しているのでここで確認する.
138 4
                $this->isTokenValid();
139
140
                // 登録,更新ボタンが押下されたかどうか.
141 4
                $isSave = $form['save']->isClicked();
142
143
                // 規格名1/2から商品規格の組み合わせを生成する.
144 4
                $ClassName1 = $form['class_name1']->getData();
145 4
                $ClassName2 = $form['class_name2']->getData();
146 4
                $ProductClasses = $this->createProductClasses($ClassName1, $ClassName2);
147
148
                // 組み合わせのフォームを生成する.
149
                // class_name1, class_name2が取得できるのがsubmit後のため, フォームを再生成して組み合わせ部分を構築している
150
                // submit後だと, フォーム項目の追加やデータ変更が許可されないため.
151 4
                $form = $this->createMatrixForm($ProductClasses, $ClassName1, $ClassName2,
152 4
                    ['product_classes_exist' => true]);
153
154
                // 登録ボタン押下時
155 4
                if ($isSave) {
156 3
                    $form->handleRequest($request);
157 3
                    if ($form->isSubmitted() && $form->isValid()) {
158 3
                        $this->saveProductClasses($Product, $form['product_classes']->getData());
159
160 3
                        $this->addSuccess('admin.product.product_class.save.complete', 'admin');
161
162 3
                        return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
163
                    }
164
                }
165
            }
166
        }
167
168
        return [
169 13
            'Product' => $Product,
170 13
            'form' => $form->createView(),
171 13
            'clearForm' => $this->createForm(FormType::class)->createView(),
172 13
            'ClassName1' => $ClassName1,
173 13
            'ClassName2' => $ClassName2,
174
        ];
175
    }
176
177
    /**
178
     * 商品規格を初期化する.
179
     *
180
     * @Route("/%eccube_admin_route%/product/product/class/{id}/clear", requirements={"id" = "\d+"}, name="admin_product_product_class_clear")
181
     */
182
    public function clearProductClasses(Request $request, Product $Product)
183
    {
184
        if (!$Product->hasProductClass()) {
185
            return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
186
        }
187
188
        $form = $this->createForm(FormType::class);
189
        $form->handleRequest($request);
190
191
        if ($form->isSubmitted() && $form->isValid()) {
192
            $ProductClasses = $this->productClassRepository->findBy([
193
                'Product' => $Product,
194
            ]);
195
196
            // デフォルト規格のみ有効にする
197
            foreach ($ProductClasses as $ProductClass) {
198
                $ProductClass->setVisible(false);
199
            }
200
            foreach ($ProductClasses as $ProductClass) {
201
                if (null === $ProductClass->getClassCategory1() && null === $ProductClass->getClassCategory2()) {
202
                    $ProductClass->setVisible(true);
203
                    break;
204
                }
205
            }
206
207
            $this->entityManager->flush();
208
209
            $this->addSuccess('admin.product.product_class.clear.complete', 'admin');
210
        }
211
212
        return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
213
    }
214
215
    /**
216
     * 規格名1/2から, 商品規格の組み合わせを生成する.
217
     *
218
     * @param ClassName $ClassName1
219
     * @param ClassName|null $ClassName2
220
     *
221
     * @return array|ProductClass[]
222
     */
223 12
    protected function createProductClasses(ClassName $ClassName1, ClassName $ClassName2 = null)
224
    {
225 12
        $ProductClasses = [];
226 12
        $ClassCategories1 = $this->classCategoryRepository->findBy(['ClassName' => $ClassName1], ['sort_no' => 'DESC']);
227 12
        $ClassCategories2 = [];
228 12
        if ($ClassName2) {
229 8
            $ClassCategories2 = $this->classCategoryRepository->findBy(['ClassName' => $ClassName2],
230 8
                ['sort_no' => 'DESC']);
231
        }
232 12
        foreach ($ClassCategories1 as $ClassCategory1) {
233
            // 規格1のみ
234 12
            if (!$ClassName2) {
235 4
                $ProductClass = new ProductClass();
236 4
                $ProductClass->setClassCategory1($ClassCategory1);
237 4
                $ProductClasses[] = $ProductClass;
238 4
                continue;
239
            }
240
            // 規格1/2
241 8
            foreach ($ClassCategories2 as $ClassCategory2) {
242 8
                $ProductClass = new ProductClass();
243 8
                $ProductClass->setClassCategory1($ClassCategory1);
244 8
                $ProductClass->setClassCategory2($ClassCategory2);
245 8
                $ProductClasses[] = $ProductClass;
246
            }
247
        }
248
249 12
        return $ProductClasses;
250
    }
251
252
    /**
253
     * 商品規格の配列をマージする.
254
     *
255
     * @param $ProductClassessForMatrix
256
     * @param $ProductClasses
257
     *
258
     * @return array|ProductClass[]
259
     */
260 11
    protected function mergeProductClassess($ProductClassessForMatrix, $ProductClasses)
261
    {
262 11
        $mergedProductClasses = [];
263 11
        foreach ($ProductClassessForMatrix as $pcfm) {
264 11
            foreach ($ProductClasses as $pc) {
265 11
                if ($pcfm->getClassCategory1()->getId() === $pc->getClassCategory1()->getId()) {
266 11
                    $cc2fm = $pcfm->getClassCategory2();
267 11
                    $cc2 = $pc->getClassCategory2();
268
269 11
                    if (null === $cc2fm && null === $cc2) {
270 3
                        $mergedProductClasses[] = $pc;
271 3
                        continue 2;
272
                    }
273
274 8
                    if ($cc2fm && $cc2 && $cc2fm->getId() === $cc2->getId()) {
275 8
                        $mergedProductClasses[] = $pc;
276 11
                        continue 2;
277
                    }
278
                }
279
            }
280
281 6
            $mergedProductClasses[] = $pcfm;
282
        }
283
284 11
        return $mergedProductClasses;
285
    }
286
287
    /**
288
     * 商品規格を登録, 更新する.
289
     *
290
     * @param Product $Product
291
     * @param array|ProductClass[] $ProductClasses
292
     */
293 8
    protected function saveProductClasses(Product $Product, $ProductClasses = [])
294
    {
295 8
        foreach ($ProductClasses as $pc) {
296
            // 新規登録時、チェックを入れていなければ更新しない
297 8
            if (!$pc->getId() && !$pc->isVisible()) {
298 4
                continue;
299
            }
300
301
            // 無効から有効にした場合は, 過去の登録情報を検索.
302 8
            if (!$pc->getId()) {
303
                /** @var ProductClass $ExistsProductClass */
304 4
                $ExistsProductClass = $this->productClassRepository->findOneBy([
305 4
                    'Product' => $Product,
306 4
                    'ClassCategory1' => $pc->getClassCategory1(),
307 4
                    'ClassCategory2' => $pc->getClassCategory2(),
308
                ]);
309
310
                // 過去の登録情報があればその情報を復旧する.
311 4
                if ($ExistsProductClass) {
312 1
                    $ExistsProductClass->copyProperties($pc, [
313 1
                        'id',
314
                        'price01_inc_tax',
315
                        'price02_inc_tax',
316
                        'create_date',
317
                        'update_date',
318
                        'Creator',
319
                    ]);
320 1
                    $pc = $ExistsProductClass;
321
                }
322
            }
323
324
            // 更新時, チェックを外した場合はPOST内容を破棄してvisibleのみ更新する.
325 8
            if ($pc->getId() && !$pc->isVisible()) {
326 2
                $this->entityManager->refresh($pc);
327 2
                $pc->setVisible(false);
328 2
                continue;
329
            }
330
331 8
            $pc->setProduct($Product);
332 8
            $this->entityManager->persist($pc);
333
334
            // 在庫の更新
335 8
            $ProductStock = $pc->getProductStock();
336 8
            if (!$ProductStock) {
337 4
                $ProductStock = new ProductStock();
338 4
                $ProductStock->setProductClass($pc);
339 4
                $this->entityManager->persist($ProductStock);
340
            }
341 8
            $ProductStock->setStock($pc->isStockUnlimited() ? null : $pc->getStock());
342
343 8
            if ($this->baseInfoRepository->get()->isOptionProductTaxRule()) {
344 6
                $rate = $pc->getTaxRate();
345 6
                $TaxRule = $pc->getTaxRule();
346 6
                if (is_numeric($rate)) {
347 5
                    if ($TaxRule) {
348
                        $TaxRule->setTaxRate($rate);
349
                    } else {
350
                        // 初期税率設定の計算方法を設定する
351 5
                        $RoundingType = $this->taxRuleRepository->find(TaxRule::DEFAULT_TAX_RULE_ID)
352 5
                            ->getRoundingType();
353
354 5
                        $TaxRule = new TaxRule();
355 5
                        $TaxRule->setProduct($Product);
356 5
                        $TaxRule->setProductClass($pc);
357 5
                        $TaxRule->setTaxRate($rate);
358 5
                        $TaxRule->setRoundingType($RoundingType);
359 5
                        $TaxRule->setTaxAdjust(0);
360 5
                        $TaxRule->setApplyDate(new \DateTime());
361 5
                        $this->entityManager->persist($TaxRule);
362
                    }
363
                } else {
364 4
                    if ($TaxRule) {
365
                        $this->taxRuleRepository->delete($TaxRule);
366 8
                        $pc->setTaxRule(null);
367
                    }
368
                }
369
            }
370
        }
371
372
        // デフォルト規格を非表示にする.
373 8
        $DefaultProductClass = $this->productClassRepository->findOneBy([
374 8
            'Product' => $Product,
375
            'ClassCategory1' => null,
376
            'ClassCategory2' => null,
377
        ]);
378 8
        $DefaultProductClass->setVisible(false);
379
380 8
        $this->entityManager->flush();
381
    }
382
383
    /**
384
     * 商品規格登録フォームを生成する.
385
     *
386
     * @param array $ProductClasses
387
     * @param ClassName|null $ClassName1
388
     * @param ClassName|null $ClassName2
389
     * @param array $options
390
     *
391
     * @return \Symfony\Component\Form\FormInterface
392
     */
393 13
    protected function createMatrixForm(
394
        $ProductClasses = [],
395
        ClassName $ClassName1 = null,
396
        ClassName $ClassName2 = null,
397
        array $options = []
398
    ) {
399 13
        $options = array_merge(['csrf_protection' => false], $options);
400 13
        $builder = $this->formFactory->createBuilder(ProductClassMatrixType::class, [
401 13
            'product_classes' => $ProductClasses,
402 13
            'class_name1' => $ClassName1,
403 13
            'class_name2' => $ClassName2,
404 13
        ], $options);
405
406 13
        return $builder->getForm();
407
    }
408
409
    /**
410
     * 商品を取得する.
411
     * 商品規格はvisible=trueのものだけを取得し, 規格分類はsort_no=DESCでソートされている.
412
     *
413
     * @param $id
414
     *
415
     * @return Product|null
416
     *
417
     * @throws \Doctrine\ORM\NonUniqueResultException
418
     */
419 13
    protected function findProduct($id)
420
    {
421 13
        $qb = $this->productRepository->createQueryBuilder('p')
422 13
            ->addSelect(['pc', 'cc1', 'cc2'])
423 13
            ->leftJoin('p.ProductClasses', 'pc')
424 13
            ->leftJoin('pc.ClassCategory1', 'cc1')
425 13
            ->leftJoin('pc.ClassCategory2', 'cc2')
426 13
            ->where('p.id = :id')
427 13
            ->andWhere('pc.visible = :pc_visible')
428 13
            ->setParameter('id', $id)
429 13
            ->setParameter('pc_visible', true)
430 13
            ->orderBy('cc1.sort_no', 'DESC')
431 13
            ->addOrderBy('cc2.sort_no', 'DESC');
432
433
        try {
434 13
            return $qb->getQuery()->getSingleResult();
435
        } catch (NoResultException $e) {
436
            return null;
437
        }
438
    }
439
}
440