Completed
Pull Request — experimental/3.1 (#2483)
by Kentaro
44:50
created

ProductClassController::edit()   D

Complexity

Conditions 30
Paths 13

Size

Total Lines 258
Code Lines 146

Duplication

Lines 57
Ratio 22.09 %

Code Coverage

Tests 121
CRAP Score 31.0036

Importance

Changes 0
Metric Value
cc 30
eloc 146
nc 13
nop 3
dl 57
loc 258
ccs 121
cts 135
cp 0.8963
crap 31.0036
rs 4.425
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
 * This file is part of EC-CUBE
4
 *
5
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
6
 *
7
 * http://www.lockon.co.jp/
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
 */
23
24
25
namespace Eccube\Controller\Admin\Product;
26
27
use Doctrine\Common\Collections\ArrayCollection;
28
use Doctrine\ORM\EntityManager;
29
use Eccube\Annotation\Inject;
30
use Eccube\Annotation\Component;
31
use Eccube\Application;
32
use Eccube\Common\Constant;
33
use Eccube\Entity\ClassName;
34
use Eccube\Entity\Product;
35
use Eccube\Entity\ProductClass;
36
use Eccube\Entity\ProductStock;
37
use Eccube\Entity\TaxRule;
38
use Eccube\Event\EccubeEvents;
39
use Eccube\Event\EventArgs;
40
use Eccube\Form\Type\Admin\ProductClassType;
41
use Eccube\Repository\BaseInfoRepository;
42
use Eccube\Repository\ClassCategoryRepository;
43
use Eccube\Repository\Master\ProductTypeRepository;
44
use Eccube\Repository\ProductClassRepository;
45
use Eccube\Repository\ProductRepository;
46
use Eccube\Repository\TaxRuleRepository;
47
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
48
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
49
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
50
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
51
use Symfony\Component\EventDispatcher\EventDispatcher;
52
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
53
use Symfony\Component\Form\FormBuilder;
54
use Symfony\Component\Form\FormError;
55
use Symfony\Component\Form\FormFactory;
56
use Symfony\Component\HttpFoundation\RedirectResponse;
57
use Symfony\Component\HttpFoundation\Request;
58
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
59
use Symfony\Component\Validator\Constraints as Assert;
60
61
/**
62
 * @Component
63
 * @Route(service=ProductClassController::class)
64
 */
65
class ProductClassController
66
{
67
    /**
68
     * @Inject(TaxRuleRepository::class)
69
     * @var TaxRuleRepository
70
     */
71
    protected $taxRuleRepository;
72
73
    /**
74
     * @Inject("config")
75
     * @var array
76
     */
77
    protected $appConfig;
78
79
    /**
80
     * @Inject(ProductTypeRepository::class)
81
     * @var ProductTypeRepository
82
     */
83
    protected $productTypeRepository;
84
85
    /**
86
     * @Inject(ClassCategoryRepository::class)
87
     * @var ClassCategoryRepository
88
     */
89
    protected $classCategoryRepository;
90
91
    /**
92
     * @Inject(ProductClassRepository::class)
93
     * @var ProductClassRepository
94
     */
95
    protected $productClassRepository;
96
97
    /**
98
     * @Inject("orm.em")
99
     * @var EntityManager
100
     */
101
    protected $entityManager;
102
103
    /**
104
     * @Inject(BaseInfoRepository::class)
105
     * @var BaseInfoRepository
106
     */
107
    protected $baseInfoRepository;
108
109
    /**
110
     * @Inject("eccube.event.dispatcher")
111
     * @var EventDispatcher
112
     */
113
    protected $eventDispatcher;
114
115
    /**
116
     * @Inject("form.factory")
117
     * @var FormFactory
118
     */
119
    protected $formFactory;
120
121
    /**
122
     * @Inject(ProductRepository::class)
123
     * @var ProductRepository
124
     */
125
    protected $productRepository;
126
127
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
introduced by
Doc comment for parameter "$id" missing
Loading history...
128
     * 商品規格が登録されていなければ新規登録、登録されていれば更新画面を表示する
129
     *
130
     * @Route("/{_admin}/product/product/class/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class")
131
     * @Template("Product/product_class.twig")
132
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
133 10
    public function index(Application $app, Request $request, $id)
134
    {
135
        /** @var $Product \Eccube\Entity\Product */
136 10
        $Product = $this->productRepository->find($id);
137 10
        $hasClassCategoryFlg = false;
138
139 10
        if (!$Product) {
140
            throw new NotFoundHttpException('商品が存在しません');
141
        }
142
143
        // 商品規格情報が存在しなければ新規登録させる
144 10
        if (!$Product->hasProductClass()) {
145
            // 登録画面を表示
146
147 5
            log_info('商品規格新規登録表示', array($id));
148
149 5
            $builder = $this->formFactory->createBuilder();
150
151
            $builder
152 5
                ->add('class_name1', EntityType::class, array(
153 5
                    'class' => 'Eccube\Entity\ClassName',
154 5
                    'choice_label' => 'name',
155 5
                    'placeholder' => '規格1を選択',
156
                    'constraints' => array(
157 5
                        new Assert\NotBlank(),
158
                    ),
159
                ))
160 5
                ->add('class_name2', EntityType::class, array(
161 5
                    'class' => 'Eccube\Entity\ClassName',
162
                    'choice_label' => 'name',
163
                    'placeholder' => '規格2を選択',
164
                    'required' => false,
165
                ));
166
167 5
            $event = new EventArgs(
168
                array(
169 5
                    'builder' => $builder,
170 5
                    'Product' => $Product,
171
                ),
172 5
                $request
173
            );
174 5
            $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_INITIALIZE, $event);
175
176 5
            $form = $builder->getForm();
177
178 5
            $productClassForm = null;
179
180 5
            if ('POST' === $request->getMethod()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
181
182 4
                $form->handleRequest($request);
183
184 4
                if ($form->isValid()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
185
186 4
                    $data = $form->getData();
187
188 4
                    $ClassName1 = $data['class_name1'];
189 4
                    $ClassName2 = $data['class_name2'];
190
191 4
                    log_info('選択された商品規格', array($ClassName1, $ClassName2));
192
193
                    // 各規格が選択されている際に、分類を保有しているか確認
194 4
                    $class1Valied = $this->isValiedCategory($ClassName1);
195 4
                    $class2Valied = $this->isValiedCategory($ClassName2);
196
197
                    // 規格が選択されていないか、選択された状態で分類が保有されていれば、画面表示
198 4
                    if($class1Valied && $class2Valied){
199 4
                        $hasClassCategoryFlg = true;
200
                    }
201
202 4
                    if (!is_null($ClassName2) && $ClassName1->getId() == $ClassName2->getId()) {
203
                        // 規格1と規格2が同じ値はエラー
204
                        $form['class_name2']->addError(new FormError('規格1と規格2は、同じ値を使用できません。'));
205
                    } else {
206
                        // 規格分類が設定されていない商品規格を取得
207 4
                        $orgProductClasses = $Product->getProductClasses();
208 4
                        $sourceProduct = $orgProductClasses[0];
209
210
                        // 規格分類が組み合わされた商品規格を取得
211 4
                        $ProductClasses = $this->createProductClasses($app, $Product, $ClassName1, $ClassName2);
212
213
                        // 組み合わされた商品規格にデフォルト値をセット
214 4
                        foreach ($ProductClasses as $productClass) {
215 4
                            $this->setDefaultProductClass($app, $productClass, $sourceProduct);
216
                        }
217
218 4
                        $builder = $this->formFactory->createBuilder();
219
220
                        $builder
221 4
                            ->add('product_classes', CollectionType::class, array(
222 4
                                'entry_type' => ProductClassType::class,
223
                                'allow_add' => true,
224
                                'allow_delete' => true,
225 4
                                'data' => $ProductClasses,
226
                             ));
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 28 spaces, but found 29.
Loading history...
227
228 4
                        $event = new EventArgs(
229
                            array(
230 4
                                'builder' => $builder,
231 4
                                'Product' => $Product,
232 4
                                'ProductClasses' => $ProductClasses,
233
                            ),
234 4
                            $request
235
                        );
236 4
                        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_CLASSES, $event);
237
238 4
                        $productClassForm = $builder->getForm()->createView();
239
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
240
                    }
241
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
242
                }
243
            }
244
245
            return [
246 5
                'form' => $form->createView(),
247 5
                'classForm' => $productClassForm,
248 5
                'Product' => $Product,
249
                'not_product_class' => true,
250
                'error' => null,
251 5
                'has_class_category_flg' => $hasClassCategoryFlg,
252
            ];
253
        } else {
254
            // 既に商品規格が登録されている場合、商品規格画面を表示する
255
256 6
            log_info('商品規格登録済表示', array($id));
257
258
            // 既に登録されている商品規格を取得
259 6
            $ProductClasses = $this->getProductClassesExcludeNonClass($Product);
260
261
            // 設定されている規格分類1、2を取得(商品規格の規格分類には必ず同じ値がセットされている)
262 6
            $ProductClass = $ProductClasses[0];
263 6
            $ClassName1 = $ProductClass->getClassCategory1()->getClassName();
264 6
            $ClassName2 = null;
265 6
            if (!is_null($ProductClass->getClassCategory2())) {
266 5
                $ClassName2 = $ProductClass->getClassCategory2()->getClassName();
267
            }
268
269
            // 規格分類が組み合わされた空の商品規格を取得
270 6
            $createProductClasses = $this->createProductClasses($app, $Product, $ClassName1, $ClassName2);
271
272 6
            $mergeProductClasses = array();
273
274
            // 商品税率が設定されている場合、商品税率を項目に設定
275 6
            $BaseInfo = $this->baseInfoRepository->get();
276 6 View Code Duplication
            if ($BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
277 6
                foreach ($ProductClasses as $class) {
278 6
                    if ($class->getTaxRule() && !$class->getTaxRule()->getDelFlg()) {
279 6
                        $class->setTaxRate($class->getTaxRule()->getTaxRate());
280
                    }
281
                }
282
            }
283
284
            // 登録済み商品規格と空の商品規格をマージ
285 6
            $flag = false;
286 6
            foreach ($createProductClasses as $createProductClass) {
287
                // 既に登録済みの商品規格にチェックボックスを設定
288 6
                foreach ($ProductClasses as $productClass) {
289 6
                    if ($productClass->getClassCategory1() == $createProductClass->getClassCategory1() &&
290 6
                            $productClass->getClassCategory2() == $createProductClass->getClassCategory2()) {
291
                                // チェックボックスを追加
292 6
                                $productClass->setAdd(true);
293 6
                                $flag = true;
294 6
                                break;
295
                    }
296
                }
297
298 6
                if (!$flag) {
299
                    $mergeProductClasses[] = $createProductClass;
300
                }
301
302 6
                $flag = false;
303
            }
304
305
            // 登録済み商品規格と空の商品規格をマージ
306 6
            foreach ($mergeProductClasses as $mergeProductClass) {
307
                // 空の商品規格にデフォルト値を設定
308
                $this->setDefaultProductClass($app, $mergeProductClass, $ProductClass);
309
                $ProductClasses->add($mergeProductClass);
0 ignored issues
show
Bug introduced by
The method add cannot be called on $ProductClasses (of type array<integer,object<Eccube\Entity\ProductClass>>).

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...
310
            }
311
312 6
            $builder = $this->formFactory->createBuilder();
313
314
            $builder
315 6
                ->add('product_classes', CollectionType::class, array(
316 6
                    'entry_type' => ProductClassType::class,
317
                    'allow_add' => true,
318
                    'allow_delete' => true,
319 6
                    'data' => $ProductClasses,
320
                ));
321
322 6
            $event = new EventArgs(
323
                array(
324 6
                    'builder' => $builder,
325 6
                    'Product' => $Product,
326 6
                    'ProductClasses' => $ProductClasses,
327
                ),
328 6
                $request
329
            );
330 6
            $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_CLASSES, $event);
331
332 6
            $productClassForm = $builder->getForm()->createView();
333
334
            return [
335 6
                'classForm' => $productClassForm,
336 6
                'Product' => $Product,
337 6
                'class_name1' => $ClassName1,
338 6
                'class_name2' => $ClassName2,
339
                'not_product_class' => false,
340
                'error' => null,
341
                'has_class_category_flg' => true,
342
            ];
343
        }
