Completed
Push — v0_9 ( 66e765...8c00af )
by Vladimir
03:55
created

Match   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 1008
Duplicated Lines 3.08 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 71.28%

Importance

Changes 0
Metric Value
wmc 103
lcom 1
cbo 11
dl 31
loc 1008
ccs 211
cts 296
cp 0.7128
rs 3.9999
c 0
b 0
f 0

57 Methods

Rating   Name   Duplication   Size   Complexity  
A involvesTeam() 0 4 2
B assignResult() 0 24 1
A getRouteName() 0 4 1
A getMatchDescription() 0 10 3
A getMatchLetter() 0 4 1
A getScore() 16 16 4
A getOpponentScore() 0 4 1
A getOpponent() 14 14 4
A getUpdated() 0 4 1
A setTimestamp() 0 6 1
A getTeamA() 0 8 2
A getTeamB() 0 8 2
A getTeamAColor() 0 4 1
A getTeamBColor() 0 4 1
A getTeamAPlayers() 0 4 1
A getTeamBPlayers() 0 4 1
A setTeamPlayers() 0 7 1
A parsePlayers() 0 8 2
A getTeamAPoints() 0 4 1
A getTeamBPoints() 0 4 1
A setTeamPoints() 0 7 1
A setTeamColors() 0 16 4
A getEloDiff() 0 4 1
A getTeamAEloNew() 0 4 1
A getTeamBEloNew() 0 4 1
A getTeamAEloOld() 0 4 1
A getTeamBEloOld() 0 4 1
A getTeamEloNew() 0 10 3
A getTeamEloOld() 0 10 3
A getMap() 0 4 1
A setMap() 0 4 1
A getMatchType() 0 4 1
A setMatchType() 0 4 1
A getMatchDetails() 0 4 1
A getServerAddress() 0 4 1
A setServerAddress() 0 6 1
A getReplayFileName() 0 8 2
A getDuration() 0 4 1
A setDuration() 0 4 1
A getEnteredBy() 0 4 1
A getLoser() 0 8 1
B getWinner() 0 17 5
A isDraw() 0 4 1
A isOfficial() 0 4 1
A resetELOs() 0 9 2
A getActivity() 0 13 3
A getPlayers() 0 14 4
B enterMatch() 0 61 4
C calculateEloDiff() 0 23 7
A isEloCorrect() 0 10 1
B recalculateElo() 0 25 2
A getMatches() 0 4 1
A getQueryBuilder() 0 15 1
A delete() 0 6 1
A getActiveStatuses() 0 4 1
B getName() 0 24 4
A updateMatchCount() 0 16 4

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 Match 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 Match, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file contains functionality relating to the official matches played in the league
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
use BZIon\Model\Column\Timestamp;
9
10
/**
11
 * A match played between two teams
12
 * @package    BZiON\Models
13
 */
