Failed Conditions
Pull Request — experimental/3.1 (#2521)
by Kiyotaka
83:08 queued 76:21
created

ProductClassController   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 786
Duplicated Lines 8.52 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 92.07%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 67
loc 786
ccs 325
cts 353
cp 0.9207
rs 1.0434
c 1
b 0
f 0
wmc 80
lcom 1
cbo 19

10 Methods

Rating   Name   Duplication   Size   Complexity  
D index() 0 211 20
D edit() 57 246 30
B render() 0 46 4
B createProductClasses() 0 37 6
A newProductClass() 0 8 1
A getProductClassesOriginal() 0 7 1
A getProductClassesExcludeNonClass() 0 9 2
B setDefaultProductClass() 10 34 6
C insertProductClass() 0 45 7
A isValiedCategory() 0 10 3

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ProductClassController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ProductClassController, and based on these observations, apply Extract Interface, too.

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\Common\Collections\Collection;
29
use Doctrine\ORM\EntityManager;
30
use Eccube\Annotation\Component;
31
use Eccube\Annotation\Inject;
32
use Eccube\Application;
33
use Eccube\Common\Constant;
34
use Eccube\Entity\BaseInfo;
35
use Eccube\Entity\ClassName;
36
use Eccube\Entity\Product;
37
use Eccube\Entity\ProductClass;
38
use Eccube\Entity\ProductStock;
39
use Eccube\Entity\TaxRule;
40
use Eccube\Event\EccubeEvents;
41
use Eccube\Event\EventArgs;
42
use Eccube\Form\Type\Admin\ProductClassType;
43
use Eccube\Repository\ClassCategoryRepository;
44
use Eccube\Repository\Master\ProductTypeRepository;
45
use Eccube\Repository\ProductClassRepository;
46
use Eccube\Repository\ProductRepository;
47
use Eccube\Repository\TaxRuleRepository;
48
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
49
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
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(BaseInfo::class)
105
     * @var BaseInfo
106
     */
107
    protected $BaseInfo;
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 4
            log_info('商品規格新規登録表示', array($id));
148
149 4
            $builder = $this->formFactory->createBuilder();
150
151
            $builder
152 4
                ->add('class_name1', EntityType::class, array(
153 4
                    'class' => 'Eccube\Entity\ClassName',
154 4
                    'choice_label' => 'name',
155 4
                    'placeholder' => '規格1を選択',
156
                    'constraints' => array(
157 4
                        new Assert\NotBlank(),
158
                    ),
159
                ))
160 4
                ->add('class_name2', EntityType::class, array(
161 4
                    'class' => 'Eccube\Entity\ClassName',
162
                    'choice_label' => 'name',
163
                    'placeholder' => '規格2を選択',
164
                    'required' => false,
165
                ));
166
167 4
            $event = new EventArgs(
168
                array(
169 4
                    'builder' => $builder,
170 4
                    'Product' => $Product,
171
                ),
172 4
                $request
173
            );
174 4
            $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_INITIALIZE, $event);
175
176 4
            $form = $builder->getForm();
177
178 4
            $productClassForm = null;
179
180 4
            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 4
                'form' => $form->createView(),
247 4
                'classForm' => $productClassForm,
248 4
                'Product' => $Product,
249
                'not_product_class' => true,
250
                'error' => null,
251 4
                '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->first();
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
            if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
276 6
                foreach ($ProductClasses as $class) {
277 6
                    if ($class->getTaxRule()) {
278 6
                        $class->setTaxRate($class->getTaxRule()->getTaxRate());
279
                    }
280
                }
281
            }
282
283
            // 登録済み商品規格と空の商品規格をマージ
284 6
            $flag = false;
285 6
            foreach ($createProductClasses as $createProductClass) {
286
                // 既に登録済みの商品規格にチェックボックスを設定
287 6
                foreach ($ProductClasses as $productClass) {
288 6
                    if ($productClass->getClassCategory1() == $createProductClass->getClassCategory1() &&
289 6
                            $productClass->getClassCategory2() == $createProductClass->getClassCategory2()) {
290
                                // チェックボックスを追加
291 6
                                $productClass->setAdd(true);
292 6
                                $flag = true;
293 6
                                break;
294
                    }
295
                }
296
297 6
                if (!$flag) {
298
                    $mergeProductClasses[] = $createProductClass;
299
                }
300
301 6
                $flag = false;
302
            }
