Completed
Push — experimental/3.1 ( b65b3f...682baa )
by chihiro
115:27 queued 115:10
created

getProductClassesExcludeNonClass()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 1
nop 1
dl 0
loc 9
ccs 7
cts 7
cp 1
crap 2
rs 9.6666
c 0
b 0
f 0
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\BaseInfo;
34
use Eccube\Entity\ClassName;
35
use Eccube\Entity\Product;
36
use Eccube\Entity\ProductClass;
37
use Eccube\Entity\ProductStock;
38
use Eccube\Entity\TaxRule;
39
use Eccube\Event\EccubeEvents;
40
use Eccube\Event\EventArgs;
41
use Eccube\Form\Type\Admin\ProductClassType;
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(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 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->setDefualtProductClass($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 6
                $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 View Code Duplication
            if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
276 6
                foreach ($ProductClasses as $class) {
277 6
                    if ($class->getTaxRule() && !$class->getTaxRule()->getDelFlg()) {
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 1
                    $mergeProductClasses[] = $createProductClass;
299
                }
300
301 6
                $flag = false;
302
            }
303
304
            // 登録済み商品規格と空の商品規格をマージ
305 6
            foreach ($mergeProductClasses as $mergeProductClass) {
306
                // 空の商品規格にデフォルト値を設定
307 1
                $this->setDefualtProductClass($app, $mergeProductClass, $ProductClass);
308 1
                $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...
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
359
        /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */
360 11
        $softDeleteFilter = $this->entityManager->getFilters()->getFilter('soft_delete');
361 11
        $softDeleteFilter->setExcludes(array(
362 11
            'Eccube\Entity\TaxRule',
363
        ));
364
365
        /** @var $Product \Eccube\Entity\Product */
366 11
        $Product = $this->productRepository->find($id);
367
368 11
        if (!$Product) {
369
            throw new NotFoundHttpException('商品が存在しません');
370
        }
371
372
        /* @var FormBuilder $builder */
373 11
        $builder = $this->formFactory->createBuilder();
374 11
        $builder->add('product_classes', CollectionType::class, array(
375 11
                    'entry_type' => ProductClassType::class,
376
                    'allow_add' => true,
377
                    'allow_delete' => true,
378
        ));
379
380 11
        $event = new EventArgs(
381
            array(
382 11
                'builder' => $builder,
383 11
                'Product' => $Product,
384
            ),
385 11
            $request
386
        );
387 11
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_INITIALIZE, $event);
388
389 11
        $form = $builder->getForm();
390
391 11
        $ProductClasses = $this->getProductClassesExcludeNonClass($Product);
392
393 11
        $form->handleRequest($request);
394 11
        if ($form->isSubmitted()) {
395 10
            switch ($request->get('mode')) {
396 10
                case 'edit':
397
                    // 新規登録
398 4
                    log_info('商品規格新規登録開始', array($id));
399
400 4 View Code Duplication
                    if (count($ProductClasses) > 0) {
401
                        // 既に登録されていれば最初の画面に戻す
402
                        log_info('商品規格登録済', array($id));
403
                        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...
404
                    }
405
406 4
                    $addProductClasses = array();
407
408 4
                    $tmpProductClass = null;
409 4 View Code Duplication
                    foreach ($form->get('product_classes') as $formData) {
410
                        // 追加対象の行をvalidate
411 4
                        $ProductClass = $formData->getData();
412
413 4
                        if ($ProductClass->getAdd()) {
414 4
                            if ($formData->isValid()) {
415 3
                                $addProductClasses[] = $ProductClass;
416
                            } else {
417
                                // 対象行のエラー
418 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...
419
                            }
420
                        }
421 3
                        $tmpProductClass = $ProductClass;
422
                    }
423
424 3 View Code Duplication
                    if (count($addProductClasses) == 0) {
425
                        // 対象がなければエラー
426
                        log_info('商品規格が未選択', array($id));
427
                        $error = array('message' => '商品規格が選択されていません。');
428
                        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...
429
                    }
430
431
                    // 選択された商品規格を登録
432 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...
433
434
                    // デフォルトの商品規格を更新
435 3
                    $defaultProductClass = $this->productClassRepository
436 3
                            ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null));
437
438 3
                    $defaultProductClass->setDelFlg(Constant::ENABLED);
439
440 3
                    $this->entityManager->flush();
441
442 3
                    log_info('商品規格新規登録完了', array($id));
443
444 3
                    $event = new EventArgs(
445
                        array(
446 3
                            'form' => $form,
447 3
                            'Product' => $Product,
448 3
                            'defaultProductClass' => $defaultProductClass,
449
                        ),
450 3
                        $request
451
                    );
452 3
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_COMPLETE, $event);
453
454 3
                    $app->addSuccess('admin.product.product_class.save.complete', 'admin');
455
456 3
                    break;
457 6
                case 'update':
458
                    // 更新
459 5
                    log_info('商品規格更新開始', array($id));
460
461 5 View Code Duplication
                    if (count($ProductClasses) == 0) {
462
                        // 商品規格が0件であれば最初の画面に戻す
463
                        log_info('商品規格が存在しません', array($id));
464
                        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...
465
                    }
466
467 5
                    $checkProductClasses = array();
468 5
                    $removeProductClasses = array();
469
470 5
                    $tempProductClass = null;
471 5 View Code Duplication
                    foreach ($form->get('product_classes') as $formData) {
472
                        // 追加対象の行をvalidate
473 5
                        $ProductClass = $formData->getData();
474
475 5
                        if ($ProductClass->getAdd()) {
476 5
                            if ($formData->isValid()) {
477 4
                                $checkProductClasses[] = $ProductClass;
478
                            } else {
479 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...
480
                            }
481
                        } else {
482
                            // 削除対象の行
483 1
                            $removeProductClasses[] = $ProductClass;
484
                        }
485 4
                        $tempProductClass = $ProductClass;
486
                    }
487
488 4 View Code Duplication
                    if (count($checkProductClasses) == 0) {
489
                        // 対象がなければエラー
490
                        log_info('商品規格が存在しません', array($id));
491
                        $error = array('message' => '商品規格が選択されていません。');
492
                        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...
493
                    }
494
495
496
                    // 登録対象と更新対象の行か判断する
497 4
                    $addProductClasses = array();
498 4
                    $updateProductClasses = array();
499 4
                    foreach ($checkProductClasses as $cp) {
500 4
                        $flag = false;
501
502
                        // 既に登録済みの商品規格か確認
503 4
                        foreach ($ProductClasses as $productClass) {
504 4
                            if ($productClass->getProduct()->getId() == $id &&
505 4
                                    $productClass->getClassCategory1() == $cp->getClassCategory1() &&
506 4
                                    $productClass->getClassCategory2() == $cp->getClassCategory2()) {
507 4
                                $updateProductClasses[] = $cp;
508
509
                                // 商品情報
510 4
                                $cp->setProduct($Product);
511
                                // 商品在庫
512 4
                                $productStock = $productClass->getProductStock();
513 4
                                if (!$cp->getStockUnlimited()) {
514 1
                                    $productStock->setStock($cp->getStock());
515
                                } else {
516 3
                                    $productStock->setStock(null);
517
                                }
518 4
                                $this->setDefualtProductClass($app, $productClass, $cp);
519 4
                                $flag = true;
520 4
                                break;
521
                            }
522
                        }
523 4
                        if (!$flag) {
524 4
                            $addProductClasses[] = $cp;
525
                        }
526
                    }
527
528 4
                    foreach ($removeProductClasses as $rc) {
529
                        // 登録されている商品規格に削除フラグをセット
530 1
                        foreach ($ProductClasses as $productClass) {
531 1
                            if ($productClass->getProduct()->getId() == $id &&
532 1
                                    $productClass->getClassCategory1() == $rc->getClassCategory1() &&
533 1
                                    $productClass->getClassCategory2() == $rc->getClassCategory2()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
534
535 1
                                $productClass->setDelFlg(Constant::ENABLED);
536 1
                                break;
537
                            }
538
                        }
539
                    }
540
541
                    // 選択された商品規格を登録
542 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...
543
544 4
                    $this->entityManager->flush();
545
546 4
                    log_info('商品規格更新完了', array($id));
547
548 4
                    $event = new EventArgs(
549
                        array(
550 4
                            'form' => $form,
551 4
                            'Product' => $Product,
552 4
                            'updateProductClasses' => $updateProductClasses,
553 4
                            'addProductClasses' => $addProductClasses,
554
                        ),
555 4
                        $request
556
                    );
557 4
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_UPDATE, $event);
558
559 4
                    $app->addSuccess('admin.product.product_class.update.complete', 'admin');