14
class Match extends UrlModel implements NamedModel
15
{
16
    const OFFICIAL = "official";
17
    const SPECIAL  = "special";
18
    const FUN      = "fm";
19
20
    use Timestamp;
21
22
    /**
23
     * The ID of the first team of the match
24
     * @var int
25
     */
26
    protected $team_a;
27
28
    /**
29
     * The ID of the second team of the match
30
     * @var int
31
     */
32
    protected $team_b;
33
34
    /**
35
     * The color of the first team
36
     * @var string
37
     */
38
    protected $team_a_color;
39
40
    /**
41
     * The color of the second team
42
     * @var string
43
     */
44
    protected $team_b_color;
45
46
    /**
47
     * The match points (usually the number of flag captures) Team A scored
48
     * @var int
49
     */
50
    protected $team_a_points;
51
52
    /**
53
     * The match points Team B scored
54
     * @var int
55
     */
56
    protected $team_b_points;
57
58
    /**
59
     * The BZIDs of players part of Team A who participated in the match, separated by commas
60
     * @var string
61
     */
62
    protected $team_a_players;
63
64
    /**
65
     * The BZIDs of players part of Team B who participated in the match, separated by commas
66
     * @var string
67
     */
68
    protected $team_b_players;
69
70
    /**
71
     * The ELO score of Team A after the match
72
     * @var int
73
     */
74
    protected $team_a_elo_new;
75
76
    /**
77
     * The ELO score of Team B after the match
78
     * @var int
79
     */
80
    protected $team_b_elo_new;
81
82
    /**
83
     * The map ID used in the match if the league supports more than one map
84
     * @var int
85
     */
86
    protected $map;
87
88
    /**
89
     * The type of match that occurred. Valid options: official, fm, special
90
     *
91
     * @var string
92
     */
93
    protected $match_type;
94
95
    /**
96
     * A JSON string of events that happened during a match, such as captures and substitutions
97
     * @var string
98
     */
99
    protected $match_details;
100
101
    /**
102
     * The server location of there the match took place
103
     * @var string
104
     */
105
    protected $server;
106
107
    /**
108
     * The file name of the replay file of the match
109
     * @var string
110
     */
111
    protected $replay_file;
112
113
    /**
114
     * The absolute value of the ELO score difference
115
     * @var int
116
     */
117
    protected $elo_diff;
118
119
    /**
120
     * The timestamp representing when the match information was last updated
121
     * @var TimeDate
122
     */
123
    protected $updated;
124
125
    /**
126
     * The duration of the match in minutes
127
     * @var int
128
     */
129
    protected $duration;
130
131
    /**
132
     * The ID of the person (i.e. referee) who last updated the match information
133
     * @var string
134
     */
135
    protected $entered_by;
136
137
    /**
138
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
139
     * @var string
140
     */
141
    protected $status;
142
143
    /**
144
     * The name of the database table used for queries
145
     */
146
    const TABLE = "matches";
147
148
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
149
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
150
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
151
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 9
    protected function assignResult($match)
157
    {
158 9
        $this->team_a = $match['team_a'];
159 9
        $this->team_b = $match['team_b'];
160 9
        $this->team_a_color = $match['team_a_color'];
161 9
        $this->team_b_color = $match['team_b_color'];
162 9
        $this->team_a_points = $match['team_a_points'];
163 9
        $this->team_b_points = $match['team_b_points'];
164 9
        $this->team_a_players = $match['team_a_players'];
165 9
        $this->team_b_players = $match['team_b_players'];
166 9
        $this->team_a_elo_new = $match['team_a_elo_new'];
167 9
        $this->team_b_elo_new = $match['team_b_elo_new'];
168 9
        $this->map = $match['map'];
169 9
        $this->match_type = $match['match_type'];
170 9
        $this->match_details = $match['match_details'];
171 9
        $this->server = $match['server'];
172 9
        $this->replay_file = $match['replay_file'];
173 9
        $this->elo_diff = $match['elo_diff'];
174 9
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
175 9
        $this->updated = TimeDate::fromMysql($match['updated']);
176 9
        $this->duration = $match['duration'];
177 9
        $this->entered_by = $match['entered_by'];
178 9
        $this->status = $match['status'];
179 9
    }
180
181
    /**
182
     * Get the name of the route that shows the object
183
     * @param  string $action The route's suffix
184
     * @return string
185
     */
186 1
    public static function getRouteName($action = 'show')
187
    {
188 1
        return "match_$action";
189
    }
190
191
    /**
192
     * Get a one word description of a match relative to a team (i.e. win, loss, or draw)
193
     *
194
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
195
     *
196
     * @return string Either "win", "loss", or "draw" relative to the team
197
     */
198 1
    public function getMatchDescription($teamID)
199
    {
200 1
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
201 1
            return "win";
202 1
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
203 1
            return "loss";
204
        }
205
206 1
        return "tie";
207
    }
208
209
    /**
210
     * Get a one letter description of a match relative to a team (i.e. W, L, or T)
211
     *
212
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
213
     *
214
     * @return string Either "W", "L", or "T" relative to the team
215
     */