303
304
            // 登録済み商品規格と空の商品規格をマージ
305 6
            foreach ($mergeProductClasses as $mergeProductClass) {
306
                // 空の商品規格にデフォルト値を設定
307
                $this->setDefaultProductClass($app, $mergeProductClass, $ProductClass);
308
                $ProductClasses->add($mergeProductClass);
309
            }
310
311 6
            $builder = $this->formFactory->createBuilder();
312
313
            $builder
314 6
                ->add('product_classes', CollectionType::class, array(
315 6
                    'entry_type' => ProductClassType::class,
316
                    'allow_add' => true,
317
                    'allow_delete' => true,
318 6
                    'data' => $ProductClasses,
319
                ));
320
321 6
            $event = new EventArgs(
322
                array(
323 6
                    'builder' => $builder,
324 6
                    'Product' => $Product,
325 6
                    'ProductClasses' => $ProductClasses,
326
                ),
327 6
                $request
328
            );
329 6
            $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_CLASSES, $event);
330
331 6
            $productClassForm = $builder->getForm()->createView();
332
333
            return [
334 6
                'classForm' => $productClassForm,
335 6
                'Product' => $Product,
336 6
                'class_name1' => $ClassName1,
337 6
                'class_name2' => $ClassName2,
338
                'not_product_class' => false,
339
                'error' => null,
340
                'has_class_category_flg' => true,
341
            ];
342
        }
343
    }
344
345
    /**
346
     * 商品規格の登録、更新、削除を行う
347
     *
348
     * @Route("/{_admin}/product/product/class/edit/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class_edit")
349
     * @Template("Product/product_class.twig")
350
     *
351
     * @param Application $app
352
     * @param Request     $request
353
     * @param int         $id
354
     * @return RedirectResponse
355
     */
356 11
    public function edit(Application $app, Request $request, $id)
357
    {
358
        /** @var $Product \Eccube\Entity\Product */
359 11
        $Product = $this->productRepository->find($id);
360
361 11
        if (!$Product) {
362
            throw new NotFoundHttpException('商品が存在しません');
363
        }
364
365
        /* @var FormBuilder $builder */
366 11
        $builder = $this->formFactory->createBuilder();
367 11
        $builder->add('product_classes', CollectionType::class, array(
368 11
                    'entry_type' => ProductClassType::class,
369
                    'allow_add' => true,
370
                    'allow_delete' => true,
371
        ));
372
373 11
        $event = new EventArgs(
374
            array(
375 11
                'builder' => $builder,
376 11
                'Product' => $Product,
377
            ),
378 11
            $request
379
        );
380 11
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_INITIALIZE, $event);
381
382 11
        $form = $builder->getForm();
383
384 11
        $ProductClasses = $this->getProductClassesExcludeNonClass($Product);
385
386 11
        $form->handleRequest($request);
387 11
        if ($form->isSubmitted()) {
388 10
            switch ($request->get('mode')) {
389 10
                case 'edit':
390
                    // 新規登録
391 4
                    log_info('商品規格新規登録開始', array($id));
392
393 4 View Code Duplication
                    if (count($ProductClasses) > 0) {
394
                        // 既に登録されていれば最初の画面に戻す
395
                        log_info('商品規格登録済', array($id));
396
                        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...
397
                    }
398
399 4
                    $addProductClasses = array();
400
401 4
                    $tmpProductClass = null;
402 4 View Code Duplication
                    foreach ($form->get('product_classes') as $formData) {
403
                        // 追加対象の行をvalidate
404 4
                        $ProductClass = $formData->getData();
405
406 4
                        if ($ProductClass->getAdd()) {
407 4
                            if ($formData->isValid()) {
408 3
                                $addProductClasses[] = $ProductClass;
409
                            } else {
410
                                // 対象行のエラー
411 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...
412
                            }
413
                        }
414 3
                        $tmpProductClass = $ProductClass;
415
                    }
416
417 3 View Code Duplication
                    if (count($addProductClasses) == 0) {
418
                        // 対象がなければエラー
419
                        log_info('商品規格が未選択', array($id));
420
                        $error = array('message' => '商品規格が選択されていません。');
421
                        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...
422
                    }
423
424
                    // 選択された商品規格を登録
425 3
                    $this->insertProductClass($app, $Product, $addProductClasses);
426
427
                    // デフォルトの商品規格を非表示
428
                    /** @var ProductClass $defaultProductClass */
429 3
                    $defaultProductClass = $this->productClassRepository
430 3
                            ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null));