560
561 4
                    break;
562
563 1
                case 'delete':
564
                    // 削除
565 1
                    log_info('商品規格削除開始', array($id));
566
567 1 View Code Duplication
                    if (count($ProductClasses) == 0) {
568
                        // 既に商品が削除されていれば元の画面に戻す
569
                        log_info('商品規格が存在しません', array($id));
570
                        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...
571
                    }
572
573 1
                    foreach ($ProductClasses as $ProductClass) {
574
                        // 登録されている商品規格に削除フラグをセット
575 1
                        $ProductClass->setDelFlg(Constant::ENABLED);
576
                    }
577
578
                    /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */
579 1
                    $softDeleteFilter = $this->entityManager->getFilters()->getFilter('soft_delete');
580 1
                    $softDeleteFilter->setExcludes(array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
581 1
                        'Eccube\Entity\ProductClass'
582
                    ));
583
584
                    // デフォルトの商品規格を更新
585 1
                    $defaultProductClass = $this->productClassRepository
586 1
                            ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null, 'del_flg' => Constant::ENABLED));
587
588 1
                    $defaultProductClass->setDelFlg(Constant::DISABLED);
589
590 1
                    $this->entityManager->flush();
591 1
                    log_info('商品規格削除完了', array($id));
592
593 1
                    $event = new EventArgs(
594
                        array(
595 1
                            'form' => $form,
596 1
                            'Product' => $Product,
597 1
                            'defaultProductClass' => $defaultProductClass,
598
                        ),
599 1
                        $request
600
                    );
