Completed
Push — master ( c26f0f...c7af1e )
by Konstantinos
18:52
created

MatchController   B

Complexity

Total Complexity 30

Size/Duplication

Total Lines 243
Duplicated Lines 18.11 %

Coupling/Cohesion

Components 2
Dependencies 16

Importance

Changes 4
Bugs 0 Features 3
Metric Value
wmc 30
c 4
b 0
f 3
lcom 2
cbo 16
dl 44
loc 243
rs 8.4614

10 Methods

Rating   Name   Duplication   Size   Complexity  
A showAction() 0 4 1
A createAction() 10 15 4
A deleteAction() 10 15 4
A editAction() 16 16 2
B recalculateAction() 0 29 3
B recalculate() 8 57 5
A log() 0 6 1
A getMessages() 0 15 4
A validate() 0 16 4
B listAction() 0 24 2

How to fix   Duplicated Code   

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:

1
<?php
2
3
use Symfony\Component\Form\FormError;
4
use Symfony\Component\HttpFoundation\RedirectResponse;
5
use Symfony\Component\HttpFoundation\Request;
6
use Symfony\Component\HttpFoundation\StreamedResponse;
7
8
class MatchController extends CRUDController
9
{
10
    /**
11
     * Whether the last edited match has had its ELO changed, requiring an ELO
12
     * recalculation
13
     *
14
     * This is useful so that a confirmation form is shown, asking the user if
15
     * they want to recalculate ELOs
16
     *
17
     * @var bool
18
     */
19
    public $recalculateNeeded = false;
20
21
    public function listAction(Request $request, Player $me, Team $team = null, $type = null)
22
    {
23
        $qb = $this->getQueryBuilder();
24
25
        $currentPage = $request->query->get('page', 1);
26
27
        $query = $qb->sortBy('time')->reverse()
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class QueryBuilder as the method with() does only exist in the following sub-classes of QueryBuilder: MatchQueryBuilder. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
28
               ->with($team, $type)
29
               ->limit(50)->fromPage($currentPage);
30
31
        $matches = $query->getModels($fast = true);
32
33
        foreach ($matches as $match) {
34
            // Don't show wrong labels for matches
35
            $match->getOriginalTimestamp()->setTimezone($me->getTimezone());
36
        }
37
38
        return array(
39
            "matches"     => $matches,
40
            "team"        => $team,
41
            "currentPage" => $currentPage,
42
            "totalPages"  => $qb->countPages()
43
        );
44
    }
45
46
    public function showAction(Match $match)
47
    {
48
        return array("match" => $match);
49
    }
50
51
    public function createAction(Player $me)
52
    {
53
        return $this->create($me, function (Match $match) use ($me) {
54 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...
55
                && (!$match->getTeamA()->isLastMatch($match)
56
                || !$match->getTeamB()->isLastMatch($match))
57
            ) {
58
                $url = Service::getGenerator()->generate('match_recalculate', array(
59
                    'match' => $match->getId(),
60
                ));
61
62
                return new RedirectResponse($url);
63
            }
64
        });
65
    }
66
67
    public function deleteAction(Player $me, Match $match)
68
    {
69
        return $this->delete($match, $me, function () use ($match, $me) {
70 View Code Duplication
            if ($match->getTeamA()->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...
71
                && $match->getTeamB()->isLastMatch($match)) {
72
                $match->resetELOs();
73
            } elseif ($me->canEdit($match)) {
74
                $url = Service::getGenerator()->generate('match_recalculate', array(
75
                    'match' => $match->getId(),
76
                ));
77
78
                return new RedirectResponse($url);
79
            }
80
        });
81
    }
82
83 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...
84
    {
85
        // TODO: Generating this response is unnecessary
86
        $response = $this->edit($match, $me, "match");
87
88
        if ($this->recalculateNeeded) {
89
            // Redirect to a confirmation form if we are assigning a new leader
90
            $url = Service::getGenerator()->generate('match_recalculate', array(
91
                'match' => $match->getId(),
92
            ));
93
94
            return new RedirectResponse($url);
95
        }
96
97
        return $response;
98
    }
99
100
    public function recalculateAction(Player $me, $match)