344
    }
345
346
    /**
347
     * 商品規格の登録、更新、削除を行う
348
     *
349
     * @Route("/{_admin}/product/product/class/edit/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class_edit")
350
     * @Template("Product/product_class.twig")
351
     *
352
     * @param Application $app
353
     * @param Request     $request
354
     * @param int         $id
355
     * @return RedirectResponse
356
     */
357 11
    public function edit(Application $app, Request $request, $id)
358
    {
359
360
        /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */
361 11
        $softDeleteFilter = $this->entityManager->getFilters()->getFilter('soft_delete');
362 11
        $softDeleteFilter->setExcludes(array(
363 11
            'Eccube\Entity\TaxRule',
364
        ));
365
366
        /** @var $Product \Eccube\Entity\Product */
367 11
        $Product = $this->productRepository->find($id);
368
369 11
        if (!$Product) {
370
            throw new NotFoundHttpException('商品が存在しません');
371
        }
372
373
        /* @var FormBuilder $builder */
374 11
        $builder = $this->formFactory->createBuilder();
375 11
        $builder->add('product_classes', CollectionType::class, array(
376 11
                    'entry_type' => ProductClassType::class,
377
                    'allow_add' => true,
378
                    'allow_delete' => true,
379
        ));
380
381 11
        $event = new EventArgs(
382
            array(
383 11
                'builder' => $builder,
384 11
                'Product' => $Product,
385
            ),
386 11
            $request
387
        );
388 11
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_INITIALIZE, $event);
389
390 11
        $form = $builder->getForm();