216 1
    public function getMatchLetter($teamID)
217
    {
218 1
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
219
    }
220
221
    /**
222
     * Get the score of a specific team
223
     *
224
     * @param int|string|TeamInterface $teamID The team we want the score for
225
     *
226
     * @return int The score that team received
227
     */
228 2 View Code Duplication
    public function getScore($teamID)
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...
229
    {
230 2
        if ($teamID instanceof TeamInterface) {
231
            // Oh no! The caller gave us a Team model instead of an ID!
232 2
            $teamID = $teamID->getId();
233
        } elseif (is_string($teamID)) {
234
            // Make sure we're comparing lowercase strings
235
            $teamID = strtolower($teamID);
236
        }
237
238 2
        if ($this->getTeamA()->getId() == $teamID) {
239 2
            return $this->getTeamAPoints();
240
        }
241
242 2
        return $this->getTeamBPoints();
243
    }
244
245
    /**
246
     * Get the score of the opponent relative to a team
247
     *
248
     * @param int|string|TeamInterface $teamID The opponent of the team we want the score for
249
     *
250
     * @return int The score of the opponent
251
     */
252 2
    public function getOpponentScore($teamID)
253
    {
254 2
        return $this->getScore($this->getOpponent($teamID));
255
    }
256
257
    /**
258
     * Get the opponent of a match relative to a team ID
259
     *
260
     * @param int|string|TeamInterface $teamID The team who is known in a match
261
     *
262
     * @return TeamInterface The opponent team
263
     */
264 8 View Code Duplication
    public function getOpponent($teamID)
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...
265
    {
266 8
        if ($teamID instanceof TeamInterface) {
267 8
            $teamID = $teamID->getId();
268
        } elseif (is_string($teamID)) {
269
            $teamID = strtolower($teamID);
270
        }
271
272 8
        if ($this->getTeamA()->getId() == $teamID) {
273 6
            return $this->getTeamB();
274
        }
275
276 4
        return $this->getTeamA();
277
    }
278
279
    /**
280
     * Get the timestamp of the last update of the match
281
     *
282
     * @return TimeDate The match's update timestamp
283
     */
284 1
    public function getUpdated()
285
    {
286 1
        return $this->updated->copy();
287
    }
288
289
    /**
290
     * Set the timestamp of the match
291
     *
292
     * @param  mixed $timestamp The match's new timestamp
293
     * @return $this
294
     */
295
    public function setTimestamp($timestamp)
296
    {
297
        $this->updateProperty($this->timestamp, "timestamp", TimeDate::from($timestamp));
298
299
        return $this;
300
    }
301
302
    /**
303
     * Get the first team involved in the match
304
     * @return TeamInterface Team A
305
     */
306 9
    public function getTeamA()
307
    {
308 9
        if ($this->match_type === self::OFFICIAL) {
309 9
            return Team::get($this->team_a);
310
        }
311
312 1
        return new ColorTeam($this->team_a_color);
313
    }
314
315
    /**
316
     * Get the second team involved in the match
317
     * @return TeamInterface Team B
318
     */
319 9
    public function getTeamB()
320
    {
321 9
        if ($this->match_type === self::OFFICIAL) {
322 9
            return Team::get($this->team_b);
323
        }
324
325 1
        return new ColorTeam($this->team_b_color);
326
    }
327
328
    /**
329
     * Get the color of Team A
330
     * @return string
331
     */
332
    public function getTeamAColor()
333
    {
334
        return $this->team_a_color;
335
    }
336
337
    /**
338
     * Get the color of Team B
339
     * @return string
340
     */
341
    public function getTeamBColor()
342
    {
343
        return $this->team_b_color;
344
    }
345
346
    /**
347
     * Get the list of players on Team A who participated in this match
348
     * @return Player[]|null Returns null if there were no players recorded for this match
349
     */
350 1
    public function getTeamAPlayers()
351
    {
352 1
        return $this->parsePlayers($this->team_a_players);
353
    }