601 1
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_DELETE, $event);
602
603 1
                    $app->addSuccess('admin.product.product_class.delete.complete', 'admin');
604
605 1
                    break;
606
                default:
607
                    break;
608
            }
609
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
610
        }
611
612 9
        return $app->redirect($app->url('admin_product_product_class', array('id' => $id)));
613
    }
614
615
    /**
616
     * 登録、更新時のエラー画面表示
617
     *
618
     */
619 2
    protected function render($app, $Product, $ProductClass, $not_product_class, $classForm, $error = null)
620
    {
621
622 2
        $ClassName1 = null;
623 2
        $ClassName2 = null;
624
        // 規格を取得
625 2
        if (isset($ProductClass)) {
626 2
            $ClassCategory1 = $ProductClass->getClassCategory1();
627 2
            if ($ClassCategory1) {
628 2
                $ClassName1 = $ClassCategory1->getClassName();
629
            }
630 2
            $ClassCategory2 = $ProductClass->getClassCategory2();
631 2
            if ($ClassCategory2) {
632 1
                $ClassName2 = $ClassCategory2->getClassName();
633
            }
634
        }
635
636 2
        $form = $app->form()
637 2
            ->add('class_name1', EntityType::class, array(
638 2
                'class' => 'Eccube\Entity\ClassName',
639 2
                'choice_label' => 'name',
640 2
                'placeholder' => '規格1を選択',
641 2
                'data' => $ClassName1,
642
            ))
643 2
            ->add('class_name2', EntityType::class, array(
644 2
                'class' => 'Eccube\Entity\ClassName',
645 2
                'choice_label' => 'name',
646 2
                'placeholder' => '規格2を選択',
647 2
                'data' => $ClassName2,
648
            ))
649 2
            ->getForm();
650
651 2
        log_info('商品規格登録エラー');
652
653
654
        return [
655 2
            'form' => $form->createView(),
656 2
            'classForm' => $classForm->createView(),
657 2
            'Product' => $Product,
658 2
            'class_name1' => $ClassName1,
659 2
            'class_name2' => $ClassName2,
660 2
            'not_product_class' => $not_product_class,
661 2
            'error' => $error,
662
            'has_class_category_flg' => true,
663
        ];
664
    }
