Failed Conditions
Push — sf/last-boss ( 98c677...e157b8 )
by Kiyotaka
05:51
created

ProductController   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 471
Duplicated Lines 9.13 %

Coupling/Cohesion

Components 1
Dependencies 23

Importance

Changes 0
Metric Value
dl 43
loc 471
rs 9.76
c 0
b 0
f 0
wmc 33
lcom 1
cbo 23

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 15 15 1
A detail() 0 39 3
A addFavorite() 0 43 2
C addCart() 13 116 11
A getPageTitle() 0 10 5
A checkVisibility() 0 20 3
C index() 15 135 8

How to fix   Duplicated Code   

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:

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;
15
16
use Eccube\Entity\BaseInfo;
17
use Eccube\Entity\Master\ProductStatus;
18
use Eccube\Entity\Product;
19
use Eccube\Event\EccubeEvents;
20
use Eccube\Event\EventArgs;
21
use Eccube\Form\Type\AddCartType;
22
use Eccube\Form\Type\Master\ProductListMaxType;
23
use Eccube\Form\Type\Master\ProductListOrderByType;
24
use Eccube\Form\Type\SearchProductType;
25
use Eccube\Repository\BaseInfoRepository;
26
use Eccube\Repository\CustomerFavoriteProductRepository;
27
use Eccube\Repository\ProductRepository;
28
use Eccube\Service\CartService;
29
use Eccube\Service\PurchaseFlow\PurchaseContext;
30
use Eccube\Service\PurchaseFlow\PurchaseFlow;
31
use Knp\Component\Pager\Paginator;
32
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
33
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
34
use Symfony\Component\HttpFoundation\Request;
35
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
36
use Symfony\Component\Routing\Annotation\Route;
37
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
38
39
class ProductController extends AbstractController
40
{
41
    /**
42
     * @var PurchaseFlow
43
     */
44
    protected $purchaseFlow;
45
46
    /**
47
     * @var CustomerFavoriteProductRepository
48
     */
49
    protected $customerFavoriteProductRepository;
50
51
    /**
52
     * @var CartService
53
     */
54
    protected $cartService;
55
56
    /**
57
     * @var ProductRepository
58
     */
59
    protected $productRepository;
60
61
    /**
62
     * @var BaseInfo
63
     */
64
    protected $BaseInfo;
65
66
    /**
67
     * @var AuthenticationUtils
68
     */
69
    protected $helper;
70
71
    private $title = '';
72
73
    /**
74
     * ProductController constructor.
75
     *
76
     * @param PurchaseFlow $cartPurchaseFlow
77
     * @param CustomerFavoriteProductRepository $customerFavoriteProductRepository
78
     * @param CartService $cartService
79
     * @param ProductRepository $productRepository
80
     * @param BaseInfoRepository $baseInfoRepository
81
     * @param AuthenticationUtils $helper
82
     */
83 View Code Duplication
    public function __construct(
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...
84
        PurchaseFlow $cartPurchaseFlow,
85
        CustomerFavoriteProductRepository $customerFavoriteProductRepository,
86
        CartService $cartService,
87
        ProductRepository $productRepository,
88
        BaseInfoRepository $baseInfoRepository,
89
        AuthenticationUtils $helper
90
    ) {
91
        $this->purchaseFlow = $cartPurchaseFlow;
92
        $this->customerFavoriteProductRepository = $customerFavoriteProductRepository;
93
        $this->cartService = $cartService;
94
        $this->productRepository = $productRepository;
95
        $this->BaseInfo = $baseInfoRepository->get();
96
        $this->helper = $helper;
97
    }
98
99
    /**
100
     * 商品一覧画面.
101
     *
102
     * @Route("/products/list", name="product_list")
103
     * @Template("Product/list.twig")
104
     */
105
    public function index(Request $request, Paginator $paginator)
106
    {
107
        // Doctrine SQLFilter
108
        if ($this->BaseInfo->isOptionNostockHidden()) {
109
            $this->entityManager->getFilters()->enable('option_nostock_hidden');
110
        }
111
112
        // handleRequestは空のqueryの場合は無視するため
113
        if ($request->getMethod() === 'GET') {
114
            $request->query->set('pageno', $request->query->get('pageno', ''));
115
        }
116
117
        // searchForm
118
        /* @var $builder \Symfony\Component\Form\FormBuilderInterface */
119
        $builder = $this->formFactory->createNamedBuilder('', SearchProductType::class);
120
121
        if ($request->getMethod() === 'GET') {
122
            $builder->setMethod('GET');
123
        }
124
125
        $event = new EventArgs(
126
            [
127
                'builder' => $builder,
128
            ],
129
            $request
130
        );
131
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_INDEX_INITIALIZE, $event);
132
133
        /* @var $searchForm \Symfony\Component\Form\FormInterface */
134
        $searchForm = $builder->getForm();
135
136
        $searchForm->handleRequest($request);
137
138
        // paginator
139
        $searchData = $searchForm->getData();
140
        $qb = $this->productRepository->getQueryBuilderBySearchData($searchData);
141
142
        $event = new EventArgs(
143
            [
144
                'searchData' => $searchData,
145
                'qb' => $qb,
146
            ],
147
            $request
148
        );
149
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_INDEX_SEARCH, $event);
150
        $searchData = $event->getArgument('searchData');