354
355
    /**
356
     * Get the list of players on Team B who participated in this match
357
     * @return Player[]|null Returns null if there were no players recorded for this match
358
     */
359 1
    public function getTeamBPlayers()
360
    {
361 1
        return $this->parsePlayers($this->team_b_players);
362
    }
363
364
    /**
365
     * Get the list of players for a team in a match
366
     * @param  Team|int|null The team or team ID
367
     * @return Player[]|null Returns null if there were no players recorded for this match
368
     */
369 9
    public function getPlayers($team = null)
370
    {
371 9
        if ($team instanceof TeamInterface) {
372 1
            $team = $team->getId();
373
        }
374
375 9
        if ($team == $this->getTeamA()->getId()) {
376 1
            return $this->getTeamAPlayers();
377 9
        } elseif ($team == $this->getTeamB()->getId()) {
378 1
            return $this->getTeamBPlayers();
379
        }
380
381 9
        return $this->parsePlayers($this->team_a_players . "," . $this->team_b_players);
382
    }
383
384
    /**
385
     * Set the players of the match's teams
386
     *
387
     * @param int[] $teamAPlayers An array of player IDs
388
     * @param int[] $teamBPlayers An array of player IDs
389
     * @return self
390
     */
391
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
392
    {
393
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers));
394
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers));
395
396
        return $this;
397
    }
398
399
    /**
400
     * Get an array of players based on a string representation
401
     * @param string $playerString
402
     * @return Player[]|null Returns null if there were no players recorded for this match
403
     */
404 9
    private function parsePlayers($playerString)
405
    {
406 9
        if ($playerString == null) {
407 1
            return null;
408
        }
409
410 9
        return Player::arrayIdToModel(explode(",", $playerString));
411
    }
412
413
    /**
414
     * Get the first team's points
415
     * @return int Team A's points
416
     */
417 4
    public function getTeamAPoints()
418
    {
419 4
        return $this->team_a_points;
420
    }
421
422
    /**
423
     * Get the second team's points
424
     * @return int Team B's points
425
     */
426 4
    public function getTeamBPoints()
427
    {
428 4
        return $this->team_b_points;
429
    }
430
431
    /**
432
     * Set the match team points
433
     *
434
     * @param  int $teamAPoints Team A's points
435
     * @param  int $teamBPoints Team B's points
436
     * @return self
437
     */
438
    public function setTeamPoints($teamAPoints, $teamBPoints)
439
    {
440
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints);
441
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints);
442
443
        return $this;
444
    }
445
446
    /**
447
     * Set the match team colors
448
     *
449
     * @param  ColorTeam|string $teamAColor The color of team A
450
     * @param  ColorTeam|string $teamBColor The color of team B
451
     * @return self
452
     */
453
    public function setTeamColors($teamAColor, $teamBColor)
454
    {
455
        if ($this->isOfficial()) {
456
            throw new \Exception("Cannot change team colors in an official match");
457
        }
458
459
        if ($teamAColor instanceof ColorTeam) {
460
            $teamAColor = $teamAColor->getId();
461
        }
462
        if ($teamBColor instanceof ColorTeam) {
463
            $teamBColor = $teamBColor->getId();
464
        }
465
466
        $this->updateProperty($this->team_a_color, "team_a_color", $teamAColor);
467
        $this->updateProperty($this->team_b_color, "team_b_color", $teamBColor);
468
    }
469
470
    /**
471
     * Get the ELO difference applied to each team's old ELO
472
     * @return int The ELO difference
473
     */
474 7
    public function getEloDiff()
475
    {
476 7
        return abs($this->elo_diff);
477
    }
478
479
    /**
480
     * Get the first team's new ELO
481
     * @return int Team A's new ELO
482
     */
483 7
    public function getTeamAEloNew()
484
    {
485 7
        return $this->team_a_elo_new;
486
    }
487
488
    /**
489
     * Get the second team's new ELO
490
     * @return int Team B's new ELO
491
     */