431 3
                    $defaultProductClass->setVisible(false);
432
433 3
                    $this->entityManager->flush();
434
435 3
                    log_info('商品規格新規登録完了', array($id));
436
437 3
                    $event = new EventArgs(
438
                        array(
439 3
                            'form' => $form,
440 3
                            'Product' => $Product,
441 3
                            'defaultProductClass' => $defaultProductClass,
442
                        ),
443 3
                        $request
444
                    );
445 3
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_COMPLETE, $event);
446
447 3
                    $app->addSuccess('admin.product.product_class.save.complete', 'admin');
448
449 3
                    break;
450 6
                case 'update':
451
                    // 更新
452 5
                    log_info('商品規格更新開始', array($id));
453
454 5 View Code Duplication
                    if (count($ProductClasses) == 0) {
455
                        // 商品規格が0件であれば最初の画面に戻す
456
                        log_info('商品規格が存在しません', array($id));
457
                        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...
458
                    }
459
460 5
                    $checkProductClasses = array();
461 5
                    $removeProductClasses = array();
462
463 5
                    $tempProductClass = null;
464 5 View Code Duplication
                    foreach ($form->get('product_classes') as $formData) {
465
                        // 追加対象の行をvalidate
466 5
                        $ProductClass = $formData->getData();
467
468 5
                        if ($ProductClass->getAdd()) {
469 5
                            if ($formData->isValid()) {
470 4
                                $checkProductClasses[] = $ProductClass;
471
                            } else {
472 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...
473
                            }
474
                        } else {
475
                            // 削除対象の行
476 1
                            $removeProductClasses[] = $ProductClass;
477
                        }
478 4
                        $tempProductClass = $ProductClass;
479
                    }
480
481 4 View Code Duplication
                    if (count($checkProductClasses) == 0) {
482
                        // 対象がなければエラー
483
                        log_info('商品規格が存在しません', array($id));
484
                        $error = array('message' => '商品規格が選択されていません。');
485
                        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...
486
                    }
487
488
489
                    // 登録対象と更新対象の行か判断する
490 4
                    $addProductClasses = array();
491 4
                    $updateProductClasses = array();
492 4
                    foreach ($checkProductClasses as $cp) {
493 4
                        $flag = false;
494
495
                        // 既に登録済みの商品規格か確認
496 4
                        foreach ($ProductClasses as $productClass) {
497 4
                            if ($productClass->getProduct()->getId() == $id &&
498 4
                                    $productClass->getClassCategory1() == $cp->getClassCategory1() &&
499 4
                                    $productClass->getClassCategory2() == $cp->getClassCategory2()) {
500 4
                                $updateProductClasses[] = $cp;
501
502
                                // 商品情報
503 4
                                $cp->setProduct($Product);
504
                                // 商品在庫
505 4
                                $productStock = $productClass->getProductStock();
506 4
                                if (!$cp->getStockUnlimited()) {
507 1
                                    $productStock->setStock($cp->getStock());
508
                                } else {
509 3
                                    $productStock->setStock(null);
510
                                }
511 4
                                $this->setDefaultProductClass($app, $productClass, $cp);
512 4
                                $flag = true;
513 4
                                break;
514
                            }
515
                        }
516 4
                        if (!$flag) {
517 4
                            $addProductClasses[] = $cp;
518
                        }
519
                    }
520
521 4
                    foreach ($removeProductClasses as $rc) {
522
                        // 登録されている商品規格を非表示
523
                        /** @var ProductClass $productClass */
524 1
                        foreach ($ProductClasses as $productClass) {
525 1
                            if ($productClass->getProduct()->getId() == $id &&
526 1
                                    $productClass->getClassCategory1() == $rc->getClassCategory1() &&
527 1
                                    $productClass->getClassCategory2() == $rc->getClassCategory2()) {
528 1
                                $productClass->setVisible(false);
529 1
                                break;
530
                            }
531
                        }
532
                    }