665
666
667
    /**
668
     * 規格1と規格2を組み合わせた商品規格を作成
669
     */
670 10
    private function createProductClasses($app, Product $Product, ClassName $ClassName1 = null, ClassName $ClassName2 = null)
671
    {
672
673 10
        $ClassCategories1 = array();
674 10
        if ($ClassName1) {
675 10
            $ClassCategories1 = $this->classCategoryRepository->findBy(array('ClassName' => $ClassName1));
676
        }
677
678 10
        $ClassCategories2 = array();
679 10
        if ($ClassName2) {
680 6
            $ClassCategories2 = $this->classCategoryRepository->findBy(array('ClassName' => $ClassName2));
681
        }
682
683 10
        $ProductClasses = array();
684 10
        foreach ($ClassCategories1 as $ClassCategory1) {
685 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...
686 6
                foreach ($ClassCategories2 as $ClassCategory2) {
687 6
                    $ProductClass = $this->newProductClass($app);
688 6
                    $ProductClass->setProduct($Product);
689 6
                    $ProductClass->setClassCategory1($ClassCategory1);
690 6
                    $ProductClass->setClassCategory2($ClassCategory2);
691 6
                    $ProductClass->setTaxRate(null);
692 6
                    $ProductClass->setDelFlg(Constant::DISABLED);
693 6
                    $ProductClasses[] = $ProductClass;
694
                }
695
            } else {
696 4
                $ProductClass = $this->newProductClass($app);
697 4
                $ProductClass->setProduct($Product);
698 4
                $ProductClass->setClassCategory1($ClassCategory1);
699 4
                $ProductClass->setTaxRate(null);
700 4
                $ProductClass->setDelFlg(Constant::DISABLED);
701 10
                $ProductClasses[] = $ProductClass;
702
            }
703
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
704
        }
705 10
        return $ProductClasses;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
706
    }
707
708
    /**
709
     * 新しい商品規格を作成
710
     */
711 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...
712
    {
713 10
        $ProductType = $this->productTypeRepository->find($this->appConfig['product_type_normal']);
714
715 10
        $ProductClass = new ProductClass();
716 10
        $ProductClass->setProductType($ProductType);
717 10
        return $ProductClass;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
718
    }
719
720
    /**
721
     * 商品規格のコピーを取得.
722
     *
723
     * @see http://symfony.com/doc/current/cookbook/form/form_collections.html
724
     * @param Product $Product
725
     * @return \Eccube\Entity\ProductClass[]
726
     */
727
    private function getProductClassesOriginal(Product $Product)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
728
    {
729
        $ProductClasses = $Product->getProductClasses();
730
        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...
731
            return true;
732
        });
733
    }
734
735
    /**
736
     * 規格なし商品を除いて商品規格を取得.
737
     *
738
     * @param Product $Product
739
     * @return \Eccube\Entity\ProductClass[]
740
     */
741 11
    private function getProductClassesExcludeNonClass(Product $Product)
742
    {
743 11
        $ProductClasses = $Product->getProductClasses();
744 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...
745 11
            $ClassCategory1 = $ProductClass->getClassCategory1();
746 11
            $ClassCategory2 = $ProductClass->getClassCategory2();
747 11
            return ($ClassCategory1 || $ClassCategory2);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
748 11
        });
749
    }
750
751
    /**
752
     * デフォルトとなる商品規格を設定
753
     *
754
     * @param $productClassDest コピー先となる商品規格
755
     * @param $productClassOrig コピー元となる商品規格
756
     */