492 7
    public function getTeamBEloNew()
493
    {
494 7
        return $this->team_b_elo_new;
495
    }
496
497
    /**
498
     * Get the first team's old ELO
499
     * @return int
500
     */
501 6
    public function getTeamAEloOld()
502
    {
503 6
        return $this->team_a_elo_new - $this->elo_diff;
504
    }
505
506
    /**
507
     * Get the second team's old ELO
508
     * @return int
509
     */
510 6
    public function getTeamBEloOld()
511
    {
512 6
        return $this->team_b_elo_new + $this->elo_diff;
513
    }
514
515
    /**
516
     * Get the team's new ELO
517
     * @param  Team $team The team whose new ELO to return
518
     * @return int|null   The new ELO, or null if the team provided has not
519
     *                    participated in the match
520
     */
521 1
    public function getTeamEloNew(Team $team)
522
    {
523 1
        if ($team->getId() == $this->team_a) {
524 1
            return $this->getTeamAEloNew();
525 1
        } elseif ($team->getId() == $this->team_b) {
526 1
            return $this->getTeamBEloNew();
527
        }
528
529
        return null;
530
    }
531
532
    /**
533
     * Get the team's old ELO
534
     * @param  Team $team The team whose old ELO to return
535
     * @return int|null   The old ELO, or null if the team provided has not
536
     *                    participated in the match
537
     */
538 1
    public function getTeamEloOld(Team $team)
539
    {
540 1
        if ($team->getId() == $this->team_a) {
541 1
            return $this->getTeamAEloOld();
542 1
        } elseif ($team->getId() == $this->team_b) {
543 1
            return $this->getTeamBEloOld();
544
        }
545
546
        return null;
547
    }
548
549
    /**
550
     * Get the map where the match was played on
551
     * @return Map Returns an invalid map if no map was found
552
     */
553 1
    public function getMap()
554
    {
555 1
        return Map::get($this->map);
556
    }
557
558
    /**
559
     * Set the map where the match was played
560
     * @param  int $map The ID of the map
561
     * @return self
562
     */
563
    public function setMap($map)
564
    {
565
        $this->updateProperty($this->map, "map", $map, "s");
566
    }
567
568
    /**
569
     * Get the match type
570
     *
571
     * @return string 'official', 'fm', or 'special'
572
     */
573 1
    public function getMatchType()
574
    {
575 1
        return $this->match_type;
576
    }
577
578
    /**
579
     * Set the match type
580
     *
581
     * @param  string $matchType A valid match type; official, fm, special
582
     *
583
     * @return static
584
     */
585
    public function setMatchType($matchType)
586
    {
587
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
588
    }
589
590
    /**
591
     * Get a JSON decoded array of events that occurred during the match
592
     * @return mixed|null Returns null if there were no events recorded for the match
593
     */
594
    public function getMatchDetails()
595
    {
596
        return json_decode($this->match_details);
597
    }
598
599
    /**
600
     * Get the server address of the server where this match took place
601
     * @return string|null Returns null if there was no server address recorded
602
     */
603 1
    public function getServerAddress()
604
    {
605 1
        return $this->server;
606
    }
607
608
    /**
609
     * Set the server address of the server where this match took place
610
     *
611
     * @param  string|null $server The server hostname
612
     * @param  int|null    $port   The server port
0 ignored issues
show
Bug introduced by
There is no parameter named $port. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
613
     * @return self
614
     */
615
    public function setServerAddress($server = null)
616
    {
617
        $this->updateProperty($this->server, "server", $server);
618
619
        return $this;
620
    }
621
622
    /**
623
     * Get the name of the replay file for this specific map
624
     * @param  int    $length The length of the replay file name; it will be truncated
625
     * @return string Returns null if there was no replay file name recorded
626
     */
627 1
    public function getReplayFileName($length = 0)
628
    {
629 1
        if ($length > 0) {
630
            return substr($this->replay_file, 0, $length);
631
        }
632
633 1
        return $this->replay_file;
634
    }