391
392 11
        $ProductClasses = $this->getProductClassesExcludeNonClass($Product);
393
394 11
        $form->handleRequest($request);
395 11
        if ($form->isSubmitted()) {
396 10
            switch ($request->get('mode')) {
397 10
                case 'edit':
398
                    // 新規登録
399 4
                    log_info('商品規格新規登録開始', array($id));
400
401 4 View Code Duplication
                    if (count($ProductClasses) > 0) {
402
                        // 既に登録されていれば最初の画面に戻す
403
                        log_info('商品規格登録済', array($id));
404
                        return $app->redirect($app->url('admin_product_product_class', array('id' => $id)));
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
405
                    }
406
407 4
                    $addProductClasses = array();
408
409 4
                    $tmpProductClass = null;
410 4 View Code Duplication
                    foreach ($form->get('product_classes') as $formData) {
411
                        // 追加対象の行をvalidate
412 4
                        $ProductClass = $formData->getData();
413
414 4
                        if ($ProductClass->getAdd()) {
415 4
                            if ($formData->isValid()) {
416 3
                                $addProductClasses[] = $ProductClass;
417
                            } else {
418
                                // 対象行のエラー
419 1
                                return $this->render($app, $Product, $ProductClass, true, $form);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->render($ap...uctClass, true, $form); (array) is incompatible with the return type documented by Eccube\Controller\Admin\...ctClassController::edit of type Symfony\Component\HttpFoundation\RedirectResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
420
                            }
421
                        }
422 3
                        $tmpProductClass = $ProductClass;
423
                    }
424
425 3 View Code Duplication
                    if (count($addProductClasses) == 0) {
426
                        // 対象がなければエラー
427
                        log_info('商品規格が未選択', array($id));
428
                        $error = array('message' => '商品規格が選択されていません。');
429
                        return $this->render($app, $Product, $tmpProductClass, true, $form, $error);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->render($ap..., true, $form, $error); (array) is incompatible with the return type documented by Eccube\Controller\Admin\...ctClassController::edit of type Symfony\Component\HttpFoundation\RedirectResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
introduced by
Missing blank line before return statement
Loading history...
430
                    }
431
432
                    // 選択された商品規格を登録
433 3
                    $this->insertProductClass($app, $Product, $addProductClasses);
0 ignored issues
show
Documentation introduced by
$addProductClasses is of type array, but the function expects a object<Doctrine\Common\C...ctions\ArrayCollection>.

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...
434
435
                    // デフォルトの商品規格を更新
436 3
                    $defaultProductClass = $this->productClassRepository
437 3
                            ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null));