757 8
    private function setDefualtProductClass($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...
758 8
        $productClassDest->setDeliveryDate($productClassOrig->getDeliveryDate());
759 8
        $productClassDest->setProduct($productClassOrig->getProduct());
760 8
        $productClassDest->setProductType($productClassOrig->getProductType());
761 8
        $productClassDest->setCode($productClassOrig->getCode());
762 8
        $productClassDest->setStock($productClassOrig->getStock());
763 8
        $productClassDest->setStockUnlimited($productClassOrig->getStockUnlimited());
764 8
        $productClassDest->setSaleLimit($productClassOrig->getSaleLimit());
765 8
        $productClassDest->setPrice01($productClassOrig->getPrice01());
766 8
        $productClassDest->setPrice02($productClassOrig->getPrice02());
767 8
        $productClassDest->setDeliveryFee($productClassOrig->getDeliveryFee());
768
769
        // 個別消費税
770 8
        if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
771 8
            if ($productClassOrig->getTaxRate() !== false && $productClassOrig->getTaxRate() !== null) {
772 3
                $productClassDest->setTaxRate($productClassOrig->getTaxRate());
773 3 View Code Duplication
                if ($productClassDest->getTaxRule()) {
774
                    $productClassDest->getTaxRule()->setTaxRate($productClassOrig->getTaxRate());
775
                    $productClassDest->getTaxRule()->setDelFlg(Constant::DISABLED);
776
                } else {
777 3
                    $taxrule = $this->taxRuleRepository->newTaxRule();
778 3
                    $taxrule->setTaxRate($productClassOrig->getTaxRate());
779 3
                    $taxrule->setApplyDate(new \DateTime());
780 3
                    $taxrule->setProduct($productClassDest->getProduct());
781 3
                    $taxrule->setProductClass($productClassDest);
782 3
                    $productClassDest->setTaxRule($taxrule);
783
                }
784
            } else {
785 8
                if ($productClassDest->getTaxRule()) {
786
                    $productClassDest->getTaxRule()->setDelFlg(Constant::ENABLED);
787
                }
788
            }
789
        }
790
    }
791
792
793
    /**
794
     * 商品規格を登録
795
     *
796
     * @param Application     $app
797
     * @param Product         $Product
798
     * @param ArrayCollection $ProductClasses 登録される商品規格
799
     */
800 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...
801
802
803
        // 選択された商品を登録
804 7
        foreach ($ProductClasses as $ProductClass) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
805
806 3
            $ProductClass->setDelFlg(Constant::DISABLED);
807 3
            $ProductClass->setProduct($Product);
808 3
            $this->entityManager->persist($ProductClass);
809
810
            // 在庫情報を作成
811 3
            $ProductStock = new ProductStock();
812 3
            $ProductClass->setProductStock($ProductStock);
813 3
            $ProductStock->setProductClass($ProductClass);
814 3
            if (!$ProductClass->getStockUnlimited()) {
815 3
                $ProductStock->setStock($ProductClass->getStock());
816
            } else {
817
                // 在庫無制限時はnullを設定
818
                $ProductStock->setStock(null);
819
            }
820 3
            $this->entityManager->persist($ProductStock);
821
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
822
        }
823
824
        // 商品税率が設定されている場合、商品税率をセット
825 7
        if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
826
            // 初期設定の税設定.
827 7
            $TaxRule = $this->taxRuleRepository->find(TaxRule::DEFAULT_TAX_RULE_ID);
828
            // 初期税率設定の計算方法を設定する
829 7
            $CalcRule = $TaxRule->getCalcRule();
830 7
            foreach ($ProductClasses as $ProductClass) {
831 3
                if ($ProductClass && is_numeric($taxRate = $ProductClass->getTaxRate())) {
832 2
                    $TaxRule = new TaxRule();
833 2
                    $TaxRule->setProduct($Product);
834 2
                    $TaxRule->setProductClass($ProductClass);
835 2
                    $TaxRule->setCalcRule($CalcRule);
836 2
                    $TaxRule->setTaxRate($taxRate);
837 2
                    $TaxRule->setTaxAdjust(0);
838 2
                    $TaxRule->setApplyDate(new \DateTime());
839 2
                    $TaxRule->setDelFlg(Constant::DISABLED);
840 3
                    $this->entityManager->persist($TaxRule);
841
                }
842
            }
843
        }
844
845
    }
846
847
    /**
848
     * 規格の分類判定
849
     *
850
     * @param $class_name
851
     * @return boolean
852
     */
853 4
    private function isValiedCategory($class_name)
854
    {
855 4
        if (empty($class_name)) {
856 4
            return true;
857
        }
858 4
        if (count($class_name->getClassCategories()) < 1) {
859
            return false;
860
        }
861 4
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
862
    }
863
}
864