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 Quiz 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 Quiz, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 10 | class Quiz extends Game |
||
| 11 | { |
||
| 12 | /** |
||
| 13 | * @var QuizMapperInterface |
||
| 14 | */ |
||
| 15 | protected $quizMapper; |
||
| 16 | |||
| 17 | /** |
||
| 18 | * @var QuizAnswerMapperInterface |
||
| 19 | */ |
||
| 20 | protected $quizAnswerMapper; |
||
| 21 | |||
| 22 | /** |
||
| 23 | * @var QuizQuestionMapperInterface |
||
| 24 | */ |
||
| 25 | protected $quizQuestionMapper; |
||
| 26 | |||
| 27 | /** |
||
| 28 | * @var QuizReplyMapperInterface |
||
| 29 | */ |
||
| 30 | protected $quizReplyMapper; |
||
| 31 | |||
| 32 | /** |
||
| 33 | * @var quizReplyAnswerMapper |
||
| 34 | */ |
||
| 35 | protected $quizReplyAnswerMapper; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * |
||
| 39 | * |
||
| 40 | * @param array $data |
||
| 41 | * @return \PlaygroundGame\Entity\Game |
||
| 42 | */ |
||
| 43 | public function createQuestion(array $data) |
||
| 44 | { |
||
| 45 | $path = $this->getOptions()->getMediaPath().DIRECTORY_SEPARATOR; |
||
| 46 | $media_url = $this->getOptions()->getMediaUrl().'/'; |
||
| 47 | |||
| 48 | $question = new \PlaygroundGame\Entity\QuizQuestion(); |
||
| 49 | $form = $this->serviceLocator->get('playgroundgame_quizquestion_form'); |
||
| 50 | $form->bind($question); |
||
| 51 | $form->setData($data); |
||
| 52 | |||
| 53 | $quiz = $this->getGameMapper()->findById($data['quiz_id']); |
||
| 54 | if (!$form->isValid()) { |
||
| 55 | return false; |
||
| 56 | } |
||
| 57 | |||
| 58 | $question->setQuiz($quiz); |
||
| 59 | |||
| 60 | // Max points and correct answers calculation for the question |
||
| 61 | if (!$question = $this->calculateMaxAnswersQuestion($question)) { |
||
| 62 | return false; |
||
| 63 | } |
||
| 64 | |||
| 65 | // Max points and correct answers recalculation for the quiz |
||
| 66 | $quiz = $this->calculateMaxAnswersQuiz($question->getQuiz()); |
||
|
|
|||
| 67 | |||
| 68 | $this->getEventManager()->trigger(__FUNCTION__, $this, array('game' => $question, 'data' => $data)); |
||
| 69 | $this->getQuizQuestionMapper()->insert($question); |
||
| 70 | $this->getEventManager()->trigger(__FUNCTION__ .'.post', $this, array('game' => $question, 'data' => $data)); |
||
| 71 | |||
| 72 | View Code Duplication | if (!empty($data['upload_image']['tmp_name'])) { |
|
| 73 | ErrorHandler::start(); |
||
| 74 | $data['upload_image']['name'] = $this->fileNewname( |
||
| 75 | $path, |
||
| 76 | $question->getId()."-".$data['upload_image']['name'] |
||
| 77 | ); |
||
| 78 | move_uploaded_file($data['upload_image']['tmp_name'], $path.$data['upload_image']['name']); |
||
| 79 | $question->setImage($media_url.$data['upload_image']['name']); |
||
| 80 | ErrorHandler::stop(true); |
||
| 81 | } |
||
| 82 | |||
| 83 | $this->getQuizQuestionMapper()->update($question); |
||
| 84 | $this->getQuizMapper()->update($quiz); |
||
| 85 | |||
| 86 | return $question; |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * @param array $data |
||
| 91 | * @return \PlaygroundGame\Entity\Game |
||
| 92 | */ |
||
| 93 | public function updateQuestion(array $data, $question) |
||
| 94 | { |
||
| 95 | $path = $this->getOptions()->getMediaPath().DIRECTORY_SEPARATOR; |
||
| 96 | $media_url = $this->getOptions()->getMediaUrl().'/'; |
||
| 97 | |||
| 98 | $form = $this->serviceLocator->get('playgroundgame_quizquestion_form'); |
||
| 99 | $form->bind($question); |
||
| 100 | $form->setData($data); |
||
| 101 | |||
| 102 | if (!$form->isValid()) { |
||
| 103 | return false; |
||
| 104 | } |
||
| 105 | |||
| 106 | // Max points and correct answers calculation for the question |
||
| 107 | if (!$question = $this->calculateMaxAnswersQuestion($question)) { |
||
| 108 | return false; |
||
| 109 | } |
||
| 110 | |||
| 111 | View Code Duplication | if (!empty($data['upload_image']['tmp_name'])) { |
|
| 112 | ErrorHandler::start(); |
||
| 113 | $data['upload_image']['name'] = $this->fileNewname( |
||
| 114 | $path, |
||
| 115 | $question->getId()."-".$data['upload_image']['name'] |
||
| 116 | ); |
||
| 117 | move_uploaded_file($data['upload_image']['tmp_name'], $path.$data['upload_image']['name']); |
||
| 118 | $question->setImage($media_url.$data['upload_image']['name']); |
||
| 119 | ErrorHandler::stop(true); |
||
| 120 | } |
||
| 121 | |||
| 122 | if (isset($data['delete_image']) && !empty($data['delete_image']) && empty($data['upload_image']['tmp_name'])) { |
||
| 123 | ErrorHandler::start(); |
||
| 124 | $image = $question->getImage(); |
||
| 125 | $image = str_replace($media_url, '', $image); |
||
| 126 | if (file_exists($path.$image)) { |
||
| 127 | unlink($path.$image); |
||
| 128 | } |
||
| 129 | $question->setImage(null); |
||
| 130 | ErrorHandler::stop(true); |
||
| 131 | } |
||
| 132 | |||
| 133 | $i = 0; |
||
| 134 | foreach ($question->getAnswers() as $answer) { |
||
| 135 | if (!empty($data['answers'][$i]['upload_image']['tmp_name'])) { |
||
| 136 | ErrorHandler::start(); |
||
| 137 | $data['answers'][$i]['upload_image']['name'] = $this->fileNewname( |
||
| 138 | $path, |
||
| 139 | $question->getId()."-".$data['answers'][$i]['upload_image']['name'] |
||
| 140 | ); |
||
| 141 | move_uploaded_file( |
||
| 142 | $data['answers'][$i]['upload_image']['tmp_name'], |
||
| 143 | $path.$data['answers'][$i]['upload_image']['name'] |
||
| 144 | ); |
||
| 145 | $answer->setImage($media_url.$data['answers'][$i]['upload_image']['name']); |
||
| 146 | ErrorHandler::stop(true); |
||
| 147 | } |
||
| 148 | $i++; |
||
| 149 | } |
||
| 150 | |||
| 151 | // Max points and correct answers recalculation for the quiz |
||
| 152 | $quiz = $this->calculateMaxAnswersQuiz($question->getQuiz()); |
||
| 153 | |||
| 154 | // If the question was a pronostic, I update entries with the results ! |
||
| 155 | if ($question->getPrediction()) { |
||
| 156 | $this->updatePrediction($question); |
||
| 157 | } |
||
| 158 | |||
| 159 | $this->getEventManager()->trigger( |
||
| 160 | __FUNCTION__, |
||
| 161 | $this, |
||
| 162 | array('question' => $question, 'data' => $data) |
||
| 163 | ); |
||
| 164 | $this->getQuizQuestionMapper()->update($question); |
||
| 165 | $this->getEventManager()->trigger( |
||
| 166 | __FUNCTION__ .'.post', |
||
| 167 | $this, |
||
| 168 | array('question' => $question, 'data' => $data) |
||
| 169 | ); |
||
| 170 | |||
| 171 | $this->getQuizMapper()->update($quiz); |
||
| 172 | |||
| 173 | return $question; |
||
| 174 | } |
||
| 175 | |||
| 176 | View Code Duplication | public function findRepliesByGame($game) |
|
| 177 | { |
||
| 178 | $em = $this->serviceLocator->get('doctrine.entitymanager.orm_default'); |
||
| 179 | $qb = $em->createQueryBuilder(); |
||
| 180 | $qb->select('r') |
||
| 181 | ->from('PlaygroundGame\Entity\QuizReply', 'r') |
||
| 182 | ->innerJoin('r.entry', 'e') |
||
| 183 | ->where('e.game = :game') |
||
| 184 | ->setParameter('game', $game); |
||
| 185 | $query = $qb->getQuery(); |
||
| 186 | |||
| 187 | $replies = $query->getResult(); |
||
| 188 | |||
| 189 | return $replies; |
||
| 190 | } |
||
| 191 | |||
| 192 | public function updatePrediction($question) |
||
| 193 | { |
||
| 194 | set_time_limit(0); |
||
| 195 | $em = $this->serviceLocator->get('doctrine.entitymanager.orm_default'); |
||
| 196 | |||
| 197 | /* @var $dbal \Doctrine\DBAL\Connection */ |
||
| 198 | $dbal = $em->getConnection(); |
||
| 199 | |||
| 200 | $answers = $question->getAnswers(); |
||
| 201 | $victoryCondition = $question->getQuiz()->getVictoryConditions()/100; |
||
| 202 | $maxCorrectAnswers = $question->getQuiz()->getMaxCorrectAnswers(); |
||
| 203 | $nbQuestionsWinner = $victoryCondition * $maxCorrectAnswers; |
||
| 204 | |||
| 205 | // I update all the answers with points and correctness |
||
| 206 | // Very fast (native query inside) |
||
| 207 | if ($question->getType() == 2) { |
||
| 208 | foreach ($answers as $answer) { |
||
| 209 | $correct = ($answer->getCorrect() == 1)?$answer->getCorrect():0; |
||
| 210 | $value = trim(strip_tags($answer->getAnswer())); |
||
| 211 | $points = ($correct)?$answer->getPoints():0; |
||
| 212 | $sql = " |
||
| 213 | UPDATE game_quiz_reply_answer AS ra |
||
| 214 | SET ra.points=IF(ra.answer=:answer, :points, 0), |
||
| 215 | ra.correct = IF(ra.answer=:answer, :isCorrect, 0) |
||
| 216 | WHERE ra.question_id = :questionId |
||
| 217 | "; |
||
| 218 | $stmt = $dbal->prepare($sql); |
||
| 219 | $stmt->execute( |
||
| 220 | array( |
||
| 221 | 'answer' => $value, |
||
| 222 | 'points' => $points, |
||
| 223 | 'isCorrect' => $correct, |
||
| 224 | 'questionId' => $question->getId() |
||
| 225 | ) |
||
| 226 | ); |
||
| 227 | } |
||
| 228 | } else { |
||
| 229 | foreach ($answers as $answer) { |
||
| 230 | $correct = ($answer->getCorrect() == 1)?$answer->getCorrect():0; |
||
| 231 | $points = ($correct)?$answer->getPoints():0; |
||
| 232 | $sql = " |
||
| 233 | UPDATE game_quiz_reply_answer AS ra |
||
| 234 | SET ra.points=:points, |
||
| 235 | ra.correct = :isCorrect |
||
| 236 | WHERE ra.question_id = :questionId |
||
| 237 | AND ra.answer_id = :answerId |
||
| 238 | "; |
||
| 239 | |||
| 240 | $stmt = $dbal->prepare($sql); |
||
| 241 | $stmt->execute( |
||
| 242 | array( |
||
| 243 | 'points' => $points, |
||
| 244 | 'isCorrect' => $correct, |
||
| 245 | 'questionId' => $question->getId(), |
||
| 246 | 'answerId' => $answer->getId() |
||
| 247 | ) |
||
| 248 | ); |
||
| 249 | } |
||
| 250 | } |
||
| 251 | |||
| 252 | // Entry update with points. WINNER is calculated also ! |
||
| 253 | $sql = " |
||
| 254 | UPDATE game_entry as e |
||
| 255 | INNER JOIN |
||
| 256 | ( |
||
| 257 | SELECT e.id, SUM(ra.points) as points, SUM(ra.correct) as correct |
||
| 258 | FROM game_entry as e |
||
| 259 | INNER JOIN game_quiz_reply AS r ON r.entry_id = e.id |
||
| 260 | INNER JOIN game_quiz_reply_answer AS ra ON ra.reply_id = r.id |
||
| 261 | GROUP BY e.id |
||
| 262 | ) i ON e.id = i.id |
||
| 263 | SET e.points = i.points, e.winner = IF( i.correct >= :nbQuestionsWinner, 1, 0) |
||
| 264 | WHERE e.game_id = :gameId |
||
| 265 | "; |
||
| 266 | |||
| 267 | $stmt = $dbal->prepare($sql); |
||
| 268 | $stmt->execute( |
||
| 269 | array( |
||
| 270 | 'gameId' => $question->getQuiz()->getId(), |
||
| 271 | 'nbQuestionsWinner' => $nbQuestionsWinner |
||
| 272 | ) |
||
| 273 | ); |
||
| 274 | |||
| 275 | $this->getEventManager()->trigger( |
||
| 276 | __FUNCTION__ .'.post', |
||
| 277 | $this, |
||
| 278 | array('question' => $question) |
||
| 279 | ); |
||
| 280 | } |
||
| 281 | /** |
||
| 282 | * This function update the sort order of the questions in a Quiz |
||
| 283 | * BEWARE : This function is time consuming (1s for 11 updates) |
||
| 284 | * If you have many replies, switch to a batch |
||
| 285 | * |
||
| 286 | * To improve performance, usage of DQL update |
||
| 287 | * http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html |
||
| 288 | * |
||
| 289 | * @param string $data |
||
| 290 | * @return boolean |
||
| 291 | */ |
||
| 292 | public function updatePredictionOLD($question) |
||
| 380 | |||
| 381 | public function getAnswersDistribution($game) |
||
| 382 | { |
||
| 383 | $em = $this->serviceLocator->get('doctrine.entitymanager.orm_default'); |
||
| 384 | |||
| 385 | /* @var $dbal \Doctrine\DBAL\Connection */ |
||
| 386 | $dbal = $em->getConnection(); |
||
| 387 | $sql = " |
||
| 388 | select q.id as question_id, q.question, qa.id as answer_id, qa.answer, count(a.id) as count from game as g |
||
| 389 | inner join game_quiz_question as q on g.id = q.quiz_id |
||
| 390 | inner join game_quiz_answer as qa on q.id = qa.question_id |
||
| 391 | left join game_quiz_reply_answer as a on a.answer_id = qa.id |
||
| 392 | where g.id = :quizId |
||
| 393 | group by q.id, qa.id |
||
| 394 | "; |
||
| 395 | |||
| 396 | $stmt = $dbal->prepare($sql); |
||
| 397 | $stmt->execute( |
||
| 398 | array( |
||
| 399 | 'quizId' => $game->getId() |
||
| 400 | ) |
||
| 401 | ); |
||
| 402 | |||
| 403 | $rows = $stmt->fetchAll(); |
||
| 404 | $result = []; |
||
| 405 | foreach ($rows as $row) { |
||
| 406 | $result[$row['question_id']][] = $row; |
||
| 407 | } |
||
| 408 | |||
| 409 | $this->getEventManager()->trigger( |
||
| 410 | __FUNCTION__ .'.post', |
||
| 411 | $this, |
||
| 412 | array('game' => $game) |
||
| 413 | ); |
||
| 414 | |||
| 415 | return $result; |
||
| 416 | } |
||
| 417 | |||
| 418 | /** |
||
| 419 | * This function update the sort order of the questions in a Quiz |
||
| 420 | * |
||
| 421 | * @param string $data |
||
| 422 | * @return boolean |
||
| 423 | */ |
||
| 424 | public function sortQuestion($data) |
||
| 436 | |||
| 437 | /** |
||
| 438 | * @return string |
||
| 439 | */ |
||
| 440 | public function calculateMaxAnswersQuestion($question) |
||
| 479 | |||
| 480 | public function calculateMaxAnswersQuiz($quiz) |
||
| 493 | |||
| 494 | View Code Duplication | public function getNumberCorrectAnswersQuiz($user, $count = 'count') |
|
| 509 | |||
| 510 | public function createQuizReply($data, $game, $user) |
||
| 655 | |||
| 656 | public function isWinner($game, $quizCorrectAnswers = 0) |
||
| 680 | |||
| 681 | /** |
||
| 682 | * DEPRECATED |
||
| 683 | */ |
||
| 684 | public function getEntriesHeader($game) |
||
| 691 | |||
| 692 | |||
| 693 | View Code Duplication | public function getEntriesQuery($game) |
|
| 694 | { |
||
| 695 | $em = $this->serviceLocator->get('doctrine.entitymanager.orm_default'); |
||
| 696 | |||
| 697 | $qb = $em->createQueryBuilder(); |
||
| 698 | $qb->select( |
||
| 699 | ' |
||
| 700 | r.id, |
||
| 701 | u.username, |
||
| 702 | u.title, |
||
| 703 | u.firstname, |
||
| 704 | u.lastname, |
||
| 705 | u.email, |
||
| 706 | u.optin, |
||
| 707 | u.optinPartner, |
||
| 708 | u.address, |
||
| 709 | u.address2, |
||
| 710 | u.postalCode, |
||
| 711 | u.city, |
||
| 712 | u.telephone, |
||
| 713 | u.mobile, |
||
| 714 | u.created_at, |
||
| 715 | u.dob, |
||
| 716 | e.winner, |
||
| 717 | e.socialShares, |
||
| 718 | e.playerData, |
||
| 719 | e.updated_at, |
||
| 720 | r.totalCorrectAnswers |
||
| 721 | ' |
||
| 722 | ) |
||
| 723 | ->from('PlaygroundGame\Entity\QuizReply', 'r') |
||
| 724 | ->innerJoin('r.entry', 'e') |
||
| 725 | ->leftJoin('e.user', 'u') |
||
| 726 | ->where($qb->expr()->eq('e.game', ':game')); |
||
| 727 | |||
| 728 | $qb->setParameter('game', $game); |
||
| 729 | |||
| 730 | return $qb; |
||
| 731 | } |
||
| 732 | |||
| 733 | public function getGameEntity() |
||
| 737 | |||
| 738 | /** |
||
| 739 | * getQuizMapper |
||
| 740 | * |
||
| 741 | * @return QuizMapperInterface |
||
| 742 | */ |
||
| 743 | public function getQuizMapper() |
||
| 751 | |||
| 752 | /** |
||
| 753 | * setQuizMapper |
||
| 754 | * |
||
| 755 | * @param QuizMapperInterface $quizMapper |
||
| 756 | * @return Game |
||
| 757 | */ |
||
| 758 | public function setQuizMapper(GameMapperInterface $quizMapper) |
||
| 764 | |||
| 765 | /** |
||
| 766 | * getQuizQuestionMapper |
||
| 767 | * |
||
| 768 | * @return QuizQuestionMapperInterface |
||
| 769 | */ |
||
| 770 | public function getQuizQuestionMapper() |
||
| 778 | |||
| 779 | /** |
||
| 780 | * setQuizQuestionMapper |
||
| 781 | * |
||
| 782 | * @param QuizQuestionMapperInterface $quizquestionMapper |
||
| 783 | * @return Quiz |
||
| 784 | */ |
||
| 785 | public function setQuizQuestionMapper($quizquestionMapper) |
||
| 791 | |||
| 792 | /** |
||
| 793 | * setQuizAnswerMapper |
||
| 794 | * |
||
| 795 | * @param QuizAnswerMapperInterface $quizAnswerMapper |
||
| 796 | * @return Quiz |
||
| 797 | */ |
||
| 798 | public function setQuizAnswerMapper($quizAnswerMapper) |
||
| 804 | |||
| 805 | /** |
||
| 806 | * getQuizAnswerMapper |
||
| 807 | * |
||
| 808 | * @return QuizAnswerMapperInterface |
||
| 809 | */ |
||
| 810 | public function getQuizAnswerMapper() |
||
| 818 | |||
| 819 | /** |
||
| 820 | * getQuizReplyMapper |
||
| 821 | * |
||
| 822 | * @return QuizReplyMapperInterface |
||
| 823 | */ |
||
| 824 | public function getQuizReplyMapper() |
||
| 832 | |||
| 833 | /** |
||
| 834 | * setQuizReplyMapper |
||
| 835 | * |
||
| 836 | * @param QuizReplyMapperInterface $quizreplyMapper |
||
| 837 | * @return Quiz |
||
| 838 | */ |
||
| 839 | public function setQuizReplyMapper($quizreplyMapper) |
||
| 845 | |||
| 846 | /** |
||
| 847 | * getQuizReplyAnswerMapper |
||
| 848 | * |
||
| 849 | * @return QuizReplyAnswerMapper |
||
| 850 | */ |
||
| 851 | public function getQuizReplyAnswerMapper() |
||
| 859 | |||
| 860 | /** |
||
| 861 | * setQuizReplyAnswerMapper |
||
| 862 | * |
||
| 863 | * @param QuizReplyAnswerMapper $quizReplyAnswerMapper |
||
| 864 | * @return Quiz |
||
| 865 | */ |
||
| 866 | public function setQuizReplyAnswerMapper($quizReplyAnswerMapper) |
||
| 872 | } |
||
| 873 |
Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.