151
152
        $pagination = $paginator->paginate(
153
            $qb,
154
            !empty($searchData['pageno']) ? $searchData['pageno'] : 1,
155
            $searchData['disp_number']->getId()
156
        );
157
158
        // addCart form
159
        $forms = [];
160 View Code Duplication
        foreach ($pagination as $Product) {
0 ignored issues
show
Bug introduced by
The expression $pagination of type object<Knp\Component\Pag...on\PaginationInterface> is not traversable.
Loading history...
Duplication introduced by
This code seems to be duplicated across 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...
161
            /* @var $builder \Symfony\Component\Form\FormBuilderInterface */
162
            $builder = $this->formFactory->createNamedBuilder(
163
                '',
164
                AddCartType::class,
165
                null,
166
                [
167
                    'product' => $this->productRepository->findWithSortedClassCategories($Product->getId()),
168
                    'allow_extra_fields' => true,
169
                ]
170
            );
171
            $addCartForm = $builder->getForm();
172
173
            $forms[$Product->getId()] = $addCartForm->createView();
174
        }
175
176
        // 表示件数
177
        $builder = $this->formFactory->createNamedBuilder(
178
            'disp_number',
179
            ProductListMaxType::class,
180
            null,
181
            [
182
                'required' => false,
183
                'allow_extra_fields' => true,
184
            ]
185
        );
186
        if ($request->getMethod() === 'GET') {
187
            $builder->setMethod('GET');
188
        }
189
190
        $event = new EventArgs(
191
            [
192
                'builder' => $builder,
193
            ],
194
            $request
195
        );
196
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_INDEX_DISP, $event);
197
198
        $dispNumberForm = $builder->getForm();
199
200
        $dispNumberForm->handleRequest($request);
201
202
        // ソート順
203
        $builder = $this->formFactory->createNamedBuilder(
204
            'orderby',
205
            ProductListOrderByType::class,
206
            null,
207
            [
208
                'required' => false,
209
                'allow_extra_fields' => true,
210
            ]
211
        );
212
        if ($request->getMethod() === 'GET') {
213
            $builder->setMethod('GET');
214
        }
215
216
        $event = new EventArgs(
217
            [
218
                'builder' => $builder,
219
            ],
220
            $request
221
        );
222
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_INDEX_ORDER, $event);
223
224
        $orderByForm = $builder->getForm();
225
226
        $orderByForm->handleRequest($request);
227
228
        $Category = $searchForm->get('category_id')->getData();
229
230
        return [
231
            'subtitle' => $this->getPageTitle($searchData),
232
            'pagination' => $pagination,
233
            'search_form' => $searchForm->createView(),
234
            'disp_number_form' => $dispNumberForm->createView(),
235
            'order_by_form' => $orderByForm->createView(),
236
            'forms' => $forms,
237
            'Category' => $Category,
238
        ];
239
    }
240
241
    /**
242
     * 商品詳細画面.
243
     *
244
     * @Route("/products/detail/{id}", name="product_detail", methods={"GET"}, requirements={"id" = "\d+"})
245
     * @Template("Product/detail.twig")
246
     * @ParamConverter("Product", options={"repository_method" = "findWithSortedClassCategories"})
247
     *
248
     * @param Request $request
249
     * @param Product $Product
250
     *
251
     * @return array
252
     */