101
    {
102
        $match = Match::get($match); // get a match even if it's deleted
103
104
        if (!$me->canEdit($match)) {
105
            throw new ForbiddenException("You are not allowed to edit that match.");
106
        }
107
108
        return $this->showConfirmationForm(function () use ($match) {
109
            $response = new StreamedResponse();
110
            $response->headers->set('Content-Type', 'text/plain');
111
            $response->setCallback(function () use ($match) {
112
                $this->recalculate($match);
113
            });
114
            $response->send();
115
        }, "Do you want to recalculate ELO history for all teams and matches after the specified match?",
116
            "ELO history recalculated",
117
            "Recalculate ELOs",
118
            function () use ($match) {
119
                if ($match->isDeleted()) {
120
                    return new RedirectResponse($match->getURL('list'));
121
                }
122
123
                return new RedirectResponse($match->getURL('show'));
124
            },
125
            "Match/recalculate.html.twig",
126
            $noButton = true
127
        );
128
    }
129
130
    /**
131
     * Recalculates match history for all teams and matches
132
     *
133
     * Recalculation is done as follows:
134
     * 1. A match is chosen as a starting point - it's stored old team ELOs are
135
     *    considered correct
136
     * 2. Team ELOs are reset to their values at the starting point
137
     * 3. Each match that occurred since the first specified match has its ELO
138
     *    recalculated based on the current team values, and the new match data
139
     *    and team ELOs are stored in the database
140
     *
141
     * @param Match $match The first match
142
     */
143
    private function recalculate(Match $match)
144
    {
145
        try {
146
            // Commented out to prevent ridiculously large recalculations
147
            //set_time_limit(0);
148
149
            $query = Match::getQueryBuilder()
150
                ->where('status')->notEquals('deleted')
151
                ->where('time')->isAfter($match->getTimestamp(), $inclusive = true)
152
                ->sortBy('time');
153
154
            /** @var Match[] $matches */
155
            $matches = $query->getModels($fast = true);
156
157
            // Send the total count to client-side javascript
158
            $this->log(count($matches) . "\n");
159
160
            // Start a transaction so tables are locked and we don't stay with
161
            // messed up data if something goes wrong
162
            Database::getInstance()->startTransaction();
163
164
            $teamsReset = [];
165
166
            // Reset match teams, in case the selected match is deleted and does
167
            // not show up in the list of matches to recalculate
168
            $match->getTeamA()->setElo($match->getTeamAEloOld());
169
            $match->getTeamB()->setElo($match->getTeamBEloOld());
170
            $teamsReset[ $match->getTeamA()->getId() ] = true;
171
            $teamsReset[ $match->getTeamB()->getId() ] = true;
172
173
            foreach ($matches as $i => $match) {
174
                // Reset teams' ELOs if they haven't been reset already
175 View Code Duplication
                if (!isset($teamsReset[ $match->getTeamA()->getId() ])) {
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...
176
                    $teamsReset[ $match->getTeamA()->getId() ] = true;
177
                    $match->getTeamA()->setElo($match->getTeamAEloOld());
178
                }
179 View Code Duplication
                if (!isset($teamsReset[ $match->getTeamB()->getId() ])) {
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...
180
                    $teamsReset[ $match->getTeamB()->getId() ] = true;
181
                    $match->getTeamB()->setElo($match->getTeamBEloOld());
182
                }
183
184
                $match->recalculateElo();
185
186
                // Send an update to the client-side javascript, so that a
187
                // progress bar can be updated
188
                $this->log("m");
189
            }
190
        } catch (Exception $e) {
191
            Database::getInstance()->rollback();
192
            Database::getInstance()->finishTransaction();
193
            throw $e;
194
        }
195
196
        Database::getInstance()->finishTransaction();
197
198
        $this->log("\n\nCalculation successful\n");
199
    }
200
201
    /**
202
     * Echo a string and flush the buffers
203
     *
204
     * Useful for streamed AJAX responses
205
     *
206
     * @param string $string The string to echo
207
     */
208
    private function log($string)
209
    {
210
        echo $string;
211
        ob_flush();
212
        flush();
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    protected function getMessages($type, $name = '')
219
    {
220
        $messages = parent::getMessages($type, $name);
221
222
        // Don't show the match info on the successful create/edit message
223
        foreach ($messages as &$action) {
224
            foreach ($action as &$status) {
225
                if (isset($status['named'])) {
226
                    $status['named'] = $status['unnamed'];
227
                }
228
            }
229
        }
230
231
        return $messages;
232
    }
233
234
    protected function validate($form)
235
    {
236
        // Make sure that two different teams participated in a match, i.e. a team
237
        // didn't match against itself
238
        $firstTeam  = $form->get('first_team')->get('team')->getData();
239
        $secondTeam = $form->get('second_team')->get('team')->getData();
240
241
        if (!$firstTeam || !$secondTeam) {
242
            return;
243
        }
244
245
        if ($firstTeam->isSameAs($secondTeam)) {
246
            $message = "You can't report a match where a team played against itself!";
247
            $form->addError(new FormError($message));
248
        }
249
    }
250
}
251