533
534
                    // 選択された商品規格を登録
535 4
                    $this->insertProductClass($app, $Product, $addProductClasses);
536
537 4
                    $this->entityManager->flush();
538
539 4
                    log_info('商品規格更新完了', array($id));
540
541 4
                    $event = new EventArgs(
542
                        array(
543 4
                            'form' => $form,
544 4
                            'Product' => $Product,
545 4
                            'updateProductClasses' => $updateProductClasses,
546 4
                            'addProductClasses' => $addProductClasses,
547
                        ),
548 4
                        $request
549
                    );
550 4
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_UPDATE, $event);
551
552 4
                    $app->addSuccess('admin.product.product_class.update.complete', 'admin');
553
554 4
                    break;
555
556 1
                case 'delete':
557
                    // 削除
558 1
                    log_info('商品規格削除開始', array($id));
559
560 1 View Code Duplication
                    if (count($ProductClasses) == 0) {
561
                        // 既に商品が削除されていれば元の画面に戻す
562
                        log_info('商品規格が存在しません', array($id));
563
                        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...
564
                    }
565
566 1
                    foreach ($ProductClasses as $ProductClass) {
567
                        // 登録されている商品規格を非表示
568 1
                        $ProductClass->setVisible(false);
569
                    }
570
571
                    // デフォルトの商品規格を表示
572
                    /** @var ProductClass $defaultProductClass */
573
574 1
                    $defaultProductClass = $this->productClassRepository
575 1
                            ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null, 'visible' => false));
576 1
                    $defaultProductClass->setVisible(true);
577
578 1
                    $this->entityManager->flush();
579 1
                    log_info('商品規格削除完了', array($id));
580
581 1
                    $event = new EventArgs(
582
                        array(
583 1
                            'form' => $form,
584 1
                            'Product' => $Product,
585 1
                            'defaultProductClass' => $defaultProductClass,
586
                        ),
587 1
                        $request
588
                    );
589 1
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_DELETE, $event);
590
591 1
                    $app->addSuccess('admin.product.product_class.delete.complete', 'admin');
592
593 1
                    break;
594
                default:
595
                    break;
596
            }
597
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
598
        }
599
600 9
        return $app->redirect($app->url('admin_product_product_class', array('id' => $id)));
601
    }
602
603
    /**
604
     * 登録、更新時のエラー画面表示
605
     *
606
     */
607 2
    protected function render($app, $Product, $ProductClass, $not_product_class, $classForm, $error = null)
608
    {
609
610 2
        $ClassName1 = null;
611 2
        $ClassName2 = null;
612
        // 規格を取得
613 2
        if (isset($ProductClass)) {
614 2
            $ClassCategory1 = $ProductClass->getClassCategory1();
615 2
            if ($ClassCategory1) {
616 2
                $ClassName1 = $ClassCategory1->getClassName();
617
            }
618 2
            $ClassCategory2 = $ProductClass->getClassCategory2();
619 2
            if ($ClassCategory2) {
620 1
                $ClassName2 = $ClassCategory2->getClassName();
621
            }
622
        }
623
624 2
        $form = $app->form()
625 2
            ->add('class_name1', EntityType::class, array(
626 2
                'class' => 'Eccube\Entity\ClassName',
627 2
                'choice_label' => 'name',
628 2
                'placeholder' => '規格1を選択',
629 2
                'data' => $ClassName1,
630
            ))
631 2
            ->add('class_name2', EntityType::class, array(
632 2
                'class' => 'Eccube\Entity\ClassName',
633 2
                'choice_label' => 'name',
634 2
                'placeholder' => '規格2を選択',
635 2
                'data' => $ClassName2,
636
            ))
637 2
            ->getForm();
638
639 2
        log_info('商品規格登録エラー');
640
641
642
        return [
643 2
            'form' => $form->createView(),
644 2
            'classForm' => $classForm->createView(),
645 2
            'Product' => $Product,
646 2
            'class_name1' => $ClassName1,
647 2
            'class_name2' => $ClassName2,
648 2
            'not_product_class' => $not_product_class,
649 2
            'error' => $error,
650
            'has_class_category_flg' => true,
651
        ];
652
    }
