Completed
Push — master ( a1d9ad...47055c )
by Vladimir
11:18
created

MatchController   C

Complexity

Total Complexity 42

Size/Duplication

Total Lines 244
Duplicated Lines 15.16 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 33.63%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 18
dl 37
loc 244
ccs 37
cts 110
cp 0.3363
rs 6.1097
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B listAction() 0 49 5
A showAction() 0 4 1
B createAction() 11 16 5
A deleteAction() 10 15 4
A editAction() 16 16 3
B recalculateAction() 0 33 4
A log() 0 6 1
A getMessages() 0 15 4
D validate() 0 40 10
B validateEdit() 0 10 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MatchController 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 MatchController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use Symfony\Component\Form\Form;
4
use Symfony\Component\Form\FormError;
5
use Symfony\Component\HttpFoundation\RedirectResponse;
6
use Symfony\Component\HttpFoundation\Request;
7
use Symfony\Component\HttpFoundation\StreamedResponse;
8
9
class MatchController extends CRUDController
10
{
11
    /**
12
     * Whether the last edited match has had its ELO changed, requiring an ELO
13
     * recalculation
14
     *
15
     * This is useful so that a confirmation form is shown, asking the user if
16
     * they want to recalculate ELOs
17
     *
18
     * @var bool
19
     */
20
    public $recalculateNeeded = false;
21
22
    public function listAction(Request $request, Player $me, Team $team = null, Player $player = null, $type = null)
23
    {
24
        /** @var MatchQueryBuilder $qb */
25
        $qb = self::getQueryBuilder();
26
        $currentPage = $this->getCurrentPage();
27
28
        if ($player) {
29
            $team = $player;
30
        }
31
32
        $query = $qb
33
            ->sortBy('time')->reverse()
34
            ->with($team, $type)
0 ignored issues
show
Bug introduced by
It seems like $team defined by parameter $team on line 22 can also be of type null; however, MatchQueryBuilder::with() does only seem to accept object<Team>|object<Player>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
35
            ->limit(50)->fromPage($currentPage)
36
        ;
37
38
        $matchType = $request->query->get('type', 'all');
39
40
        if (in_array($matchType, [Match::FUN, Match::OFFICIAL, Match::SPECIAL])) {
41
            $query->where('type')->is($matchType);
42
        }
43
44
        $matches = $query->getModels($fast = true);
45
46
        /** @var Match $match */
47
        foreach ($matches as $match) {
48
            // Don't show wrong labels for matches
49
            $match->getOriginalTimestamp()->setTimezone($me->getTimezone());
50
        }
51
52
        $matches = __::groupBy($matches, function ($match) {
53
            /** @var Match $match */
54
            $ts = $match->getOriginalTimestamp();
55
56 1
            if ($ts->year !== TimeDate::now()->year) {
57
                return $ts->format(TimeDate::DATE_FULL);
58 1
            }
59
60
            return $ts->format(TimeDate::DATE_MEDIUM);
61 1
        });
62
63
        return [
64 1
            'matchType'   => $type,
65 1
            'matches'     => $matches,
66 1
            'team'        => $team,
67 1
            'currentPage' => $currentPage,
68
            'totalPages'  => $qb->countPages(),
69
        ];
70
    }
71
72
    public function showAction(Match $match)
73
    {
74
        return array("match" => $match);
75 1
    }
76
77
    public function createAction(Player $me)
78
    {
79
        return $this->create($me, function (Match $match) use ($me) {
80 View Code Duplication
            if ($me->canEdit($match)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
                && $match->isOfficial()
82
                && (!$match->getTeamA()->isLastMatch($match)
83
                || !$match->getTeamB()->isLastMatch($match))
84
            ) {
85
                $url = Service::getGenerator()->generate('match_recalculate', array(
86
                    'match' => $match->getId(),
87
                ));
88
89
                return new RedirectResponse($url);
90
            }
91
        });
92
    }
93
94
    public function deleteAction(Player $me, Match $match)
95
    {
96
        return $this->delete($match, $me, function () use ($match, $me) {
97 View Code Duplication
            if ($match->getTeamA()->isLastMatch($match) && $match->getTeamB()->isLastMatch($match)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98
                $match->resetTeamElos();
99
                $match->resetPlayerElos();
100
            } elseif ($me->canEdit($match)) {
101
                $url = Service::getGenerator()->generate('match_recalculate', array(
102
                    'match' => $match->getId(),
103
                ));
104
105
                return new RedirectResponse($url);
106
            }
107
        });
108
    }
109
110 View Code Duplication
    public function editAction(Player $me, Match $match)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
    {
112
        // TODO: Generating this response is unnecessary
113
        $response = $this->edit($match, $me, "match");
114
115
        if ($this->recalculateNeeded && $match->isOfficial()) {
116
            // Redirect to a confirmation form if we are assigning a new leader
117
            $url = Service::getGenerator()->generate('match_recalculate', array(
118
                'match' => $match->getId(),
119
            ));
120
121
            return new RedirectResponse($url);
122
        }
123
124
        return $response;
125
    }
126
127
    public function recalculateAction(Player $me, $match)
128
    {
129
        $match = Match::get($match); // get a match even if it's deleted
130
131
        if (!$me->canEdit($match)) {
132
            throw new ForbiddenException("You are not allowed to edit that match.");
133
        }
134
135
        if (!$match->isOfficial()) {
136
            throw new BadRequestException("You can't recalculate ELO history for a special match.");
137
        }
138
139
        return $this->showConfirmationForm(function () use ($match) {
140
            $response = new StreamedResponse();
141
            $response->headers->set('Content-Type', 'text/plain');
142
            $response->setCallback(function () use ($match) {
143
                $this->log(Match::recalculateMatchesSince($match));
144
            });
145
            $response->send();
146
        }, "Do you want to recalculate ELO history for all teams and matches after the specified match?",
147
            "ELO history recalculated",
148
            "Recalculate ELOs",
149
            function () use ($match) {
150
                if ($match->isDeleted()) {
151
                    return new RedirectResponse($match->getURL('list'));
152
                }
153
154
                return new RedirectResponse($match->getURL('show'));
155
            },
156
            "Match/recalculate.html.twig",
157
            $noButton = true
158
        );
159
    }
160
161
    /**
162 1
     * Echo a string and flush the buffers
163
     *
164 1
     * Useful for streamed AJAX responses
165
     *
166
     * @param string $string The string to echo
167 1
     */
168 1
    private function log($string)
169 1
    {
170 1
        echo $string;
171
        ob_flush();
172
        flush();
173
    }
174
175 1
    /**
176
     * {@inheritdoc}
177
     */
178 1
    protected function getMessages($type, $name = '')
179
    {
180
        $messages = parent::getMessages($type, $name);
181
182 1
        // Don't show the match info on the successful create/edit message
183 1
        foreach ($messages as &$action) {
184
            foreach ($action as &$status) {
185 1
                if (isset($status['named'])) {
186
                    $status['named'] = $status['unnamed'];
187
                }
188
            }
189 1
        }
190 1
191 1
        return $messages;
192
    }
193
194 1
    /**
195
     * @param Form $form
196 1
     */
197 1
    protected function validate($form)
198 1
    {
199 1
        // Make sure that two different teams participated in a match, i.e. a team
200
        // didn't match against itself
201 1
        $firstTeam  = $form->get('first_team')->get('team')->getData();
202 1
        $secondTeam = $form->get('second_team')->get('team')->getData();
203
204 1
        if (!$firstTeam || !$secondTeam) {
205
            return;
206 1
        }
207 1
208 1
        if ($firstTeam->isSameAs($secondTeam)) {
209
            $message = "You can't report a match where a team played against itself!";
210 1
            $form->addError(new FormError($message));
211 1
        }
212 1
213
        $matchType = $form->get('type')->getData();
214
215
        foreach (array('first_team', 'second_team') as $team) {
216
            $input = $form->get($team);
217 1
            $teamInput = $input->get('team');
218
            $teamParticipants = $input->get('participants');
219
220
            if ($matchType === Match::FUN) {
221
                if (!$teamInput->getData() instanceof ColorTeam) {
222
                    $message = "Please enter a team color for fun and special matches.";
223
                    $teamInput->addError(new FormError($message));
224
                }
225
            } elseif ($matchType === Match::OFFICIAL) {
226
                if ($teamInput->getData() instanceof ColorTeam) {
227
                    $participants = $teamParticipants->getData();
228
229
                    if (empty($participants)) {
230
                        $message = 'A player roster is necessary for a color team for a mixed official match.';
231
                        $teamInput->addError(new FormError($message));
232
                    }
233
                }
234
            }
235
        }
236
    }
237
238
    /**
239
     * @param Form $form
240
     * @param Match $match
241
     */
242
    protected function validateEdit($form, $match)
243
    {
244
        if ($match->isOfficial() && $form->get('type')->getData() !== Match::OFFICIAL) {
245
            $message = "You cannot change this match's type.";
246
            $form->get('type')->addError(new FormError($message));
247
        } elseif (!$match->isOfficial() && $form->get('type')->getData() === Match::OFFICIAL) {
248
            $message = "You can't make this an official match.";
249
            $form->get('type')->addError(new FormError($message));
250
        }
251
    }
252
}
253