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 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 |
||
| 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 | /** |
||
| 128 | * 商品規格が登録されていなければ新規登録、登録されていれば更新画面を表示する |
||
| 129 | * |
||
| 130 | * @Route("/{_admin}/product/product/class/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class") |
||
| 131 | * @Template("Product/product_class.twig") |
||
| 132 | */ |
||
| 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()) { |
|
| 181 | |||
| 182 | 4 | $form->handleRequest($request); |
|
| 183 | |||
| 184 | 4 | if ($form->isValid()) { |
|
| 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 | )); |
||
| 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 | |||
| 240 | } |
||
| 241 | |||
| 242 | } |
||
| 243 | } |
||
| 244 | |||
| 245 | return [ |
||
| 246 | 5 | 'form' => $form->createView(), |
|
| 247 | 5 | 'classForm' => $productClassForm, |
|
| 248 | 5 | 'Product' => $Product, |
|
| 249 | 'not_product_class' => true, |
||
| 250 | 'error' => null, |
||
| 251 | 5 | 'has_class_category_flg' => $hasClassCategoryFlg, |
|
| 252 | ]; |
||
| 253 | } else { |
||
| 254 | // 既に商品規格が登録されている場合、商品規格画面を表示する |
||
| 255 | |||
| 256 | 6 | log_info('商品規格登録済表示', array($id)); |
|
| 257 | |||
| 258 | // 既に登録されている商品規格を取得 |
||
| 259 | 6 | $ProductClasses = $this->getProductClassesExcludeNonClass($Product); |
|
| 260 | |||
| 261 | // 設定されている規格分類1、2を取得(商品規格の規格分類には必ず同じ値がセットされている) |
||
| 262 | 6 | $ProductClass = $ProductClasses[0]; |
|
| 263 | 6 | $ClassName1 = $ProductClass->getClassCategory1()->getClassName(); |
|
| 264 | 6 | $ClassName2 = null; |
|
| 265 | 6 | if (!is_null($ProductClass->getClassCategory2())) { |
|
| 266 | 5 | $ClassName2 = $ProductClass->getClassCategory2()->getClassName(); |
|
| 267 | } |
||
| 268 | |||
| 269 | // 規格分類が組み合わされた空の商品規格を取得 |
||
| 270 | 6 | $createProductClasses = $this->createProductClasses($app, $Product, $ClassName1, $ClassName2); |
|
| 271 | |||
| 272 | 6 | $mergeProductClasses = array(); |
|
| 273 | |||
| 274 | // 商品税率が設定されている場合、商品税率を項目に設定 |
||
| 275 | 6 | 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 | $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) |
|
| 614 | |||
| 615 | /** |
||
| 616 | * 登録、更新時のエラー画面表示 |
||
| 617 | * |
||
| 618 | */ |
||
| 619 | 2 | protected function render($app, $Product, $ProductClass, $not_product_class, $classForm, $error = null) |
|
| 665 | |||
| 666 | |||
| 667 | /** |
||
| 668 | * 規格1と規格2を組み合わせた商品規格を作成 |
||
| 669 | */ |
||
| 670 | 10 | private function createProductClasses($app, Product $Product, ClassName $ClassName1 = null, ClassName $ClassName2 = null) |
|
| 707 | |||
| 708 | /** |
||
| 709 | * 新しい商品規格を作成 |
||
| 710 | */ |
||
| 711 | 10 | private function newProductClass(Application $app) |
|
| 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) |
||
| 734 | |||
| 735 | /** |
||
| 736 | * 規格なし商品を除いて商品規格を取得. |
||
| 737 | * |
||
| 738 | * @param Product $Product |
||
| 739 | * @return \Eccube\Entity\ProductClass[] |
||
| 740 | */ |
||
| 741 | 11 | private function getProductClassesExcludeNonClass(Product $Product) |
|
| 750 | |||
| 751 | /** |
||
| 752 | * デフォルトとなる商品規格を設定 |
||
| 753 | * |
||
| 754 | * @param $productClassDest コピー先となる商品規格 |
||
| 755 | * @param $productClassOrig コピー元となる商品規格 |
||
| 756 | */ |
||
| 757 | 8 | private function setDefaultProductClass($app, $productClassDest, $productClassOrig) { |
|
| 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) { |
|
| 846 | |||
| 847 | /** |
||
| 848 | * 規格の分類判定 |
||
| 849 | * |
||
| 850 | * @param $class_name |
||
| 851 | * @return boolean |
||
| 852 | */ |
||
| 853 | 4 | private function isValiedCategory($class_name) |
|
| 863 | } |
||
| 864 |