653
654
655
    /**
656
     * 規格1と規格2を組み合わせた商品規格を作成
657
     */
658 10
    private function createProductClasses($app, Product $Product, ClassName $ClassName1 = null, ClassName $ClassName2 = null)
659
    {
660
661 10
        $ClassCategories1 = array();
662 10
        if ($ClassName1) {
663 10
            $ClassCategories1 = $this->classCategoryRepository->findBy(array('ClassName' => $ClassName1));
664
        }
665
666 10
        $ClassCategories2 = array();
667 10
        if ($ClassName2) {
668 5
            $ClassCategories2 = $this->classCategoryRepository->findBy(array('ClassName' => $ClassName2));
669
        }
670
671 10
        $ProductClasses = array();
672 10
        foreach ($ClassCategories1 as $ClassCategory1) {
673 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...
674 5
                foreach ($ClassCategories2 as $ClassCategory2) {
675 5
                    $ProductClass = $this->newProductClass($app);
676 5
                    $ProductClass->setProduct($Product);
677 5
                    $ProductClass->setClassCategory1($ClassCategory1);
678 5
                    $ProductClass->setClassCategory2($ClassCategory2);
679 5
                    $ProductClass->setTaxRate(null);
680 5
                    $ProductClass->setVisible(true);
681 5
                    $ProductClasses[] = $ProductClass;
682
                }
683
            } else {
684 5
                $ProductClass = $this->newProductClass($app);
685 5
                $ProductClass->setProduct($Product);
686 5
                $ProductClass->setClassCategory1($ClassCategory1);
687 5
                $ProductClass->setTaxRate(null);
688 5
                $ProductClass->setVisible(true);
689 10
                $ProductClasses[] = $ProductClass;
690
            }
691
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
692
        }
693 10
        return $ProductClasses;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
694
    }
695
696
    /**
697
     * 新しい商品規格を作成
698
     */
699 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...
700
    {
701 10
        $ProductType = $this->productTypeRepository->find($this->appConfig['product_type_normal']);
702
703 10
        $ProductClass = new ProductClass();
704 10
        $ProductClass->setProductType($ProductType);
705 10
        return $ProductClass;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
706
    }
707
708
    /**
709
     * 商品規格のコピーを取得.
710
     *
711
     * @see http://symfony.com/doc/current/cookbook/form/form_collections.html
712
     * @param Product $Product
713
     * @return \Eccube\Entity\ProductClass[]
714
     */
715
    private function getProductClassesOriginal(Product $Product)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
716
    {
717
        $ProductClasses = $Product->getProductClasses();
718
        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...
719
            return true;
720
        });
721
    }
722
723
    /**
724
     * 規格なし商品を除いて商品規格を取得.
725
     *
726
     * @param Product $Product
727
     * @return Collection
728
     */
729 11
    private function getProductClassesExcludeNonClass(Product $Product)
730
    {
731 11
        $ProductClasses = $Product->getProductClasses();
732 11
        return new ArrayCollection(array_values($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...
733 11
            $ClassCategory1 = $ProductClass->getClassCategory1();
734 11
            $ClassCategory2 = $ProductClass->getClassCategory2();
735 11
            return ($ClassCategory1 || $ClassCategory2);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
736 11
        })->toArray()));
737
    }
738
739
    /**
740
     * デフォルトとなる商品規格を設定
741
     *
742
     * @param $productClassDest ProductClass コピー先となる商品規格
743
     * @param $productClassOrig ProductClass コピー元となる商品規格
744
     */
745 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...
746 8
        $productClassDest->setDeliveryDate($productClassOrig->getDeliveryDate());
747 8
        $productClassDest->setProduct($productClassOrig->getProduct());
748 8
        $productClassDest->setProductType($productClassOrig->getProductType());
749 8
        $productClassDest->setCode($productClassOrig->getCode());
750 8
        $productClassDest->setStock($productClassOrig->getStock());