635
636
    /**
637
     * Get the match duration
638
     * @return int The duration of the match in minutes
639
     */
640 3
    public function getDuration()
641
    {
642 3
        return $this->duration;
643
    }
644
645
    /**
646
     * Set the match duration
647
     *
648
     * @param  int  $duration The new duration of the match in minutes
649
     * @return self
650
     */
651
    public function setDuration($duration)
652
    {
653
        return $this->updateProperty($this->duration, "duration", $duration);
654
    }
655
656
    /**
657
     * Get the user who entered the match
658
     * @return Player
659
     */
660 2
    public function getEnteredBy()
661
    {
662 2
        return Player::get($this->entered_by);
663
    }
664
665
    /**
666
     * Get the loser of the match
667
     *
668
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
669
     */
670 8
    public function getLoser()
671
    {
672
        // Get the winner of the match
673 8
        $winner = $this->getWinner();
674
675
        // Get the team that wasn't the winner... Duh
676 8
        return $this->getOpponent($winner);
677
    }
678
679
    /**
680
     * Get the winner of a match
681
     *
682
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
683
     */
684 8
    public function getWinner()
685
    {
686
        // Get the team that had its ELO increased
687 8
        if ($this->elo_diff > 0) {
688 6
            return $this->getTeamA();
689 3
        } elseif ($this->elo_diff < 0) {
690 2
            return $this->getTeamB();
691 1
        } elseif ($this->team_a_points > $this->team_b_points) {
692
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
693 1
            return $this->getTeamA();
694
        } elseif ($this->team_a_points < $this->team_b_points) {
695
            return $this->getTeamB();
696
        }
697
698
        // If the scores are the same, return Team A because well, fuck you that's why
699
        return $this->getTeamA();
700
    }
701
702
    /**
703
     * Determine whether the match was a draw
704
     * @return bool True if the match ended without any winning teams
705
     */
706 9
    public function isDraw()
707
    {
708 9
        return $this->team_a_points == $this->team_b_points;
709
    }
710
711
    /**
712
     * Find out whether the match involves a team
713
     *
714
     * @param  TeamInterface $team The team to check
715
     * @return bool
716
     */
717 1
    public function involvesTeam($team)
718
    {
719 1
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
720
    }
721
722
    /**
723
     * Find out if the match is played between official teams
724
     */
725 1
    public function isOfficial()
726
    {
727 1
        return self::OFFICIAL === $this->getMatchType();
728
    }
729
730
    /**
731
     * Reset the ELOs of the teams participating in the match
732
     *
733
     * @return self
734
     */
735
    public function resetELOs()
736
    {
737
        if ($this->match_type === self::OFFICIAL) {
738
            $this->getTeamA()->changeELO(-$this->elo_diff);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeELO() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
739
            $this->getTeamB()->changeELO(+$this->elo_diff);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeELO() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
740
        }
741
742
        return $this;
743
    }
744
745
    /**
746
     * Calculate the match's contribution to the team activity
747
     *
748
     * @return float
749
     */
750 1
    public function getActivity()
751
    {
752 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
753 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
754
755 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
756
757 1
        if (is_nan($activity) || $activity < 0.0) {
758
            return 0.0;
759
        }
760
761 1
        return $activity;
762
    }
763
764
    /**
765
     * Enter a new match to the database
766
     * @param  int             $a          Team A's ID
767
     * @param  int             $b          Team B's ID
768
     * @param  int             $a_points   Team A's match points
769
     * @param  int             $b_points   Team B's match points
770
     * @param  int             $duration   The match duration in minutes
771
     * @param  int|null        $entered_by The ID of the player reporting the match
772
     * @param  string|DateTime $timestamp  When the match was played
773
     * @param  int[]           $a_players  The IDs of the first team's players
774
     * @param  int[]           $b_players  The IDs of the second team's players
775
     * @param  string|null     $server     The address of the server where the match was played
776
     * @param  int|null        $port       The port of the server where the match was played
0 ignored issues
show
Bug introduced by
There is no parameter named $port. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
777
     * @param  string          $replayFile The name of the replay file of the match
778
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
779
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
780
     * @param  string          $a_color    Team A's color
781
     * @param  string          $b_color    Team b's color
782
     * @return Match           An object representing the match that was just entered
783
     */
