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 |
||
| 63 | class ProductClassController |
||
| 64 | { |
||
| 65 | /** |
||
| 66 | * @Inject(TaxRuleRepository::class) |
||
| 67 | * @var TaxRuleRepository |
||
| 68 | */ |
||
| 69 | protected $taxRuleRepository; |
||
| 70 | |||
| 71 | /** |
||
| 72 | * @Inject("config") |
||
| 73 | * @var array |
||
| 74 | */ |
||
| 75 | protected $appConfig; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * @Inject(ProductTypeRepository::class) |
||
| 79 | * @var ProductTypeRepository |
||
| 80 | */ |
||
| 81 | protected $productTypeRepository; |
||
| 82 | |||
| 83 | /** |
||
| 84 | * @Inject(ClassCategoryRepository::class) |
||
| 85 | * @var ClassCategoryRepository |
||
| 86 | */ |
||
| 87 | protected $classCategoryRepository; |
||
| 88 | |||
| 89 | /** |
||
| 90 | * @Inject(ProductClassRepository::class) |
||
| 91 | * @var ProductClassRepository |
||
| 92 | */ |
||
| 93 | protected $productClassRepository; |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @Inject("orm.em") |
||
| 97 | * @var EntityManager |
||
| 98 | */ |
||
| 99 | protected $entityManager; |
||
| 100 | |||
| 101 | /** |
||
| 102 | * @Inject(BaseInfo::class) |
||
| 103 | * @var BaseInfo |
||
| 104 | */ |
||
| 105 | protected $BaseInfo; |
||
| 106 | |||
| 107 | /** |
||
| 108 | * @Inject("eccube.event.dispatcher") |
||
| 109 | * @var EventDispatcher |
||
| 110 | */ |
||
| 111 | protected $eventDispatcher; |
||
| 112 | |||
| 113 | /** |
||
| 114 | * @Inject("form.factory") |
||
| 115 | * @var FormFactory |
||
| 116 | */ |
||
| 117 | protected $formFactory; |
||
| 118 | |||
| 119 | /** |
||
| 120 | * @Inject(ProductRepository::class) |
||
| 121 | * @var ProductRepository |
||
| 122 | */ |
||
| 123 | protected $productRepository; |
||
| 124 | |||
| 125 | /** |
||
| 126 | * 商品規格が登録されていなければ新規登録、登録されていれば更新画面を表示する |
||
| 127 | * |
||
| 128 | * @Route("/{_admin}/product/product/class/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class") |
||
| 129 | * @Template("Product/product_class.twig") |
||
| 130 | */ |
||
| 131 | 10 | public function index(Application $app, Request $request, $id) |
|
| 132 | { |
||
| 133 | /** @var $Product \Eccube\Entity\Product */ |
||
| 134 | 10 | $Product = $this->productRepository->find($id); |
|
| 135 | 10 | $hasClassCategoryFlg = false; |
|
| 136 | |||
| 137 | 10 | if (!$Product) { |
|
| 138 | throw new NotFoundHttpException('商品が存在しません'); |
||
| 139 | } |
||
| 140 | |||
| 141 | // 商品規格情報が存在しなければ新規登録させる |
||
| 142 | 10 | if (!$Product->hasProductClass()) { |
|
| 143 | // 登録画面を表示 |
||
| 144 | |||
| 145 | 4 | log_info('商品規格新規登録表示', array($id)); |
|
| 146 | |||
| 147 | 4 | $builder = $this->formFactory->createBuilder(); |
|
| 148 | |||
| 149 | $builder |
||
| 150 | 4 | ->add('class_name1', EntityType::class, array( |
|
| 151 | 4 | 'class' => 'Eccube\Entity\ClassName', |
|
| 152 | 4 | 'choice_label' => 'name', |
|
| 153 | 4 | 'placeholder' => '規格1を選択', |
|
| 154 | 'constraints' => array( |
||
| 155 | 4 | new Assert\NotBlank(), |
|
| 156 | ), |
||
| 157 | )) |
||
| 158 | 4 | ->add('class_name2', EntityType::class, array( |
|
| 159 | 4 | 'class' => 'Eccube\Entity\ClassName', |
|
| 160 | 'choice_label' => 'name', |
||
| 161 | 'placeholder' => '規格2を選択', |
||
| 162 | 'required' => false, |
||
| 163 | )); |
||
| 164 | |||
| 165 | 4 | $event = new EventArgs( |
|
| 166 | array( |
||
| 167 | 4 | 'builder' => $builder, |
|
| 168 | 4 | 'Product' => $Product, |
|
| 169 | ), |
||
| 170 | 4 | $request |
|
| 171 | ); |
||
| 172 | 4 | $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_INITIALIZE, $event); |
|
| 173 | |||
| 174 | 4 | $form = $builder->getForm(); |
|
| 175 | |||
| 176 | 4 | $productClassForm = null; |
|
| 177 | |||
| 178 | 4 | if ('POST' === $request->getMethod()) { |
|
| 179 | |||
| 180 | 4 | $form->handleRequest($request); |
|
| 181 | |||
| 182 | 4 | if ($form->isValid()) { |
|
| 183 | |||
| 184 | 4 | $data = $form->getData(); |
|
| 185 | |||
| 186 | 4 | $ClassName1 = $data['class_name1']; |
|
| 187 | 4 | $ClassName2 = $data['class_name2']; |
|
| 188 | |||
| 189 | 4 | log_info('選択された商品規格', array($ClassName1, $ClassName2)); |
|
| 190 | |||
| 191 | // 各規格が選択されている際に、分類を保有しているか確認 |
||
| 192 | 4 | $class1Valied = $this->isValiedCategory($ClassName1); |
|
| 193 | 4 | $class2Valied = $this->isValiedCategory($ClassName2); |
|
| 194 | |||
| 195 | // 規格が選択されていないか、選択された状態で分類が保有されていれば、画面表示 |
||
| 196 | 4 | if($class1Valied && $class2Valied){ |
|
| 197 | 4 | $hasClassCategoryFlg = true; |
|
| 198 | } |
||
| 199 | |||
| 200 | 4 | if (!is_null($ClassName2) && $ClassName1->getId() == $ClassName2->getId()) { |
|
| 201 | // 規格1と規格2が同じ値はエラー |
||
| 202 | $form['class_name2']->addError(new FormError('規格1と規格2は、同じ値を使用できません。')); |
||
| 203 | } else { |
||
| 204 | // 規格分類が設定されていない商品規格を取得 |
||
| 205 | 4 | $orgProductClasses = $Product->getProductClasses(); |
|
| 206 | 4 | $sourceProduct = $orgProductClasses[0]; |
|
| 207 | |||
| 208 | // 規格分類が組み合わされた商品規格を取得 |
||
| 209 | 4 | $ProductClasses = $this->createProductClasses($app, $Product, $ClassName1, $ClassName2); |
|
| 210 | |||
| 211 | // 組み合わされた商品規格にデフォルト値をセット |
||
| 212 | 4 | foreach ($ProductClasses as $productClass) { |
|
| 213 | 4 | $this->setDefaultProductClass($app, $productClass, $sourceProduct); |
|
| 214 | } |
||
| 215 | |||
| 216 | 4 | $builder = $this->formFactory->createBuilder(); |
|
| 217 | |||
| 218 | $builder |
||
| 219 | 4 | ->add('product_classes', CollectionType::class, array( |
|
| 220 | 4 | 'entry_type' => ProductClassType::class, |
|
| 221 | 'allow_add' => true, |
||
| 222 | 'allow_delete' => true, |
||
| 223 | 4 | 'data' => $ProductClasses, |
|
| 224 | )); |
||
| 225 | |||
| 226 | 4 | $event = new EventArgs( |
|
| 227 | array( |
||
| 228 | 4 | 'builder' => $builder, |
|
| 229 | 4 | 'Product' => $Product, |
|
| 230 | 4 | 'ProductClasses' => $ProductClasses, |
|
| 231 | ), |
||
| 232 | 4 | $request |
|
| 233 | ); |
||
| 234 | 4 | $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_CLASSES, $event); |
|
| 235 | |||
| 236 | 4 | $productClassForm = $builder->getForm()->createView(); |
|
| 237 | |||
| 238 | } |
||
| 239 | |||
| 240 | } |
||
| 241 | } |
||
| 242 | |||
| 243 | return [ |
||
| 244 | 4 | 'form' => $form->createView(), |
|
| 245 | 4 | 'classForm' => $productClassForm, |
|
| 246 | 4 | 'Product' => $Product, |
|
| 247 | 'not_product_class' => true, |
||
| 248 | 'error' => null, |
||
| 249 | 4 | 'has_class_category_flg' => $hasClassCategoryFlg, |
|
| 250 | ]; |
||
| 251 | } else { |
||
| 252 | // 既に商品規格が登録されている場合、商品規格画面を表示する |
||
| 253 | |||
| 254 | 6 | log_info('商品規格登録済表示', array($id)); |
|
| 255 | |||
| 256 | // 既に登録されている商品規格を取得 |
||
| 257 | 6 | $ProductClasses = $this->getProductClassesExcludeNonClass($Product); |
|
| 258 | |||
| 259 | // 設定されている規格分類1、2を取得(商品規格の規格分類には必ず同じ値がセットされている) |
||
| 260 | 6 | $ProductClass = $ProductClasses->first(); |
|
| 261 | 6 | $ClassName1 = $ProductClass->getClassCategory1()->getClassName(); |
|
| 262 | 6 | $ClassName2 = null; |
|
| 263 | 6 | if (!is_null($ProductClass->getClassCategory2())) { |
|
| 264 | 6 | $ClassName2 = $ProductClass->getClassCategory2()->getClassName(); |
|
| 265 | } |
||
| 266 | |||
| 267 | // 規格分類が組み合わされた空の商品規格を取得 |
||
| 268 | 6 | $createProductClasses = $this->createProductClasses($app, $Product, $ClassName1, $ClassName2); |
|
| 269 | |||
| 270 | 6 | $mergeProductClasses = array(); |
|
| 271 | |||
| 272 | // 商品税率が設定されている場合、商品税率を項目に設定 |
||
| 273 | 6 | if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) { |
|
| 274 | 6 | foreach ($ProductClasses as $class) { |
|
| 275 | 6 | if ($class->getTaxRule()) { |
|
| 276 | 6 | $class->setTaxRate($class->getTaxRule()->getTaxRate()); |
|
| 277 | } |
||
| 278 | } |
||
| 279 | } |
||
| 280 | |||
| 281 | // 登録済み商品規格と空の商品規格をマージ |
||
| 282 | 6 | $flag = false; |
|
| 283 | 6 | foreach ($createProductClasses as $createProductClass) { |
|
| 284 | // 既に登録済みの商品規格にチェックボックスを設定 |
||
| 285 | 6 | foreach ($ProductClasses as $productClass) { |
|
| 286 | 6 | if ($productClass->getClassCategory1() == $createProductClass->getClassCategory1() && |
|
| 287 | 6 | $productClass->getClassCategory2() == $createProductClass->getClassCategory2()) { |
|
| 288 | // チェックボックスを追加 |
||
| 289 | 6 | $productClass->setAdd(true); |
|
| 290 | 6 | $flag = true; |
|
| 291 | 6 | break; |
|
| 292 | } |
||
| 293 | } |
||
| 294 | |||
| 295 | 6 | if (!$flag) { |
|
| 296 | 1 | $mergeProductClasses[] = $createProductClass; |
|
| 297 | } |
||
| 298 | |||
| 299 | 6 | $flag = false; |
|
| 300 | } |
||
| 301 | |||
| 302 | // 登録済み商品規格と空の商品規格をマージ |
||
| 303 | 6 | foreach ($mergeProductClasses as $mergeProductClass) { |
|
| 304 | // 空の商品規格にデフォルト値を設定 |
||
| 305 | 1 | $this->setDefaultProductClass($app, $mergeProductClass, $ProductClass); |
|
| 306 | 1 | $ProductClasses->add($mergeProductClass); |
|
| 307 | } |
||
| 308 | |||
| 309 | 6 | $builder = $this->formFactory->createBuilder(); |
|
| 310 | |||
| 311 | $builder |
||
| 312 | 6 | ->add('product_classes', CollectionType::class, array( |
|
| 313 | 6 | 'entry_type' => ProductClassType::class, |
|
| 314 | 'allow_add' => true, |
||
| 315 | 'allow_delete' => true, |
||
| 316 | 6 | 'data' => $ProductClasses, |
|
| 317 | )); |
||
| 318 | |||
| 319 | 6 | $event = new EventArgs( |
|
| 320 | array( |
||
| 321 | 6 | 'builder' => $builder, |
|
| 322 | 6 | 'Product' => $Product, |
|
| 323 | 6 | 'ProductClasses' => $ProductClasses, |
|
| 324 | ), |
||
| 325 | 6 | $request |
|
| 326 | ); |
||
| 327 | 6 | $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_INDEX_CLASSES, $event); |
|
| 328 | |||
| 329 | 6 | $productClassForm = $builder->getForm()->createView(); |
|
| 330 | |||
| 331 | return [ |
||
| 332 | 6 | 'classForm' => $productClassForm, |
|
| 333 | 6 | 'Product' => $Product, |
|
| 334 | 6 | 'class_name1' => $ClassName1, |
|
| 335 | 6 | 'class_name2' => $ClassName2, |
|
| 336 | 'not_product_class' => false, |
||
| 337 | 'error' => null, |
||
| 338 | 'has_class_category_flg' => true, |
||
| 339 | ]; |
||
| 340 | } |
||
| 341 | } |
||
| 342 | |||
| 343 | /** |
||
| 344 | * 商品規格の登録、更新、削除を行う |
||
| 345 | * |
||
| 346 | * @Route("/{_admin}/product/product/class/edit/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class_edit") |
||
| 347 | * @Template("Product/product_class.twig") |
||
| 348 | * |
||
| 349 | * @param Application $app |
||
| 350 | * @param Request $request |
||
| 351 | * @param int $id |
||
| 352 | * @return RedirectResponse |
||
| 353 | */ |
||
| 354 | 11 | public function edit(Application $app, Request $request, $id) |
|
| 600 | |||
| 601 | /** |
||
| 602 | * 登録、更新時のエラー画面表示 |
||
| 603 | * |
||
| 604 | */ |
||
| 605 | 2 | protected function render($app, $Product, $ProductClass, $not_product_class, $classForm, $error = null) |
|
| 651 | |||
| 652 | |||
| 653 | /** |
||
| 654 | * 規格1と規格2を組み合わせた商品規格を作成 |
||
| 655 | */ |
||
| 656 | 10 | private function createProductClasses($app, Product $Product, ClassName $ClassName1 = null, ClassName $ClassName2 = null) |
|
| 693 | |||
| 694 | /** |
||
| 695 | * 新しい商品規格を作成 |
||
| 696 | */ |
||
| 697 | 10 | private function newProductClass(Application $app) |
|
| 705 | |||
| 706 | /** |
||
| 707 | * 商品規格のコピーを取得. |
||
| 708 | * |
||
| 709 | * @see http://symfony.com/doc/current/cookbook/form/form_collections.html |
||
| 710 | * @param Product $Product |
||
| 711 | * @return \Eccube\Entity\ProductClass[] |
||
| 712 | */ |
||
| 713 | private function getProductClassesOriginal(Product $Product) |
||
| 720 | |||
| 721 | /** |
||
| 722 | * 規格なし商品を除いて商品規格を取得. |
||
| 723 | * |
||
| 724 | * @param Product $Product |
||
| 725 | * @return Collection |
||
| 726 | */ |
||
| 727 | 11 | private function getProductClassesExcludeNonClass(Product $Product) |
|
| 736 | |||
| 737 | /** |
||
| 738 | * デフォルトとなる商品規格を設定 |
||
| 739 | * |
||
| 740 | * @param $productClassDest ProductClass コピー先となる商品規格 |
||
| 741 | * @param $productClassOrig ProductClass コピー元となる商品規格 |
||
| 742 | */ |
||
| 743 | 8 | private function setDefaultProductClass($app, $productClassDest, $productClassOrig) { |
|
| 777 | |||
| 778 | |||
| 779 | /** |
||
| 780 | * 商品規格を登録 |
||
| 781 | * |
||
| 782 | * @param Application $app |
||
| 783 | * @param Product $Product |
||
| 784 | * @param ProductClass[] $ProductClasses 登録される商品規格 |
||
| 785 | */ |
||
| 786 | 7 | private function insertProductClass($app, $Product, $ProductClasses) { |
|
| 831 | |||
| 832 | /** |
||
| 833 | * 規格の分類判定 |
||
| 834 | * |
||
| 835 | * @param $class_name |
||
| 836 | * @return boolean |
||
| 837 | */ |
||
| 838 | 4 | private function isValiedCategory($class_name) |
|
| 848 | } |
||
| 849 |