751 8
        $productClassDest->setStockUnlimited($productClassOrig->getStockUnlimited());
752 8
        $productClassDest->setSaleLimit($productClassOrig->getSaleLimit());
753 8
        $productClassDest->setPrice01($productClassOrig->getPrice01());
754 8
        $productClassDest->setPrice02($productClassOrig->getPrice02());
755 8
        $productClassDest->setDeliveryFee($productClassOrig->getDeliveryFee());
756
757
        // 個別消費税
758 8
        if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
759 8
            if ($productClassOrig->getTaxRate() !== false && $productClassOrig->getTaxRate() !== null) {
760 3
                $productClassDest->setTaxRate($productClassOrig->getTaxRate());
761 3 View Code Duplication
                if ($productClassDest->getTaxRule()) {
762
                    $productClassDest->getTaxRule()->setTaxRate($productClassOrig->getTaxRate());
763
                } else {
764 3
                    $taxrule = $this->taxRuleRepository->newTaxRule();
765 3
                    $taxrule->setTaxRate($productClassOrig->getTaxRate());
766 3
                    $taxrule->setApplyDate(new \DateTime());
767 3
                    $taxrule->setProduct($productClassDest->getProduct());
768 3
                    $taxrule->setProductClass($productClassDest);
769 3
                    $productClassDest->setTaxRule($taxrule);
770
                }
771
            } else {
772 8
                if ($productClassDest->getTaxRule()) {
773
                    $this->taxRuleRepository->delete($productClassDest->getTaxRule());
774
                    $productClassDest->setTaxRule(null);
775
                }
776
            }
777
        }
778
    }
779
780
781
    /**
782
     * 商品規格を登録
783
     *
784
     * @param Application     $app
785
     * @param Product         $Product
786
     * @param ProductClass[] $ProductClasses 登録される商品規格
787
     */
788 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...
789
790
791
        // 選択された商品を登録
792 7
        foreach ($ProductClasses as $ProductClass) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
793
794 3
            $ProductClass->setVisible(true);
795 3
            $ProductClass->setProduct($Product);
796 3
            $this->entityManager->persist($ProductClass);
797
798
            // 在庫情報を作成
799 3
            $ProductStock = new ProductStock();
800 3
            $ProductClass->setProductStock($ProductStock);
801 3
            $ProductStock->setProductClass($ProductClass);
802 3
            if (!$ProductClass->getStockUnlimited()) {
803 3
                $ProductStock->setStock($ProductClass->getStock());
804
            } else {
805
                // 在庫無制限時はnullを設定
806
                $ProductStock->setStock(null);
807
            }
808 3
            $this->entityManager->persist($ProductStock);
809
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
810
        }
811
812
        // 商品税率が設定されている場合、商品税率をセット
813 7
        if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
814
            // 初期設定の税設定.
815 7
            $TaxRule = $this->taxRuleRepository->find(TaxRule::DEFAULT_TAX_RULE_ID);
816
            // 初期税率設定の計算方法を設定する
817 7
            $CalcRule = $TaxRule->getCalcRule();
818 7
            foreach ($ProductClasses as $ProductClass) {
819 3
                if ($ProductClass && is_numeric($taxRate = $ProductClass->getTaxRate())) {
820 2
                    $TaxRule = new TaxRule();
821 2
                    $TaxRule->setProduct($Product);
822 2
                    $TaxRule->setProductClass($ProductClass);
823 2
                    $TaxRule->setCalcRule($CalcRule);
824 2
                    $TaxRule->setTaxRate($taxRate);
825 2
                    $TaxRule->setTaxAdjust(0);
826 2
                    $TaxRule->setApplyDate(new \DateTime());
827 3
                    $this->entityManager->persist($TaxRule);
828
                }
829
            }
830
        }
831
832
    }
833
834
    /**
835
     * 規格の分類判定
836
     *
837
     * @param $class_name
838
     * @return boolean
839
     */
840 4
    private function isValiedCategory($class_name)
841
    {
842 4
        if (empty($class_name)) {
843 4
            return true;
844
        }
845 4
        if (count($class_name->getClassCategories()) < 1) {
846
            return false;
847
        }
848 4
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
849
    }
850
}
851