253
    public function detail(Request $request, Product $Product)
254
    {
255
        if (!$this->checkVisibility($Product)) {
256
            throw new NotFoundHttpException();
257
        }
258
259
        $builder = $this->formFactory->createNamedBuilder(
260
            '',
261
            AddCartType::class,
262
            null,
263
            [
264
                'product' => $Product,
265
                'id_add_product_id' => false,
266
            ]
267
        );
268
269
        $event = new EventArgs(
270
            [
271
                'builder' => $builder,
272
                'Product' => $Product,
273
            ],
274
            $request
275
        );
276
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_DETAIL_INITIALIZE, $event);
277
278
        $is_favorite = false;
279
        if ($this->isGranted('ROLE_USER')) {
280
            $Customer = $this->getUser();
281
            $is_favorite = $this->customerFavoriteProductRepository->isFavorite($Customer, $Product);
0 ignored issues
show
Documentation introduced by
$Customer is of type null|object, but the function expects a object<Eccube\Entity\Customer>.

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...
282
        }
283
284
        return [
285
            'title' => $this->title,
286
            'subtitle' => $Product->getName(),
287
            'form' => $builder->getForm()->createView(),
288
            'Product' => $Product,
289
            'is_favorite' => $is_favorite,
290
        ];
291
    }
292
293
    /**
294
     * お気に入り追加.
295
     *
296
     * @Route("/products/add_favorite/{id}", name="product_add_favorite", requirements={"id" = "\d+"})
297
     */
298
    public function addFavorite(Request $request, Product $Product)
299
    {
300
        $this->checkVisibility($Product);
301
302
        $event = new EventArgs(
303
            [
304
                'Product' => $Product,
305
            ],
306
            $request
307
        );
308
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_FAVORITE_ADD_INITIALIZE, $event);
309
310
        if ($this->isGranted('ROLE_USER')) {
311
            $Customer = $this->getUser();
312
            $this->customerFavoriteProductRepository->addFavorite($Customer, $Product);
0 ignored issues
show
Documentation introduced by
$Customer is of type null|object, but the function expects a object<Eccube\Entity\Customer>.

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...
313
            $this->session->getFlashBag()->set('product_detail.just_added_favorite', $Product->getId());
314
315
            $event = new EventArgs(
316
                [
317
                    'Product' => $Product,
318
                ],
319
                $request
320
            );
321
            $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_FAVORITE_ADD_COMPLETE, $event);
322
323
            return $this->redirectToRoute('product_detail', ['id' => $Product->getId()]);
324
        } else {
325
            // 非会員の場合、ログイン画面を表示
326
            //  ログイン後の画面遷移先を設定
327
            $this->setLoginTargetPath($this->generateUrl('product_add_favorite', ['id' => $Product->getId()]));
328
            $this->session->getFlashBag()->set('eccube.add.favorite', true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string|array.

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...
329
330
            $event = new EventArgs(
331
                [
332
                    'Product' => $Product,
333
                ],
334
                $request
335
            );
336
            $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_FAVORITE_ADD_COMPLETE, $event);
337
338
            return $this->redirectToRoute('mypage_login');
339
        }
340
    }
341
342
    /**
343
     * カートに追加.
344
     *
345
     * @Route("/products/add_cart/{id}", name="product_add_cart", methods={"POST"}, requirements={"id" = "\d+"})
346
     */
347
    public function addCart(Request $request, Product $Product)
348
    {
349
        // エラーメッセージの配列
350
        $errorMessages = [];
351
        if (!$this->checkVisibility($Product)) {
352
            throw new NotFoundHttpException();
353
        }
354
355
        $builder = $this->formFactory->createNamedBuilder(
356
            '',
357
            AddCartType::class,
358
            null,
359
            [
360
                'product' => $Product,
361
                'id_add_product_id' => false,
362
            ]
363
        );
364
365
        $event = new EventArgs(
366
            [
367
                'builder' => $builder,
368
                'Product' => $Product,
369
            ],
370
            $request
371
        );
372
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_CART_ADD_INITIALIZE, $event);
373
374
        /* @var $form \Symfony\Component\Form\FormInterface */
375
        $form = $builder->getForm();
376
        $form->handleRequest($request);
377
378
        if (!$form->isValid()) {
379
            throw new NotFoundHttpException();
380
        }
