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 |
||
44 | class ProductClassController |
||
|
|||
45 | { |
||
46 | /** |
||
47 | * 商品規格が登録されていなければ新規登録、登録されていれば更新画面を表示する |
||
48 | */ |
||
49 | 13 | public function index(Application $app, Request $request, $id) |
|
263 | |||
264 | /** |
||
265 | * 商品規格の登録、更新、削除を行う |
||
266 | * |
||
267 | * @param Application $app |
||
268 | * @param Request $request |
||
269 | * @param int $id |
||
270 | * @return RedirectResponse |
||
271 | */ |
||
272 | 12 | public function edit(Application $app, Request $request, $id) |
|
273 | { |
||
274 | |||
275 | /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */ |
||
276 | 12 | $softDeleteFilter = $app['orm.em']->getFilters()->getFilter('soft_delete'); |
|
277 | 12 | $softDeleteFilter->setExcludes(array( |
|
278 | 12 | 'Eccube\Entity\TaxRule', |
|
279 | )); |
||
280 | |||
281 | /** @var $Product \Eccube\Entity\Product */ |
||
282 | 12 | $Product = $app['eccube.repository.product']->find($id); |
|
283 | |||
284 | 12 | if (!$Product) { |
|
285 | throw new NotFoundHttpException('商品が存在しません'); |
||
286 | } |
||
287 | |||
288 | /* @var FormBuilder $builder */ |
||
289 | 12 | $builder = $app['form.factory']->createBuilder(); |
|
290 | 12 | $builder->add('product_classes', 'collection', array( |
|
291 | 12 | 'type' => 'admin_product_class', |
|
292 | 'allow_add' => true, |
||
293 | 'allow_delete' => true, |
||
294 | )); |
||
295 | |||
296 | 12 | $event = new EventArgs( |
|
297 | array( |
||
298 | 12 | 'builder' => $builder, |
|
299 | 12 | 'Product' => $Product, |
|
300 | ), |
||
301 | $request |
||
302 | ); |
||
303 | 12 | $app['eccube.event.dispatcher']->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_INITIALIZE, $event); |
|
304 | |||
305 | 12 | $form = $builder->getForm(); |
|
306 | |||
307 | 12 | $ProductClasses = $this->getProductClassesExcludeNonClass($Product); |
|
308 | |||
309 | 12 | $form->handleRequest($request); |
|
310 | 12 | if ($form->isSubmitted()) { |
|
311 | 10 | switch ($request->get('mode')) { |
|
312 | 10 | case 'edit': |
|
313 | // 新規登録 |
||
314 | 4 | log_info('商品規格新規登録開始', array($id)); |
|
315 | |||
316 | 4 | View Code Duplication | if (count($ProductClasses) > 0) { |
317 | // 既に登録されていれば最初の画面に戻す |
||
318 | log_info('商品規格登録済', array($id)); |
||
319 | return $app->redirect($app->url('admin_product_product_class', array('id' => $id))); |
||
320 | } |
||
321 | |||
322 | 4 | $addProductClasses = array(); |
|
323 | |||
324 | 4 | $tmpProductClass = null; |
|
325 | 4 | View Code Duplication | foreach ($form->get('product_classes') as $formData) { |
326 | // 追加対象の行をvalidate |
||
327 | 4 | $ProductClass = $formData->getData(); |
|
328 | |||
329 | 4 | if ($ProductClass->getAdd()) { |
|
330 | 4 | if ($formData->isValid()) { |
|
331 | 3 | $addProductClasses[] = $ProductClass; |
|
332 | } else { |
||
333 | // 対象行のエラー |
||
334 | 1 | return $this->render($app, $Product, $ProductClass, true, $form); |
|
335 | } |
||
336 | } |
||
337 | 3 | $tmpProductClass = $ProductClass; |
|
338 | } |
||
339 | |||
340 | 3 | View Code Duplication | if (count($addProductClasses) == 0) { |
341 | // 対象がなければエラー |
||
342 | log_info('商品規格が未選択', array($id)); |
||
343 | $error = array('message' => '商品規格が選択されていません。'); |
||
344 | return $this->render($app, $Product, $tmpProductClass, true, $form, $error); |
||
345 | } |
||
346 | |||
347 | // 選択された商品規格を登録 |
||
348 | 3 | $this->insertProductClass($app, $Product, $addProductClasses); |
|
349 | |||
350 | // デフォルトの商品規格を更新 |
||
351 | 3 | $defaultProductClass = $app['eccube.repository.product_class'] |
|
352 | 3 | ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null)); |
|
353 | |||
354 | 3 | $defaultProductClass->setDelFlg(Constant::ENABLED); |
|
355 | |||
356 | 3 | $app['orm.em']->flush(); |
|
357 | |||
358 | 3 | log_info('商品規格新規登録完了', array($id)); |
|
359 | |||
360 | 3 | $event = new EventArgs( |
|
361 | array( |
||
362 | 3 | 'form' => $form, |
|
363 | 3 | 'Product' => $Product, |
|
364 | 3 | 'defaultProductClass' => $defaultProductClass, |
|
365 | ), |
||
366 | $request |
||
367 | ); |
||
368 | 3 | $app['eccube.event.dispatcher']->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_COMPLETE, $event); |
|
369 | |||
370 | 3 | $app->addSuccess('admin.product.product_class.save.complete', 'admin'); |
|
371 | |||
372 | 3 | break; |
|
373 | 6 | case 'update': |
|
374 | // 更新 |
||
375 | 5 | log_info('商品規格更新開始', array($id)); |
|
376 | |||
377 | 5 | View Code Duplication | if (count($ProductClasses) == 0) { |
378 | // 商品規格が0件であれば最初の画面に戻す |
||
379 | log_info('商品規格が存在しません', array($id)); |
||
380 | return $app->redirect($app->url('admin_product_product_class', array('id' => $id))); |
||
381 | } |
||
382 | |||
383 | 5 | $checkProductClasses = array(); |
|
384 | 5 | $removeProductClasses = array(); |
|
385 | |||
386 | 5 | $tempProductClass = null; |
|
387 | 5 | View Code Duplication | foreach ($form->get('product_classes') as $formData) { |
388 | // 追加対象の行をvalidate |
||
389 | 5 | $ProductClass = $formData->getData(); |
|
390 | |||
391 | 5 | if ($ProductClass->getAdd()) { |
|
392 | 5 | if ($formData->isValid()) { |
|
393 | 4 | $checkProductClasses[] = $ProductClass; |
|
394 | } else { |
||
395 | 5 | return $this->render($app, $Product, $ProductClass, false, $form); |
|
396 | } |
||
397 | } else { |
||
398 | // 削除対象の行 |
||
399 | 1 | $removeProductClasses[] = $ProductClass; |
|
400 | } |
||
401 | 4 | $tempProductClass = $ProductClass; |
|
402 | } |
||
403 | |||
404 | 4 | View Code Duplication | if (count($checkProductClasses) == 0) { |
405 | // 対象がなければエラー |
||
406 | log_info('商品規格が存在しません', array($id)); |
||
407 | $error = array('message' => '商品規格が選択されていません。'); |
||
408 | return $this->render($app, $Product, $tempProductClass, false, $form, $error); |
||
409 | } |
||
410 | |||
411 | |||
412 | // 登録対象と更新対象の行か判断する |
||
413 | 4 | $addProductClasses = array(); |
|
414 | 4 | $updateProductClasses = array(); |
|
415 | 4 | foreach ($checkProductClasses as $cp) { |
|
416 | 4 | $flag = false; |
|
417 | |||
418 | // 既に登録済みの商品規格か確認 |
||
419 | 4 | foreach ($ProductClasses as $productClass) { |
|
420 | 4 | if ($productClass->getProduct()->getId() == $id && |
|
421 | 4 | $productClass->getClassCategory1() == $cp->getClassCategory1() && |
|
422 | 4 | $productClass->getClassCategory2() == $cp->getClassCategory2()) { |
|
423 | 4 | $updateProductClasses[] = $cp; |
|
424 | |||
425 | // 商品情報 |
||
426 | 4 | $cp->setProduct($Product); |
|
427 | // 商品在庫 |
||
428 | 4 | $productStock = $productClass->getProductStock(); |
|
429 | 4 | if (!$cp->getStockUnlimited()) { |
|
430 | 1 | $productStock->setStock($cp->getStock()); |
|
431 | } else { |
||
432 | 3 | $productStock->setStock(null); |
|
433 | } |
||
434 | 4 | $this->setDefualtProductClass($app, $productClass, $cp); |
|
435 | 4 | $flag = true; |
|
436 | 4 | break; |
|
437 | } |
||
438 | } |
||
439 | 4 | if (!$flag) { |
|
440 | 4 | $addProductClasses[] = $cp; |
|
441 | } |
||
442 | } |
||
443 | |||
444 | 4 | foreach ($removeProductClasses as $rc) { |
|
445 | // 登録されている商品規格に削除フラグをセット |
||
446 | 1 | foreach ($ProductClasses as $productClass) { |
|
447 | 1 | if ($productClass->getProduct()->getId() == $id && |
|
448 | 1 | $productClass->getClassCategory1() == $rc->getClassCategory1() && |
|
449 | 1 | $productClass->getClassCategory2() == $rc->getClassCategory2()) { |
|
450 | |||
451 | $productClass->setDelFlg(Constant::ENABLED); |
||
452 | 4 | break; |
|
453 | } |
||
454 | } |
||
455 | } |
||
456 | |||
457 | // 選択された商品規格を登録 |
||
458 | 4 | $this->insertProductClass($app, $Product, $addProductClasses); |
|
459 | |||
460 | 4 | $app['orm.em']->flush(); |
|
461 | |||
462 | 4 | log_info('商品規格更新完了', array($id)); |
|
463 | |||
464 | 4 | $event = new EventArgs( |
|
465 | array( |
||
466 | 4 | 'form' => $form, |
|
467 | 4 | 'Product' => $Product, |
|
468 | 4 | 'updateProductClasses' => $updateProductClasses, |
|
469 | 4 | 'addProductClasses' => $addProductClasses, |
|
470 | ), |
||
471 | $request |
||
472 | ); |
||
473 | 4 | $app['eccube.event.dispatcher']->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_UPDATE, $event); |
|
474 | |||
475 | 4 | $app->addSuccess('admin.product.product_class.update.complete', 'admin'); |
|
476 | |||
477 | 4 | break; |
|
478 | |||
479 | 1 | case 'delete': |
|
480 | // 削除 |
||
481 | 1 | log_info('商品規格削除開始', array($id)); |
|
482 | |||
483 | 1 | View Code Duplication | if (count($ProductClasses) == 0) { |
484 | // 既に商品が削除されていれば元の画面に戻す |
||
485 | log_info('商品規格が存在しません', array($id)); |
||
486 | return $app->redirect($app->url('admin_product_product_class', array('id' => $id))); |
||
487 | } |
||
488 | |||
489 | 1 | foreach ($ProductClasses as $ProductClass) { |
|
490 | // 登録されている商品規格に削除フラグをセット |
||
491 | 1 | $ProductClass->setDelFlg(Constant::ENABLED); |
|
492 | } |
||
493 | |||
494 | /* @var $softDeleteFilter \Eccube\Doctrine\Filter\SoftDeleteFilter */ |
||
495 | 1 | $softDeleteFilter = $app['orm.em']->getFilters()->getFilter('soft_delete'); |
|
496 | 1 | $softDeleteFilter->setExcludes(array( |
|
497 | 'Eccube\Entity\ProductClass' |
||
498 | 1 | )); |
|
499 | |||
500 | // デフォルトの商品規格を更新 |
||
501 | 1 | $defaultProductClass = $app['eccube.repository.product_class'] |
|
502 | 1 | ->findOneBy(array('Product' => $Product, 'ClassCategory1' => null, 'ClassCategory2' => null, 'del_flg' => Constant::ENABLED)); |
|
503 | |||
504 | 1 | $defaultProductClass->setDelFlg(Constant::DISABLED); |
|
505 | |||
506 | 1 | $app['orm.em']->flush(); |
|
507 | 1 | log_info('商品規格削除完了', array($id)); |
|
508 | |||
509 | 1 | $event = new EventArgs( |
|
510 | array( |
||
511 | 1 | 'form' => $form, |
|
512 | 1 | 'Product' => $Product, |
|
513 | 1 | 'defaultProductClass' => $defaultProductClass, |
|
514 | ), |
||
515 | $request |
||
516 | ); |
||
517 | 1 | $app['eccube.event.dispatcher']->dispatch(EccubeEvents::ADMIN_PRODUCT_PRODUCT_CLASS_EDIT_DELETE, $event); |
|
518 | |||
519 | 1 | $app->addSuccess('admin.product.product_class.delete.complete', 'admin'); |
|
520 | |||
521 | 1 | break; |
|
522 | default: |
||
523 | 8 | break; |
|
524 | } |
||
525 | |||
526 | } |
||
527 | |||
528 | 10 | return $app->redirect($app->url('admin_product_product_class', array('id' => $id))); |
|
529 | } |
||
530 | |||
531 | /** |
||
532 | * 登録、更新時のエラー画面表示 |
||
533 | * |
||
534 | */ |
||
535 | 2 | protected function render($app, $Product, $ProductClass, $not_product_class, $classForm, $error = null) |
|
581 | |||
582 | |||
583 | /** |
||
584 | * 規格1と規格2を組み合わせた商品規格を作成 |
||
585 | */ |
||
586 | 12 | private function createProductClasses($app, Product $Product, ClassName $ClassName1 = null, ClassName $ClassName2 = null) |
|
623 | |||
624 | /** |
||
625 | * 新しい商品規格を作成 |
||
626 | */ |
||
627 | 12 | private function newProductClass(Application $app) |
|
635 | |||
636 | /** |
||
637 | * 商品規格のコピーを取得. |
||
638 | * |
||
639 | * @see http://symfony.com/doc/current/cookbook/form/form_collections.html |
||
640 | * @param Product $Product |
||
641 | * @return \Eccube\Entity\ProductClass[] |
||
642 | */ |
||
643 | private function getProductClassesOriginal(Product $Product) |
||
650 | |||
651 | /** |
||
652 | * 規格なし商品を除いて商品規格を取得. |
||
653 | * |
||
654 | * @param Product $Product |
||
655 | * @return \Eccube\Entity\ProductClass[] |
||
656 | */ |
||
657 | 13 | private function getProductClassesExcludeNonClass(Product $Product) |
|
666 | |||
667 | /** |
||
668 | * デフォルトとなる商品規格を設定 |
||
669 | * |
||
670 | * @param $productClassDest コピー先となる商品規格 |
||
671 | * @param $productClassOrig コピー元となる商品規格 |
||
672 | */ |
||
673 | 9 | private function setDefualtProductClass($app, $productClassDest, $productClassOrig) { |
|
708 | |||
709 | |||
710 | /** |
||
711 | * 商品規格を登録 |
||
712 | * |
||
713 | * @param Application $app |
||
714 | * @param Product $Product |
||
715 | * @param ArrayCollection $ProductClasses 登録される商品規格 |
||
716 | */ |
||
717 | 7 | private function insertProductClass($app, $Product, $ProductClasses) { |
|
764 | |||
765 | /** |
||
766 | * 規格の分類判定 |
||
767 | * |
||
768 | * @param $class_name |
||
769 | * @return boolean |
||
770 | */ |
||
771 | 4 | private function isValiedCategory($class_name) |
|
781 | } |