438
439 3
                    $defaultProductClass->setDelFlg(Constant::ENABLED);
440
441 3
                    $this->entityManager->flush();
442
443 3
                    log_info('商品規格新規登録完了', array($id));
444
445 3
                    $event = new EventArgs(
446
                        array(
447 3
                            'form' => $form,
448 3
                            'Product' => $Product,
449 3
                            'defaultProductClass' => $defaultProductClass,
450
                        ),
451 3
                        $request
452
                    );
453 3
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_COMPLETE, $event);
454
455 3
                    $app->addSuccess('admin.product.product_class.save.complete', 'admin');
456
457 3
                    break;
458 6
                case 'update':
459
                    // 更新
460 5
                    log_info('商品規格更新開始', array($id));
461
462 5 View Code Duplication
                    if (count($ProductClasses) == 0) {
463
                        // 商品規格が0件であれば最初の画面に戻す
464
                        log_info('商品規格が存在しません', array($id));
465
                        return $app->redirect($app->url('admin_product_product_class', array('id' => $id)));
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
466
                    }
467
468 5
                    $checkProductClasses = array();
469 5
                    $removeProductClasses = array();
470
471 5
                    $tempProductClass = null;
472 5 View Code Duplication
                    foreach ($form->get('product_classes') as $formData) {
473
                        // 追加対象の行をvalidate
474 5
                        $ProductClass = $formData->getData();
475
476 5
                        if ($ProductClass->getAdd()) {
477 5
                            if ($formData->isValid()) {
478 4
                                $checkProductClasses[] = $ProductClass;
479
                            } else {
480 5
                                return $this->render($app, $Product, $ProductClass, false, $form);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->render($ap...ctClass, false, $form); (array) is incompatible with the return type documented by Eccube\Controller\Admin\...ctClassController::edit of type Symfony\Component\HttpFoundation\RedirectResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
481
                            }
482
                        } else {
483
                            // 削除対象の行
484 1
                            $removeProductClasses[] = $ProductClass;
485
                        }
486 4
                        $tempProductClass = $ProductClass;
487
                    }