784 9
    public static function enterMatch(
785
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
786
        $a_players = array(), $b_players = array(), $server = null, $replayFile = null,
787
        $map = null, $matchType = "official", $a_color = null, $b_color = null
788
    ) {
789
        $matchData = array(
790 9
            'team_a_color'   => strtolower($a_color),
791 9
            'team_b_color'   => strtolower($b_color),
792 9
            'team_a_points'  => $a_points,
793 9
            'team_b_points'  => $b_points,
794 9
            'team_a_players' => implode(',', $a_players),
795 9
            'team_b_players' => implode(',', $b_players),
796 9
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
797 9
            'duration'       => $duration,
798 9
            'entered_by'     => $entered_by,
799 9
            'server'         => $server,
800 9
            'replay_file'    => $replayFile,
801 9
            'map'            => $map,
802 9
            'status'         => 'entered',
803 9
            'match_type'     => $matchType
804
        );
805
806 9
        if ($matchType === self::OFFICIAL) {
807 9
            $team_a = Team::get($a);
808 9
            $team_b = Team::get($b);
809 9
            $a_elo = $team_a->getElo();
810 9
            $b_elo = $team_b->getElo();
811
812 9
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
813
814
            // Update team ELOs
815 9
            $team_a->changeElo($diff);
816 9
            $team_b->changeElo(-$diff);
817
818 9
            $matchData = array_merge($matchData, array(
819 9
                'team_a'         => $a,
820 9
                'team_b'         => $b,
821 9
                'team_a_elo_new' => $team_a->getElo(),
822 9
                'team_b_elo_new' => $team_b->getElo(),
823 9
                'elo_diff'       => $diff
824
            ));
825
        }
826
827 9
        $match = self::create($matchData, 'updated');
828
829 9
        if ($matchType === self::OFFICIAL) {
830 9
            $match->updateMatchCount();
831
        }
832
833 9
        $players = $match->getPlayers();
834
835 9
        Database::getInstance()->startTransaction();
836
837 9
        foreach ($players as $player) {
0 ignored issues
show
Bug introduced by
The expression $players of type null|array<integer,object<Model>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
838 9
            $player->setLastMatch($match->getId());
839
        }
840
841 9
        Database::getInstance()->finishTransaction();
842
843 9
        return $match;
844
    }
845
846
    /**
847
     * Calculate the ELO score difference
848
     *
849
     * Computes the ELO score difference on each team after a match, based on
850
     * GU League's rules.
851
     *
852
     * @param  int $a_elo    Team A's current ELO score
853
     * @param  int $b_elo    Team B's current ELO score
854
     * @param  int $a_points Team A's match points
855
     * @param  int $b_points Team B's match points
856
     * @param  int $duration The match duration in minutes
857
     * @return int The ELO score difference
858
     */
859 9
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
860
    {
861 9
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
862 9
        if ($a_points > $b_points) {
863 5
            $diff = 50 * (1 - $prob);
864 5
        } elseif ($a_points == $b_points) {
865 4
            $diff = 50 * (0.5 - $prob);
866
        } else {
867 1
            $diff = 50 * (0 - $prob);
868
        }
869
870
        // Apply ELO modifiers from `config.yml`
871 9
        $durations = Service::getParameter('bzion.league.duration');
872 9
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
873
874 9
        if (abs($diff) < 1 && $diff != 0) {
875
            // ELOs such as 0.75 should round up to 1...
876 2
            return ($diff > 0) ? 1 : -1;
877
        }
878
879
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
880 7
        return intval($diff);
881
    }
882
883
    /**
884
     * Find if a match's stored ELO is correct
885
     */
886
    public function isEloCorrect()