381
382
        $addCartData = $form->getData();
383
384
        log_info(
385
            'カート追加処理開始',
386
            [
387
                'product_id' => $Product->getId(),
388
                'product_class_id' => $addCartData['product_class_id'],
389
                'quantity' => $addCartData['quantity'],
390
            ]
391
        );
392
393
        // カートへ追加
394
        $this->cartService->addProduct($addCartData['product_class_id'], $addCartData['quantity']);
395
396
        // 明細の正規化
397
        $Carts = $this->cartService->getCarts();
398 View Code Duplication
        foreach ($Carts as $Cart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
399
            $result = $this->purchaseFlow->validate($Cart, new PurchaseContext($Cart, $this->getUser()));
400
            // 復旧不可のエラーが発生した場合は追加した明細を削除.
401
            if ($result->hasError()) {
402
                $this->cartService->removeProduct($addCartData['product_class_id']);
403
                foreach ($result->getErrors() as $error) {
404
                    $errorMessages[] = $error->getMessage();
405
                }
406
            }
407
            foreach ($result->getWarning() as $warning) {
408
                $errorMessages[] = $warning->getMessage();
409
            }
410
        }
411
412
        $this->cartService->save();
413
414
        log_info(
415
            'カート追加処理完了',
416
            [
417
                'product_id' => $Product->getId(),
418
                'product_class_id' => $addCartData['product_class_id'],
419
                'quantity' => $addCartData['quantity'],
420
            ]
421
        );
422
423
        $event = new EventArgs(
424
            [
425
                'form' => $form,
426
                'Product' => $Product,
427
            ],
428
            $request
429
        );
430
        $this->eventDispatcher->dispatch(EccubeEvents::FRONT_PRODUCT_CART_ADD_COMPLETE, $event);
431
432
        if ($event->getResponse() !== null) {
433
            return $event->getResponse();
434
        }
435
436
        if ($request->isXmlHttpRequest()) {
437
            // ajaxでのリクエストの場合は結果をjson形式で返す。
438
439
            // 初期化
440
            $done = null;
0 ignored issues
show
Unused Code introduced by
$done is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
441
            $messages = [];
442
443
            if (empty($errorMessages)) {
444
                // エラーが発生していない場合
445
                $done = true;
446
                array_push($messages, 'カートに追加しました。');
447
            } else {
448
                // エラーが発生している場合
449
                $done = false;
450
                $messages = $errorMessages;
451
            }
452
453
            return $this->json(['done' => $done, 'messages' => $messages]);
454
        } else {
455
            // ajax以外でのリクエストの場合はカート画面へリダイレクト
456
            foreach ($errorMessages as $errorMessage) {
457
                $this->addRequestError($errorMessage);
458
            }
459
460
            return $this->redirectToRoute('cart');
461
        }
462
    }
463
464
    /**
465
     * ページタイトルの設定
466
     *
467
     * @param  null|array $searchData
468
     *
469
     * @return str
470
     */
471
    private function getPageTitle($searchData)
472
    {
473
        if (isset($searchData['name']) && !empty($searchData['name'])) {
474
            return trans('front.product.search_result');
475
        } elseif (isset($searchData['category_id']) && $searchData['category_id']) {
476
            return $searchData['category_id']->getName();
477
        } else {
478
            return trans('front.product.all_products');
479
        }
480
    }
481
482
    /**
483
     * 閲覧可能な商品かどうかを判定
484
     *
485
     * @param Product $Product
486
     *
487
     * @return boolean 閲覧可能な場合はtrue
488
     */
489
    private function checkVisibility(Product $Product)
490
    {
491
        $is_admin = $this->session->has('_security_admin');
492
493
        // 管理ユーザの場合はステータスやオプションにかかわらず閲覧可能.
494
        if (!$is_admin) {
495
            // 在庫なし商品の非表示オプションが有効な場合.
496
            // if ($this->BaseInfo->isOptionNostockHidden()) {
497
            //     if (!$Product->getStockFind()) {
498
            //         return false;
499
            //     }
500
            // }
501
            // 公開ステータスでない商品は表示しない.
502
            if ($Product->getStatus()->getId() !== ProductStatus::DISPLAY_SHOW) {
503
                return false;
504
            }
505
        }
506
507
        return true;
508
    }
509
}
510