488
489 4 View Code Duplication
                    if (count($checkProductClasses) == 0) {
490
                        // 対象がなければエラー
491
                        log_info('商品規格が存在しません', array($id));
492
                        $error = array('message' => '商品規格が選択されていません。');
493
                        return $this->render($app, $Product, $tempProductClass, false, $form, $error);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->render($ap... false, $form, $error); (array) is incompatible with the return type documented by Eccube\Controller\Admin\...ctClassController::edit of type Symfony\Component\HttpFoundation\RedirectResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
introduced by
Missing blank line before return statement
Loading history...
494
                    }
495
496
497
                    // 登録対象と更新対象の行か判断する
498 4
                    $addProductClasses = array();
499 4
                    $updateProductClasses = array();
500 4
                    foreach ($checkProductClasses as $cp) {
501 4
                        $flag = false;
502
503
                        // 既に登録済みの商品規格か確認
504 4
                        foreach ($ProductClasses as $productClass) {
505 4
                            if ($productClass->getProduct()->getId() == $id &&
506 4
                                    $productClass->getClassCategory1() == $cp->getClassCategory1() &&
507 4
                                    $productClass->getClassCategory2() == $cp->getClassCategory2()) {
508 4
                                $updateProductClasses[] = $cp;
509
510
                                // 商品情報
511 4
                                $cp->setProduct($Product);
512
                                // 商品在庫
513 4
                                $productStock = $productClass->getProductStock();
514 4
                                if (!$cp->getStockUnlimited()) {
515 1
                                    $productStock->setStock($cp->getStock());
516
                                } else {
517 3
                                    $productStock->setStock(null);
518
                                }
519 4
                                $this->setDefaultProductClass($app, $productClass, $cp);
520 4
                                $flag = true;
521 4
                                break;
522
                            }
523
                        }
524 4
                        if (!$flag) {
525 4
                            $addProductClasses[] = $cp;
526
                        }
527
                    }
528
529 4
                    foreach ($removeProductClasses as $rc) {
530
                        // 登録されている商品規格に削除フラグをセット
531 1
                        foreach ($ProductClasses as $productClass) {
532 1
                            if ($productClass->getProduct()->getId() == $id &&
533 1
                                    $productClass->getClassCategory1() == $rc->getClassCategory1() &&
534 1
                                    $productClass->getClassCategory2() == $rc->getClassCategory2()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
535
536 1
                                $productClass->setDelFlg(Constant::ENABLED);
537 1
                                break;
538
                            }
539
                        }
540
                    }
541
542
                    // 選択された商品規格を登録
543 4
                    $this->insertProductClass($app, $Product, $addProductClasses);
0 ignored issues
show
Documentation introduced by
$addProductClasses is of type array, but the function expects a object<Doctrine\Common\C...ctions\ArrayCollection>.

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...
544
545 4
                    $this->entityManager->flush();
546
547 4
                    log_info('商品規格更新完了', array($id));