887
    {
888
        return $this->elo_diff === $this->calculateEloDiff(
889
            $this->getTeamAEloOld(),
890
            $this->getTeamBEloOld(),
891
            $this->getTeamAPoints(),
892
            $this->getTeamBPoints(),
893
            $this->getDuration()
894
        );
895
    }
896
897
    /**
898
     * Recalculate the match's elo and adjust the team ELO values
899
     */
900
    public function recalculateElo()
901
    {
902
        if ($this->match_type !== self::OFFICIAL) {
903
            return;
904
        }
905
906
        $a = $this->getTeamA();
907
        $b = $this->getTeamB();
908
909
        $elo = $this->calculateEloDiff(
910
            $a->getElo(),
911
            $b->getElo(),
912
            $this->getTeamAPoints(),
913
            $this->getTeamBPoints(),
914
            $this->getDuration()
915
        );
916
917
        $this->updateProperty($this->elo_diff, "elo_diff", $elo);
918
919
        $a->changeElo($elo);
920
        $b->changeElo(-$elo);
921
922
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo());
923
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo());
924
    }
925
926
    /**
927
     * Get all the matches in the database
928
     */
929 1
    public static function getMatches()
930
    {
931 1
        return self::getQueryBuilder()->active()->getModels();
932
    }
933
934
    /**
935
     * Get a query builder for matches
936
     * @return MatchQueryBuilder
937
     */
938 3
    public static function getQueryBuilder()
939
    {
940 3
        return new MatchQueryBuilder('Match', array(
941
            'columns' => array(
942
                'firstTeam'        => 'team_a',
943
                'secondTeam'       => 'team_b',
944
                'firstTeamPoints'  => 'team_a_points',
945
                'secondTeamPoints' => 'team_b_points',
946
                'time'             => 'timestamp',
947
                'map'              => 'map',
948
                'type'             => 'match_type',
949
                'status'           => 'status'
950 3
            ),
951
        ));
952
    }
953
954
    /**
955
     * {@inheritdoc}
956
     */
957
    public function delete()
958
    {
959
        $this->updateMatchCount(true);
960
961
        return parent::delete();
962
    }
963
964
    /**
965
     * {@inheritdoc}
966
     */
967 3
    public static function getActiveStatuses()
968
    {
969 3
        return array('entered');
970
    }
971
972
    /**
973
     * {@inheritdoc}
974
     */
975 1
    public function getName()
976
    {
977 1
        switch ($this->getMatchType()) {
978 1
            case self::OFFICIAL:
979 1
                $description = "(+/- " . $this->getEloDiff() . ") ";
980 1
                break;
981 1
            case self::FUN:
982 1
                $description = "Fun Match:";
983 1
                break;
984
            case self::SPECIAL:
985
                $description = "Special Match: ";
986
                break;
987
            default:
988
                $description = "";
989
        }
990
991 1
        return sprintf("%s %s [%d] vs [%d] %s",
992
            $description,
993 1
            $this->getWinner()->getName(),
994 1
            $this->getScore($this->getWinner()),
995 1
            $this->getScore($this->getLoser()),
996 1
            $this->getLoser()->getName()
997
        );
998
    }
999
1000
    /**
1001
     * Update the match count of the teams participating in the match
1002
     *
1003
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1004
     */
1005 9
    private function updateMatchCount($decrement = false)
1006
    {
1007 9
        if ($this->match_type !== self::OFFICIAL) {
1008
            return;
1009
        }
1010
1011 9
        $diff = ($decrement) ? -1 : 1;
1012
1013 9
        if ($this->isDraw()) {
1014 4
            $this->getTeamA()->changeMatchCount($diff, 'draw');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1015 4
            $this->getTeamB()->changeMatchCount($diff, 'draw');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1016
        } else {
1017 6
            $this->getWinner()->changeMatchCount($diff, 'win');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1018 6
            $this->getLoser()->changeMatchCount($diff, 'loss');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1019
        }
1020 9
    }
1021
}
1022