548
549 4
                    $event = new EventArgs(
550
                        array(
551 4
                            'form' => $form,
552 4
                            'Product' => $Product,
553 4
                            'updateProductClasses' => $updateProductClasses,
554 4
                            'addProductClasses' => $addProductClasses,
555
                        ),
556 4
                        $request
557
                    );
558 4
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_UPDATE, $event);
559
560 4
                    $app->addSuccess('admin.product.product_class.update.complete', 'admin');
561
562 4
                    break;
563
564 1
                case 'delete':
565
                    // 削除
566 1
                    log_info('商品規格削除開始', array($id));
567
568 1 View Code Duplication
                    if (count($ProductClasses) == 0) {
569
                        // 既に商品が削除されていれば元の画面に戻す
570
                        log_info('商品規格が存在しません', array($id));
571
                        return $app->redirect($app->url('admin_product_product_class', array('id' => $id)));
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
572
                    }
573
574 1
                    foreach ($ProductClasses as $ProductClass) {
575
                        // 登録されている商品規格に削除フラグをセット
576 1
                        $ProductClass->setDelFlg(Constant::ENABLED);
577
                    }
578
579
                    /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */
580 1
                    $softDeleteFilter = $this->entityManager->getFilters()->getFilter('soft_delete');
581 1
                    $softDeleteFilter->setExcludes(array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
582 1
                        'Eccube\Entity\ProductClass'
583
                    ));
584
585
                    // デフォルトの商品規格を更新
586 1
                    $defaultProductClass = $this->productClassRepository
587 1
                            ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null, 'del_flg' => Constant::ENABLED));
588
589 1
                    $defaultProductClass->setDelFlg(Constant::DISABLED);
590
591 1
                    $this->entityManager->flush();
592 1
                    log_info('商品規格削除完了', array($id));
593
594 1
                    $event = new EventArgs(
595
                        array(
596 1
                            'form' => $form,
597 1
                            'Product' => $Product,
598 1
                            'defaultProductClass' => $defaultProductClass,
599
                        ),
600 1
                        $request
601
                    );
602 1
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_DELETE, $event);
603
604 1
                    $app->addSuccess('admin.product.product_class.delete.complete', 'admin');
605
606 1
                    break;
607
                default:
608
                    break;
609
            }
610
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
611
        }
612
613 9
        return $app->redirect($app->url('admin_product_product_class', array('id' => $id)));
614
    }
615
616
    /**
617
     * 登録、更新時のエラー画面表示
618
     *
619
     */
620 2
    protected function render($app, $Product, $ProductClass, $not_product_class, $classForm, $error = null)
621
    {
622
623 2
        $ClassName1 = null;
624 2
        $ClassName2 = null;
625
        // 規格を取得
626 2
        if (isset($ProductClass)) {
627 2
            $ClassCategory1 = $ProductClass->getClassCategory1();
628 2
            if ($ClassCategory1) {
629 2
                $ClassName1 = $ClassCategory1->getClassName();
630
            }
631 2
            $ClassCategory2 = $ProductClass->getClassCategory2();
632 2
            if ($ClassCategory2) {
633 1
                $ClassName2 = $ClassCategory2->getClassName();
634
            }
635
        }
636
637 2
        $form = $app->form()
638 2
            ->add('class_name1', EntityType::class, array(
639 2
                'class' => 'Eccube\Entity\ClassName',
640 2
                'choice_label' => 'name',
641 2
                'placeholder' => '規格1を選択',
642 2
                'data' => $ClassName1,
643
            ))
644 2
            ->add('class_name2', EntityType::class, array(
645 2
                'class' => 'Eccube\Entity\ClassName',
646 2
                'choice_label' => 'name',
647 2
                'placeholder' => '規格2を選択',
648 2
                'data' => $ClassName2,
649
            ))
650 2
            ->getForm();
651
652 2
        log_info('商品規格登録エラー');
653
654
655
        return [
656 2
            'form' => $form->createView(),
657 2
            'classForm' => $classForm->createView(),
658 2
            'Product' => $Product,
659 2
            'class_name1' => $ClassName1,
660 2
            'class_name2' => $ClassName2,
661 2
            'not_product_class' => $not_product_class,
662 2
            'error' => $error,
663
            'has_class_category_flg' => true,
664
        ];
665
    }
666
667
668
    /**
669
     * 規格1と規格2を組み合わせた商品規格を作成
670
     */
671 10
    private function createProductClasses($app, Product $Product, ClassName $ClassName1 = null, ClassName $ClassName2 = null)
672
    {
673
674 10
        $ClassCategories1 = array();
675 10
        if ($ClassName1) {
676 10
            $ClassCategories1 = $this->classCategoryRepository->findBy(array('ClassName' => $ClassName1));
677
        }
678
679 10
        $ClassCategories2 = array();
680 10
        if ($ClassName2) {
681 5
            $ClassCategories2 = $this->classCategoryRepository->findBy(array('ClassName' => $ClassName2));
682
        }
683
684 10
        $ProductClasses = array();
685 10
        foreach ($ClassCategories1 as $ClassCategory1) {
686 10
            if ($ClassCategories2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ClassCategories2 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
687 5
                foreach ($ClassCategories2 as $ClassCategory2) {
688 5
                    $ProductClass = $this->newProductClass($app);
689 5
                    $ProductClass->setProduct($Product);
690 5
                    $ProductClass->setClassCategory1($ClassCategory1);
691 5
                    $ProductClass->setClassCategory2($ClassCategory2);
692 5
                    $ProductClass->setTaxRate(null);
693 5
                    $ProductClass->setDelFlg(Constant::DISABLED);
694 5
                    $ProductClasses[] = $ProductClass;
695
                }
696
            } else {
697 5
                $ProductClass = $this->newProductClass($app);
698 5
                $ProductClass->setProduct($Product);
699 5
                $ProductClass->setClassCategory1($ClassCategory1);
700 5
                $ProductClass->setTaxRate(null);
701 5
                $ProductClass->setDelFlg(Constant::DISABLED);
702 10
                $ProductClasses[] = $ProductClass;
703
            }
704
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
705
        }
706 10
        return $ProductClasses;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
707
    }
708
709
    /**
710
     * 新しい商品規格を作成
711
     */
712 10
    private function newProductClass(Application $app)
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
713
    {
714 10
        $ProductType = $this->productTypeRepository->find($this->appConfig['product_type_normal']);
715
716 10
        $ProductClass = new ProductClass();
717 10
        $ProductClass->setProductType($ProductType);
718 10
        return $ProductClass;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
719
    }
720
721
    /**
722
     * 商品規格のコピーを取得.
723
     *
724
     * @see http://symfony.com/doc/current/cookbook/form/form_collections.html
725
     * @param Product $Product
726
     * @return \Eccube\Entity\ProductClass[]
727
     */
728
    private function getProductClassesOriginal(Product $Product)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
729
    {
730
        $ProductClasses = $Product->getProductClasses();
731
        return $ProductClasses->filter(function($ProductClass) {
0 ignored issues
show
Unused Code introduced by
The parameter $ProductClass is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
introduced by
Missing blank line before return statement
Loading history...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
732
            return true;
733
        });
734
    }
735
736
    /**
737
     * 規格なし商品を除いて商品規格を取得.
738
     *
739
     * @param Product $Product
740
     * @return \Eccube\Entity\ProductClass[]
741
     */
742 11
    private function getProductClassesExcludeNonClass(Product $Product)
743
    {
744 11
        $ProductClasses = $Product->getProductClasses();
745 11
        return $ProductClasses->filter(function($ProductClass) {
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
746 11
            $ClassCategory1 = $ProductClass->getClassCategory1();
747 11
            $ClassCategory2 = $ProductClass->getClassCategory2();
748 11
            return ($ClassCategory1 || $ClassCategory2);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
749 11
        });
750
    }
751
752
    /**
753
     * デフォルトとなる商品規格を設定
754
     *
755
     * @param $productClassDest コピー先となる商品規格
756
     * @param $productClassOrig コピー元となる商品規格
757
     */
758 8
    private function setDefaultProductClass($app, $productClassDest, $productClassOrig) {
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
759 8
        $productClassDest->setDeliveryDate($productClassOrig->getDeliveryDate());
760 8
        $productClassDest->setProduct($productClassOrig->getProduct());
761 8
        $productClassDest->setProductType($productClassOrig->getProductType());
762 8
        $productClassDest->setCode($productClassOrig->getCode());
763 8
        $productClassDest->setStock($productClassOrig->getStock());
764 8
        $productClassDest->setStockUnlimited($productClassOrig->getStockUnlimited());
765 8
        $productClassDest->setSaleLimit($productClassOrig->getSaleLimit());
766 8
        $productClassDest->setPrice01($productClassOrig->getPrice01());
767 8
        $productClassDest->setPrice02($productClassOrig->getPrice02());
768 8
        $productClassDest->setDeliveryFee($productClassOrig->getDeliveryFee());
769
770
        // 個別消費税
771 8
        $BaseInfo = $this->baseInfoRepository->get();
772 8
        if ($BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
773 8
            if ($productClassOrig->getTaxRate() !== false && $productClassOrig->getTaxRate() !== null) {
774 3
                $productClassDest->setTaxRate($productClassOrig->getTaxRate());
775 3 View Code Duplication
                if ($productClassDest->getTaxRule()) {
776
                    $productClassDest->getTaxRule()->setTaxRate($productClassOrig->getTaxRate());
777
                    $productClassDest->getTaxRule()->setDelFlg(Constant::DISABLED);
778
                } else {
779 3
                    $taxrule = $this->taxRuleRepository->newTaxRule();
780 3
                    $taxrule->setTaxRate($productClassOrig->getTaxRate());
781 3
                    $taxrule->setApplyDate(new \DateTime());
782 3
                    $taxrule->setProduct($productClassDest->getProduct());
783 3
                    $taxrule->setProductClass($productClassDest);
784 3
                    $productClassDest->setTaxRule($taxrule);
785
                }
786
            } else {
787 8
                if ($productClassDest->getTaxRule()) {
788
                    $productClassDest->getTaxRule()->setDelFlg(Constant::ENABLED);
789
                }
790
            }
791
        }
792
    }
793
794
795
    /**
796
     * 商品規格を登録
797
     *
798
     * @param Application     $app
799
     * @param Product         $Product
800
     * @param ArrayCollection $ProductClasses 登録される商品規格
801
     */
802 7
    private function insertProductClass($app, $Product, $ProductClasses) {
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
803
804 7
        $BaseInfo = $this->baseInfoRepository->get();
805
806
        // 選択された商品を登録
807 7
        foreach ($ProductClasses as $ProductClass) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
808
809 3
            $ProductClass->setDelFlg(Constant::DISABLED);
810 3
            $ProductClass->setProduct($Product);
811 3
            $this->entityManager->persist($ProductClass);
812
813
            // 在庫情報を作成
814 3
            $ProductStock = new ProductStock();
815 3
            $ProductClass->setProductStock($ProductStock);
816 3
            $ProductStock->setProductClass($ProductClass);
817 3
            if (!$ProductClass->getStockUnlimited()) {
818 3
                $ProductStock->setStock($ProductClass->getStock());
819
            } else {
820
                // 在庫無制限時はnullを設定
821
                $ProductStock->setStock(null);
822
            }
823 3
            $this->entityManager->persist($ProductStock);
824
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
825
        }
826
827
        // 商品税率が設定されている場合、商品税率をセット
828 7
        if ($BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
829
            // 初期設定の税設定.
830 7
            $TaxRule = $this->taxRuleRepository->find(TaxRule::DEFAULT_TAX_RULE_ID);
831
            // 初期税率設定の計算方法を設定する
832 7
            $CalcRule = $TaxRule->getCalcRule();
833 7
            foreach ($ProductClasses as $ProductClass) {
834 3
                if ($ProductClass && is_numeric($taxRate = $ProductClass->getTaxRate())) {
835 2
                    $TaxRule = new TaxRule();
836 2
                    $TaxRule->setProduct($Product);
837 2
                    $TaxRule->setProductClass($ProductClass);
838 2
                    $TaxRule->setCalcRule($CalcRule);
839 2
                    $TaxRule->setTaxRate($taxRate);
840 2
                    $TaxRule->setTaxAdjust(0);
841 2
                    $TaxRule->setApplyDate(new \DateTime());
842 2
                    $TaxRule->setDelFlg(Constant::DISABLED);
843 3
                    $this->entityManager->persist($TaxRule);
844
                }
845
            }
846
        }
847
848
    }
849
850
    /**
851
     * 規格の分類判定
852
     *
853
     * @param $class_name
854
     * @return boolean
855
     */
856 4
    private function isValiedCategory($class_name)
857
    {
858 4
        if (empty($class_name)) {
859 4
            return true;
860
        }
861 4
        if (count($class_name->getClassCategories()) < 1) {
862
            return false;
863
        }
864 4
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
865
    }
866
}
867