Completed
Push — feature/player-elo ( 60e1be...8997fd )
by Vladimir
10:30
created

Match::recalculateMatchesSince()   C

Complexity

Conditions 9
Paths 84

Size

Total Lines 62
Code Lines 32

Duplication

Lines 8
Ratio 12.9 %

Code Coverage

Tests 24
CRAP Score 10.2655

Importance

Changes 0
Metric Value
dl 8
loc 62
ccs 24
cts 32
cp 0.75
rs 6.6867
c 0
b 0
f 0
cc 9
eloc 32
nc 84
nop 1
crap 10.2655

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    const TEAM_V_TEAM   = 0;
21
    const TEAM_V_MIXED  = 1;
22
    const MIXED_V_MIXED = 2;
23
24
    use Timestamp;
25
26
    /**
27
     * The ID of the first team of the match
28
     * @var int
29
     */
30
    protected $team_a;
31
32
    /**
33
     * The ID of the second team of the match
34
     * @var int
35
     */
36
    protected $team_b;
37
38
    /**
39
     * The color of the first team
40
     * @var string
41
     */
42
    protected $team_a_color;
43
44
    /**
45
     * The color of the second team
46
     * @var string
47
     */
48
    protected $team_b_color;
49
50
    /**
51
     * The match points (usually the number of flag captures) Team A scored
52
     * @var int
53
     */
54
    protected $team_a_points;
55
56
    /**
57
     * The match points Team B scored
58
     * @var int
59
     */
60
    protected $team_b_points;
61
62
    /**
63
     * The BZIDs of players part of Team A who participated in the match, separated by commas
64
     * @var string
65
     */
66
    protected $team_a_players;
67
68
    /**
69
     * The BZIDs of players part of Team B who participated in the match, separated by commas
70
     * @var string
71
     */
72
    protected $team_b_players;
73
74
    /**
75
     * The ELO score of Team A after the match
76
     * @var int
77
     */
78
    protected $team_a_elo_new;
79
80
    /**
81
     * The ELO score of Team B after the match
82
     * @var int
83
     */
84
    protected $team_b_elo_new;
85
86
    /**
87
     * The map ID used in the match if the league supports more than one map
88
     * @var int
89
     */
90
    protected $map;
91
92
    /**
93
     * The type of match that occurred. Valid options: official, fm, special
94
     *
95
     * @var string
96
     */
97
    protected $match_type;
98
99
    /**
100
     * A JSON string of events that happened during a match, such as captures and substitutions
101
     * @var string
102
     */
103
    protected $match_details;
104
105
    /**
106
     * The server location of there the match took place
107
     * @var string
108
     */
109
    protected $server;
110
111
    /**
112
     * The file name of the replay file of the match
113
     * @var string
114
     */
115
    protected $replay_file;
116
117
    /**
118
     * The value of the ELO score difference
119
     * @var int
120
     */
121
    protected $elo_diff;
122
123
    /**
124
     * The value of the player Elo difference
125
     * @var int
126
     */
127
    protected $player_elo_diff;
128
129
    /**
130
     * The timestamp representing when the match information was last updated
131
     * @var TimeDate
132
     */
133
    protected $updated;
134
135
    /**
136
     * The duration of the match in minutes
137
     * @var int
138
     */
139
    protected $duration;
140
141
    /**
142
     * The ID of the person (i.e. referee) who last updated the match information
143
     * @var string
144
     */
145
    protected $entered_by;
146
147
    /**
148
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
149
     * @var string
150
     */
151
    protected $status;
152
153
    /**
154
     * The name of the database table used for queries
155
     */
156
    const TABLE = "matches";
157
158
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
159
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
160
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
161
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 41
    protected function assignResult($match)
167
    {
168 41
        $this->team_a = $match['team_a'];
169 41
        $this->team_b = $match['team_b'];
170 41
        $this->team_a_color = $match['team_a_color'];
171 41
        $this->team_b_color = $match['team_b_color'];
172 41
        $this->team_a_points = $match['team_a_points'];
173 41
        $this->team_b_points = $match['team_b_points'];
174 41
        $this->team_a_players = $match['team_a_players'];
175 41
        $this->team_b_players = $match['team_b_players'];
176 41
        $this->team_a_elo_new = $match['team_a_elo_new'];
177 41
        $this->team_b_elo_new = $match['team_b_elo_new'];
178 41
        $this->map = $match['map'];
179 41
        $this->match_type = $match['match_type'];
180 41
        $this->match_details = $match['match_details'];
181 41
        $this->server = $match['server'];
182 41
        $this->replay_file = $match['replay_file'];
183 41
        $this->elo_diff = $match['elo_diff'];
184 41
        $this->player_elo_diff = $match['player_elo_diff'];
185 41
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
186 41
        $this->updated = TimeDate::fromMysql($match['updated']);
187 41
        $this->duration = $match['duration'];
188 41
        $this->entered_by = $match['entered_by'];
189 41
        $this->status = $match['status'];
190 41
    }
191
192
    /**
193
     * Get the name of the route that shows the object
194
     * @param  string $action The route's suffix
195
     * @return string
196
     */
197 1
    public static function getRouteName($action = 'show')
198
    {
199 1
        return "match_$action";
200
    }
201
202
    /**
203
     * Get a one word description of a match relative to a team (i.e. win, loss, or draw)
204
     *
205
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
206
     *
207
     * @return string Either "win", "loss", or "draw" relative to the team
208
     */
209 1
    public function getMatchDescription($teamID)
210
    {
211 1
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
212 1
            return "win";
213 1
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
214 1
            return "loss";
215
        }
216
217 1
        return "tie";
218
    }
219
220
    /**
221
     * Get a one letter description of a match relative to a team (i.e. W, L, or T)
222
     *
223
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
224
     *
225
     * @return string Either "W", "L", or "T" relative to the team
226
     */
227 1
    public function getMatchLetter($teamID)
228
    {
229 1
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
230
    }
231
232
    /**
233
     * Get the score of a specific team
234
     *
235
     * @param int|string|TeamInterface $teamID The team we want the score for
236
     *
237
     * @return int The score that team received
238
     */
239 23 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...
240
    {
241 23
        if ($teamID instanceof TeamInterface) {
242
            // Oh no! The caller gave us a Team model instead of an ID!
243 23
            $teamID = $teamID->getId();
244 2
        } elseif (is_string($teamID)) {
245
            // Make sure we're comparing lowercase strings
246
            $teamID = strtolower($teamID);
247
        }
248
249 23
        if ($this->getTeamA()->getId() == $teamID) {
250 23
            return $this->getTeamAPoints();
251
        }
252
253 23
        return $this->getTeamBPoints();
254
    }
255
256
    /**
257
     * Get the score of the opponent relative to a team
258
     *
259
     * @param int|string|TeamInterface $teamID The opponent of the team we want the score for
260
     *
261
     * @return int The score of the opponent
262
     */
263 2
    public function getOpponentScore($teamID)
264
    {
265 2
        return $this->getScore($this->getOpponent($teamID));
266
    }
267
268
    /**
269
     * Get the opponent of a match relative to a team ID
270
     *
271
     * @param int|string|TeamInterface $teamID The team who is known in a match
272
     *
273
     * @return TeamInterface The opponent team
274
     */
275 36 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...
276
    {
277 36
        if ($teamID instanceof TeamInterface) {
278 36
            $teamID = $teamID->getId();
279 2
        } elseif (is_string($teamID)) {
280
            $teamID = strtolower($teamID);
281
        }
282
283 36
        if ($this->getTeamA()->getId() == $teamID) {
284 24
            return $this->getTeamB();
285
        }
286
287 14
        return $this->getTeamA();
288
    }
289
290
    /**
291
     * Get the timestamp of the last update of the match
292
     *
293
     * @return TimeDate The match's update timestamp
294
     */
295 1
    public function getUpdated()
296
    {
297 1
        return $this->updated->copy();
298
    }
299
300
    /**
301
     * Set the timestamp of the match
302
     *
303
     * @param  mixed $timestamp The match's new timestamp
304
     * @return $this
305
     */
306
    public function setTimestamp($timestamp)
307
    {
308
        $this->updateProperty($this->timestamp, "timestamp", TimeDate::from($timestamp));
309
310
        return $this;
311
    }
312
313
    /**
314
     * Get the first team involved in the match
315
     * @return TeamInterface Team A
316
     */
317 40
    public function getTeamA()
318
    {
319 40
        $team = Team::get($this->team_a);
320
321 40
        if ($this->match_type === self::OFFICIAL && $team->isValid()) {
322 29
            return $team;
323
        }
324
325 12
        return new ColorTeam($this->team_a_color);
326
    }
327
328
    /**
329
     * Get the second team involved in the match
330
     * @return TeamInterface Team B
331
     */
332 40
    public function getTeamB()
333
    {
334 40
        $team = Team::get($this->team_b);
335
336 40
        if ($this->match_type === self::OFFICIAL && $team->isValid()) {
337 27
            return $team;
338
        }
339
340 14
        return new ColorTeam($this->team_b_color);
341
    }
342
343
    /**
344
     * Get the color of Team A
345
     * @return string
346
     */
347
    public function getTeamAColor()
348
    {
349
        return $this->team_a_color;
350
    }
351
352
    /**
353
     * Get the color of Team B
354
     * @return string
355
     */
356
    public function getTeamBColor()
357
    {
358
        return $this->team_b_color;
359
    }
360
361
    /**
362
     * Get the list of players on Team A who participated in this match
363
     * @return Player[] Returns null if there were no players recorded for this match
364
     */
365 39
    public function getTeamAPlayers()
366
    {
367 39
        return $this->parsePlayers($this->team_a_players);
368
    }
369
370
    /**
371
     * Get the list of players on Team B who participated in this match
372
     * @return Player[] Returns null if there were no players recorded for this match
373
     */
374 39
    public function getTeamBPlayers()
375
    {
376 39
        return $this->parsePlayers($this->team_b_players);
377
    }
378
379
    /**
380
     * Get the list of players for a team in a match
381
     * @param  Team|int|null The team or team ID
382
     * @return Player[]|null Returns null if there were no players recorded for this match
383
     */
384 4
    public function getPlayers($team = null)
385
    {
386 4
        if ($team instanceof TeamInterface) {
387 1
            $team = $team->getId();
388
        }
389
390 4
        if ($this->getTeamA()->isValid() && $team === $this->getTeamA()->getId()) {
391 1
            return $this->getTeamAPlayers();
392 4
        } elseif ($this->getTeamB()->isValid() && $team === $this->getTeamB()->getId()) {
393 1
            return $this->getTeamBPlayers();
394
        }
395
396 3
        return $this->parsePlayers($this->team_a_players . "," . $this->team_b_players);
397
    }
398
399
    /**
400
     * Set the players of the match's teams
401
     *
402
     * @param int[] $teamAPlayers An array of player IDs
403
     * @param int[] $teamBPlayers An array of player IDs
404
     * @return self
405
     */
406
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
407
    {
408
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers));
409
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers));
410
411
        return $this;
412
    }
413
414
    /**
415
     * Get an array of players based on a string representation
416
     * @param string $playerString
417
     * @return Player[] Returns null if there were no players recorded for this match
418
     */
419 39
    private function parsePlayers($playerString)
420
    {
421 39
        if ($playerString == null) {
422 12
            return [];
423
        }
424
425 30
        return Player::arrayIdToModel(explode(",", $playerString));
426
    }
427
428
    /**
429
     * Get the first team's points
430
     * @return int Team A's points
431
     */
432 28
    public function getTeamAPoints()
433
    {
434 28
        return $this->team_a_points;
435
    }
436
437
    /**
438
     * Get the second team's points
439
     * @return int Team B's points
440
     */
441 28
    public function getTeamBPoints()
442
    {
443 28
        return $this->team_b_points;
444
    }
445
446
    /**
447
     * Set the match team points
448
     *
449
     * @param  int $teamAPoints Team A's points
450
     * @param  int $teamBPoints Team B's points
451
     * @return self
452
     */
453 1
    public function setTeamPoints($teamAPoints, $teamBPoints)
454
    {
455 1
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints);
456 1
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints);
457
458 1
        return $this;
459
    }
460
461
    /**
462
     * Set the match team colors
463
     *
464
     * @param  ColorTeam|string $teamAColor The color of team A
465
     * @param  ColorTeam|string $teamBColor The color of team B
466
     * @return self
467
     */
468
    public function setTeamColors($teamAColor, $teamBColor)
469
    {
470
        if ($this->isOfficial()) {
471
            throw new \Exception("Cannot change team colors in an official match");
472
        }
473
474
        if ($teamAColor instanceof ColorTeam) {
475
            $teamAColor = $teamAColor->getId();
476
        }
477
        if ($teamBColor instanceof ColorTeam) {
478
            $teamBColor = $teamBColor->getId();
479
        }
480
481
        $this->updateProperty($this->team_a_color, "team_a_color", $teamAColor);
482
        $this->updateProperty($this->team_b_color, "team_b_color", $teamBColor);
483
    }
484
485
    /**
486
     * Get the ELO difference applied to each team's old ELO
487
     *
488
     * @param bool $absoluteValue Whether or not to get the absolute value of the Elo difference
489
     *
490
     * @return int The ELO difference
491
     */
492 30
    public function getEloDiff($absoluteValue = true)
493
    {
494 30
        return ($absoluteValue) ? abs($this->elo_diff) : $this->elo_diff;
495
    }
496
497
    /**
498
     * Get the Elo difference applied to players
499
     *
500
     * @param bool $absoluteValue Whether or not to get the absolute value of the Elo difference
501
     *
502
     * @return int The Elo difference for players
503
     */
504 39
    public function getPlayerEloDiff($absoluteValue = true)
505
    {
506 39
        return ($absoluteValue) ? abs($this->player_elo_diff) : $this->player_elo_diff;
507
    }
508
509
    /**
510
     * Set the Elo difference applied to players
511
     *
512
     * @param int $diff
513
     */
514
    public function setPlayerEloDiff($diff)
515
    {
516
        $this->updateProperty($this->player_elo_diff, 'player_elo_diff', $diff);
517
    }
518
519
    /**
520
     * Get the first team's new ELO
521
     * @return int Team A's new ELO
522
     */
523 7
    public function getTeamAEloNew()
524
    {
525 7
        return $this->team_a_elo_new;
526
    }
527
528
    /**
529
     * Get the second team's new ELO
530
     * @return int Team B's new ELO
531
     */
532 7
    public function getTeamBEloNew()
533
    {
534 7
        return $this->team_b_elo_new;
535
    }
536
537
    /**
538
     * Get the first team's old ELO
539
     * @return int
540
     */
541 7
    public function getTeamAEloOld()
542
    {
543 7
        return $this->team_a_elo_new - $this->elo_diff;
544
    }
545
546
    /**
547
     * Get the second team's old ELO
548
     * @return int
549
     */
550 7
    public function getTeamBEloOld()
551
    {
552 7
        return $this->team_b_elo_new + $this->elo_diff;
553
    }
554
555
    /**
556
     * Get the team's new ELO
557
     * @param  Team $team The team whose new ELO to return
558
     * @return int|null   The new ELO, or null if the team provided has not
559
     *                    participated in the match
560
     */
561 1
    public function getTeamEloNew(Team $team)
562
    {
563 1
        if ($team->getId() == $this->team_a) {
564 1
            return $this->getTeamAEloNew();
565 1
        } elseif ($team->getId() == $this->team_b) {
566 1
            return $this->getTeamBEloNew();
567
        }
568
569
        return null;
570
    }
571
572
    /**
573
     * Get the team's old ELO
574
     * @param  Team $team The team whose old ELO to return
575
     * @return int|null   The old ELO, or null if the team provided has not
576
     *                    participated in the match
577
     */
578 1
    public function getTeamEloOld(Team $team)
579
    {
580 1
        if ($team->getId() == $this->team_a) {
581 1
            return $this->getTeamAEloOld();
582 1
        } elseif ($team->getId() == $this->team_b) {
583 1
            return $this->getTeamBEloOld();
584
        }
585
586
        return null;
587
    }
588
589
    /**
590
     * Get the map where the match was played on
591
     * @return Map Returns an invalid map if no map was found
592
     */
593 1
    public function getMap()
594
    {
595 1
        return Map::get($this->map);
596
    }
597
598
    /**
599
     * Set the map where the match was played
600
     * @param  int $map The ID of the map
601
     * @return self
602
     */
603
    public function setMap($map)
604
    {
605
        $this->updateProperty($this->map, "map", $map, "s");
606
    }
607
608
    /**
609
     * Get the type of official match this is. Whether it has just traditional teams or has mixed teams.
610
     *
611
     * Possible official match types:
612
     *   - Team vs Team
613
     *   - Team vs Mixed
614
     *   - Mixed vs Mixed
615
     *
616
     * @see Match::TEAM_V_TEAM
617
     * @see Match::TEAM_V_MIXED
618
     * @see Match::MIXED_V_MIXED
619
     *
620
     * @return int
621
     */
622 23
    public function getTeamMatchType()
623
    {
624 23
        if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) {
625 9
            return self::TEAM_V_TEAM;
626 14
        } elseif ($this->getTeamA()->supportsMatchCount() xor $this->getTeamB()->supportsMatchCount()) {
627 9
            return self::TEAM_V_MIXED;
628
        }
629
630 5
        return self::MIXED_V_MIXED;
631
    }
632
633
    /**
634
     * Get the match type
635
     *
636
     * @return string 'official', 'fm', or 'special'
637
     */
638 22
    public function getMatchType()
639
    {
640 22
        return $this->match_type;
641
    }
642
643
    /**
644
     * Set the match type
645
     *
646
     * @param  string $matchType A valid match type; official, fm, special
647
     *
648
     * @return static
649
     */
650
    public function setMatchType($matchType)
651
    {
652
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
653
    }
654
655
    /**
656
     * Get a JSON decoded array of events that occurred during the match
657
     * @return mixed|null Returns null if there were no events recorded for the match
658
     */
659
    public function getMatchDetails()
660
    {
661
        return json_decode($this->match_details);
662
    }
663
664
    /**
665
     * Get the server address of the server where this match took place
666
     * @return string|null Returns null if there was no server address recorded
667
     */
668 1
    public function getServerAddress()
669
    {
670 1
        return $this->server;
671
    }
672
673
    /**
674
     * Set the server address of the server where this match took place
675
     *
676
     * @param  string|null $server The server hostname
677
     * @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...
678
     * @return self
679
     */
680
    public function setServerAddress($server = null)
681
    {
682
        $this->updateProperty($this->server, "server", $server);
683
684
        return $this;
685
    }
686
687
    /**
688
     * Get the name of the replay file for this specific map
689
     * @param  int    $length The length of the replay file name; it will be truncated
690
     * @return string Returns null if there was no replay file name recorded
691
     */
692 1
    public function getReplayFileName($length = 0)
693
    {
694 1
        if ($length > 0) {
695
            return substr($this->replay_file, 0, $length);
696
        }
697
698 1
        return $this->replay_file;
699
    }
700
701
    /**
702
     * Get the match duration
703
     * @return int The duration of the match in minutes
704
     */
705 6
    public function getDuration()
706
    {
707 6
        return $this->duration;
708
    }
709
710
    /**
711
     * Set the match duration
712
     *
713
     * @param  int  $duration The new duration of the match in minutes
714
     * @return self
715
     */
716
    public function setDuration($duration)
717
    {
718
        return $this->updateProperty($this->duration, "duration", $duration);
719
    }
720
721
    /**
722
     * Get the user who entered the match
723
     * @return Player
724
     */
725 2
    public function getEnteredBy()
726
    {
727 2
        return Player::get($this->entered_by);
728
    }
729
730
    /**
731
     * Get the loser of the match
732
     *
733
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
734
     */
735 36
    public function getLoser()
736
    {
737
        // Get the winner of the match
738 36
        $winner = $this->getWinner();
739
740
        // Get the team that wasn't the winner... Duh
741 36
        return $this->getOpponent($winner);
742
    }
743
744
    /**
745
     * Get the winner of a match
746
     *
747
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
748
     */
749 36
    public function getWinner()
750
    {
751
        // "As Mother Teresa once said, it's not enough if you win. Others must lose."
752
        //   -Stephen Colbert
753
754
        // Get the team that had its Elo increased or the team whose players had their Elo increased
755 36
        if ($this->elo_diff > 0 || $this->player_elo_diff > 0) {
756 22
            return $this->getTeamA();
757 15
        } elseif ($this->elo_diff < 0 || $this->player_elo_diff < 0) {
758 12
            return $this->getTeamB();
759 3
        } elseif ($this->team_a_points > $this->team_b_points) {
760
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
761 2
            return $this->getTeamA();
762 1
        } elseif ($this->team_a_points < $this->team_b_points) {
763
            return $this->getTeamB();
764
        }
765
766
        // If the scores are the same, return Team A because well, fuck you that's why
767 1
        return $this->getTeamA();
768
    }
769
770
    /**
771
     * Determine whether the match was a draw
772
     * @return bool True if the match ended without any winning teams
773
     */
774 39
    public function isDraw()
775
    {
776 39
        return $this->team_a_points == $this->team_b_points;
777
    }
778
779
    /**
780
     * Find out whether the match involves a team
781
     *
782
     * @param  TeamInterface $team The team to check
783
     * @return bool
784
     */
785 1
    public function involvesTeam($team)
786
    {
787 1
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
788
    }
789
790
    /**
791
     * Find out if the match is played between official teams
792
     */
793 22
    public function isOfficial()
794
    {
795 22
        return self::OFFICIAL === $this->getMatchType();
796
    }
797
798
    /**
799
     * Reset the ELOs of the teams participating in the match
800
     *
801
     * @return self
802
     */
803
    public function resetELOs()
804
    {
805
        if ($this->match_type === self::OFFICIAL) {
806
            $this->getTeamA()->supportsMatchCount() && $this->getTeamA()->changeELO(-$this->elo_diff);
0 ignored issues
show
Bug introduced by
The method changeELO() does not seem to exist on object<TeamInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
807
            $this->getTeamB()->supportsMatchCount() && $this->getTeamB()->changeELO(+$this->elo_diff);
0 ignored issues
show
Bug introduced by
The method changeELO() does not seem to exist on object<TeamInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
808
        }
809
810
        return $this;
811
    }
812
813
    /**
814
     * Calculate the match's contribution to the team activity
815
     *
816
     * @return float
817
     */
818 1
    public function getActivity()
819
    {
820 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
821 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
822
823 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
824
825 1
        if (is_nan($activity) || $activity < 0.0) {
826
            return 0.0;
827
        }
828
829 1
        return $activity;
830
    }
831
832
    /**
833
     * Calculate the Elo differences for players and teams for a given match.
834
     *
835
     * @param  Team $a
836
     * @param  Team $b
837
     * @param  int  $a_points
838
     * @param  int  $b_points
839
     * @param  int[]|Player[] $a_players
840
     * @param  int[]|Player[] $b_players
841
     * @param  int  $duration
842
     *
843
     * @throws InvalidArgumentException When a "Mixed" team is entered without a player roster
844
     *
845
     * @return array
846
     */
847 40
    private static function calculateElos($a, $b, $a_points, $b_points, $a_players, $b_players, $duration)
848
    {
849
        // Get the type of official match
850 40
        $matchType = Match::MIXED_V_MIXED;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
851
852 40
        if ($a->supportsMatchCount() && $b->supportsMatchCount()) {
853 23
            $matchType = Match::TEAM_V_TEAM;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
854 18
        } elseif ($a->supportsMatchCount() xor $b->supportsMatchCount()) {
855 12
            $matchType = Match::TEAM_V_MIXED;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
856
        }
857
858 40
        if ($matchType == Match::TEAM_V_MIXED &&
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
859 40
            ((!$a->isValid() && empty($a_players)) || (!$b->isValid() && empty($b_players)))) {
860 1
            throw new InvalidArgumentException('A Mixed team must have a player roster to calculate the Elo for team Elo differences');
861
        }
862
863
        //
864
        // Handle Player Elo Diff Calculations
865
        //
866
867
        // By default, we won't have a player Elo difference since we won't force matches to have a roster
868 39
        $playerEloDiff = null;
869
870 39
        $a_players_elo = 1200;
871 39
        $b_players_elo = 1200;
872
873
        // Only bother to calculate a player Elo diff if we have players reported for both teams
874 39
        if (!empty($a_players) && !empty($b_players)) {
875 28
            $a_players_elo = self::getAveragePlayerElo($a_players);
876 28
            $b_players_elo = self::getAveragePlayerElo($b_players);
877
878 28
            $playerEloDiff = self::calculateEloDiff($a_players_elo, $b_players_elo, $a_points, $b_points, $duration);
879
        }
880
881
        //
882
        // Handle Team Elo Diff Calculations
883
        //
884
885
        // By default, we'll assume a Mixed vs Mixed official match where Elos do not change for teams
886 39
        $teamEloDiff = null;
887
888
        // Work with calculations for team Elos to handle the following situations:
889
        //   - Team vs Team  :: Use team Elos for calculations
890
        //   - Team vs Mixed :: Use team Elo and the player average Elo for the "Mixed" team
891 39
        if ($matchType == Match::TEAM_V_TEAM) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
892 23
            $teamEloDiff = self::calculateEloDiff($a->getElo(), $b->getElo(), $a_points, $b_points, $duration);
893 17
        } elseif ($matchType == Match::TEAM_V_MIXED) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
894 11
            $a_team_elo = ($a->supportsMatchCount()) ? $a->getElo() : $a_players_elo;
895 11
            $b_team_elo = ($b->supportsMatchCount()) ? $b->getElo() : $b_players_elo;
896
897 11
            $teamEloDiff = self::calculateEloDiff($a_team_elo, $b_team_elo, $a_points, $b_points, $duration);
898
        }
899
900
        return [
901 39
            'match_type' => $matchType,
902 39
            'team_elo'   => $teamEloDiff,
903 39
            'player_elo' => $playerEloDiff
904
        ];
905
    }
906
907
    /**
908
     * Enter a new match to the database
909
     * @param  int             $a          Team A's ID
910
     * @param  int             $b          Team B's ID
911
     * @param  int             $a_points   Team A's match points
912
     * @param  int             $b_points   Team B's match points
913
     * @param  int             $duration   The match duration in minutes
914
     * @param  int|null        $entered_by The ID of the player reporting the match
915
     * @param  string|DateTime $timestamp  When the match was played
916
     * @param  int[]           $a_players  The IDs of the first team's players
917
     * @param  int[]           $b_players  The IDs of the second team's players
918
     * @param  string|null     $server     The address of the server where the match was played
919
     * @param  string          $replayFile The name of the replay file of the match
920
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
921
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
922
     * @param  string          $a_color    Team A's color
923
     * @param  string          $b_color    Team b's color
924
     *
925
     * @throws InvalidArgumentException When a ColorTeam is selected for an official match and no players are defined
926
     *                                  for that team
927
     *
928
     * @return Match           An object representing the match that was just entered
929
     */
930 42
    public static function enterMatch(
931
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
932
        $a_players = array(), $b_players = array(), $server = null, $replayFile = null,
933
        $map = null, $matchType = "official", $a_color = null, $b_color = null
934
    ) {
935
        $matchData = array(
936 42
            'team_a_color'   => strtolower($a_color),
937 42
            'team_b_color'   => strtolower($b_color),
938 42
            'team_a_points'  => $a_points,
939 42
            'team_b_points'  => $b_points,
940 42
            'team_a_players' => implode(',', $a_players),
941 42
            'team_b_players' => implode(',', $b_players),
942 42
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
943 42
            'duration'       => $duration,
944 42
            'entered_by'     => $entered_by,
945 42
            'server'         => $server,
946 42
            'replay_file'    => $replayFile,
947 42
            'map'            => $map,
948 42
            'status'         => 'entered',
949 42
            'match_type'     => $matchType
950
        );
951
952
        // (P)layer Elo Diff and (T)eam Elo Diff; respectively
953 42
        $tEloDiff = null;
0 ignored issues
show
Unused Code introduced by
$tEloDiff is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
954
955 42
        if ($matchType === self::OFFICIAL) {
956 40
            $team_a = Team::get($a);
957 40
            $team_b = Team::get($b);
958
959 40
            $eloCalcs = self::calculateElos($team_a, $team_b, $a_points, $b_points, $a_players, $b_players, $duration);
960
961 39
            $matchData['elo_diff'] = $tEloDiff = $eloCalcs['team_elo'];
962 39
            $matchData['player_elo_diff'] = $eloCalcs['player_elo'];
963
964
            // Update team ELOs
965 39
            if ($team_a->isValid()) {
966 29
                $team_a->adjustElo($tEloDiff);
967
968 29
                $matchData['team_a'] = $a;
969 29
                $matchData['team_a_elo_new'] = $team_a->getElo();
970
            }
971 39
            if ($team_b->isValid()) {
972 27
                $team_b->adjustElo(-$tEloDiff);
973
974 27
                $matchData['team_b'] = $b;
975 27
                $matchData['team_b_elo_new'] = $team_b->getElo();
976
            }
977
        }
978
979 41
        $match = self::create($matchData, 'updated');
980 41
        $match->updateMatchCount();
981 41
        $match->updatePlayerElo();
982
983 41
        return $match;
984
    }
985
986
    /**
987
     * Calculate the ELO score difference
988
     *
989
     * Computes the ELO score difference on each team after a match, based on
990
     * GU League's rules.
991
     *
992
     * @param  int $a_elo    Team A's current ELO score
993
     * @param  int $b_elo    Team B's current ELO score
994
     * @param  int $a_points Team A's match points
995
     * @param  int $b_points Team B's match points
996
     * @param  int $duration The match duration in minutes
997
     * @return int The ELO score difference
998
     */
999 39
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
1000
    {
1001 39
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
1002 39
        if ($a_points > $b_points) {
1003 14
            $diff = 50 * (1 - $prob);
1004 27
        } elseif ($a_points == $b_points) {
1005 19
            $diff = 50 * (0.5 - $prob);
1006
        } else {
1007 8
            $diff = 50 * (0 - $prob);
1008
        }
1009
1010
        // Apply ELO modifiers from `config.yml`
1011 39
        $durations = Service::getParameter('bzion.league.duration');
1012 39
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
1013
1014 39
        if (abs($diff) < 1 && $diff != 0) {
1015
            // ELOs such as 0.75 should round up to 1...
1016 2
            return ($diff > 0) ? 1 : -1;
1017
        }
1018
1019
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
1020 37
        return intval($diff);
1021
    }
1022
1023
    /**
1024
     * Find if a match's stored ELO is correct
1025
     */
1026
    public function isEloCorrect()
1027
    {
1028
        return $this->elo_diff === $this->calculateEloDiff(
1029
            $this->getTeamAEloOld(),
1030
            $this->getTeamBEloOld(),
1031
            $this->getTeamAPoints(),
1032
            $this->getTeamBPoints(),
1033
            $this->getDuration()
1034
        );
1035
    }
1036
1037
    /**
1038
     * Recalculate the match's elo and adjust the team ELO values
1039
     */
1040 3
    public function recalculateElo()
1041
    {
1042 3
        if ($this->match_type !== self::OFFICIAL) {
1043
            return;
1044
        }
1045
1046 3
        $a = $this->getTeamA();
1047 3
        $b = $this->getTeamB();
1048
1049 3
        $this->db->execute('DELETE FROM player_elo WHERE match_id = ?', [$this->getId()]);
1050
1051 3
        foreach ($this->getPlayers() as $player) {
0 ignored issues
show
Bug introduced by
The expression $this->getPlayers() of type array<integer,object<Player>>|null 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...
1052 3
            $player->invalidateMatchFromCache($this);
1053
        }
1054
1055 3
        $eloCalcs = self::calculateElos(
1056 3
            $a, $b,
0 ignored issues
show
Compatibility introduced by
$a of type object<TeamInterface> is not a sub-type of object<Team>. It seems like you assume a concrete implementation of the interface TeamInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$b of type object<TeamInterface> is not a sub-type of object<Team>. It seems like you assume a concrete implementation of the interface TeamInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
1057 3
            $this->getTeamAPoints(), $this->getTeamBPoints(),
1058 3
            $this->getTeamAPlayers(), $this->getTeamBPlayers(),
1059 3
            $this->getDuration()
1060
        );
1061
1062 3
        $elo = $eloCalcs['team_elo'];
1063
1064 3
        $this->updateProperty($this->elo_diff, 'elo_diff', $elo);
1065 3
        $this->updateProperty($this->player_elo_diff, 'player_elo_diff', $eloCalcs['player_elo']);
1066
1067 3
        if ($a->supportsMatchCount()) {
1068 3
            $a->adjustElo($elo);
1069 3
            $this->updateProperty($this->team_a_elo_new, 'team_a_elo_new', $a->getElo());
1070
        }
1071
1072 3
        if ($b->supportsMatchCount()) {
1073 3
            $b->adjustElo(-$elo);
1074 3
            $this->updateProperty($this->team_b_elo_new, 'team_b_elo_new', $b->getElo());
1075
        }
1076
1077 3
        $this->updatePlayerElo();
1078 3
    }
1079
1080
    /**
1081
     * Get all the matches in the database
1082
     */
1083 1
    public static function getMatches()
1084
    {
1085 1
        return self::getQueryBuilder()->active()->getModels();
1086
    }
1087
1088
    /**
1089
     * Get a query builder for matches
1090
     * @return MatchQueryBuilder
1091
     */
1092 26
    public static function getQueryBuilder()
1093
    {
1094 26
        return new MatchQueryBuilder('Match', array(
1095 26
            'columns' => array(
1096
                'firstTeam'        => 'team_a',
1097
                'secondTeam'       => 'team_b',
1098
                'firstTeamPoints'  => 'team_a_points',
1099
                'secondTeamPoints' => 'team_b_points',
1100
                'time'             => 'timestamp',
1101
                'map'              => 'map',
1102
                'type'             => 'match_type',
1103
                'status'           => 'status'
1104
            ),
1105
        ));
1106
    }
1107
1108
    /**
1109
     * {@inheritdoc}
1110
     */
1111 1
    public function delete()
1112
    {
1113 1
        $this->updateMatchCount(true);
1114
1115 1
        parent::delete();
1116 1
    }
1117
1118
    /**
1119
     * {@inheritdoc}
1120
     */
1121 3
    public static function getActiveStatuses()
1122
    {
1123 3
        return array('entered');
1124
    }
1125
1126
    /**
1127
     * {@inheritdoc}
1128
     */
1129 22
    public function getName()
1130
    {
1131 22
        $description = '';
1132
1133 22
        switch ($this->getMatchType()) {
1134 22
            case self::OFFICIAL:
1135
                // Only show Elo diff if both teams are actual teams
1136 21
                if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) {
1137 9
                    $description = "(+/- {$this->getEloDiff()})";
1138
                }
1139 21
                break;
1140
1141 2
            case self::FUN:
1142 2
                $description = 'Fun Match:';
1143 2
                break;
1144
1145
            case self::SPECIAL:
1146
                $description = 'Special Match:';
1147
                break;
1148
1149
            default:
1150
                break;
1151
        }
1152
1153 22
        return trim(sprintf('%s %s [%d] vs [%d] %s',
1154 22
            $description,
1155 22
            $this->getWinner()->getName(),
1156 22
            $this->getScore($this->getWinner()),
1157 22
            $this->getScore($this->getLoser()),
1158 22
            $this->getLoser()->getName()
1159
        ));
1160
    }
1161
1162
    /**
1163
     * Recalculates match history for all teams and matches
1164
     *
1165
     * Recalculation is done as follows:
1166
     * 1. A match is chosen as a starting point - it's stored old team ELOs are
1167
     *    considered correct
1168
     * 2. Team ELOs are reset to their values at the starting point
1169
     * 3. Each match that occurred since the first specified match has its ELO
1170
     *    recalculated based on the current team values, and the new match data
1171
     *    and team ELOs are stored in the database
1172
     *
1173
     * @param Match $match The first match
1174
     *
1175
     * @throws Exception
1176
     */
1177 1
    public static function recalculateMatchesSince(Match $match)
1178
    {
1179
        try {
1180
            // Commented out to prevent ridiculously large recalculations
1181
            //set_time_limit(0);
1182
1183 1
            $query = Match::getQueryBuilder()
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1184 1
                ->where('status')->notEquals('deleted')
1185 1
                ->where('type')->equals(Match::OFFICIAL)
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1186 1
                ->where('time')->isAfter($match->getTimestamp(), $inclusive = true)
1187 1
                ->sortBy('time');
1188
1189
            /** @var Match[] $matches */
1190 1
            $matches = $query->getModels($fast = true);
1191
1192
            // Send the total count to client-side javascript
1193 1
            echo count($matches) . "\n";
1194
1195
            // Start a transaction so tables are locked and we don't stay with
1196
            // messed up data if something goes wrong
1197 1
            Database::getInstance()->startTransaction();
1198
1199 1
            $teamsReset = [];
1200
1201
            // Reset match teams, in case the selected match is deleted and does
1202
            // not show up in the list of matches to recalculate
1203 1
            if ($match->getTeamA()->supportsMatchCount()) {
1204 1
                $match->getTeamA()->setElo($match->getTeamAEloOld());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method setElo() 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...
1205 1
                $teamsReset[ $match->getTeamA()->getId() ] = true;
1206
            }
1207 1
            if ($match->getTeamB()->supportsMatchCount()) {
1208 1
                $match->getTeamB()->setElo($match->getTeamBEloOld());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method setElo() 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...
1209 1
                $teamsReset[ $match->getTeamB()->getId() ] = true;
1210
            }
1211
1212 1
            foreach ($matches as $i => &$match) {
1213
                // Reset teams' ELOs if they haven't been reset already
1214 1 View Code Duplication
                if ($match->getTeamA()->supportsMatchCount() && !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...
1215
                    $teamsReset[ $match->getTeamA()->getId() ] = true;
1216
                    $match->getTeamA()->setElo($match->getTeamAEloOld());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method setElo() 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...
1217
                }
1218 1 View Code Duplication
                if ($match->getTeamB()->supportsMatchCount() && !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...
1219
                    $teamsReset[ $match->getTeamB()->getId() ] = true;
1220
                    $match->getTeamB()->setElo($match->getTeamBEloOld());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method setElo() 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...
1221
                }
1222
1223 1
                $match->recalculateElo();
1224
1225
                // Send an update to the client-side javascript, so that a
1226
                // progress bar can be updated
1227 1
                echo "m";
1228
            }
1229
        } catch (Exception $e) {
1230
            Database::getInstance()->rollback();
1231
            Database::getInstance()->finishTransaction();
1232
            throw $e;
1233
        }
1234
1235 1
        Database::getInstance()->finishTransaction();
1236
1237 1
        echo "\n\nCalculation successful\n";
1238 1
    }
1239
1240
    /**
1241
     * Get the average ELO for an array of players
1242
     *
1243
     * @param int[]|Player[] $players
1244
     *
1245
     * @return float|int
1246
     */
1247
    private static function getAveragePlayerElo($players)
1248
    {
1249 28
        $getElo = function ($n) {
1250 28
            if ($n instanceof Player) {
1251 2
                return $n->getElo();
1252
            }
1253
1254 28
            return Player::get($n)->getElo();
1255 28
        };
1256
1257 28
        return array_sum(array_map($getElo, $players)) / count($players);
1258
    }
1259
1260
    /**
1261
     * Update the match count of the teams participating in the match
1262
     *
1263
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1264
     */
1265 41
    private function updateMatchCount($decrement = false)
1266
    {
1267 41
        if ($this->match_type !== self::OFFICIAL) {
1268 3
            return;
1269
        }
1270
1271 39
        $diff = ($decrement) ? -1 : 1;
1272
1273 39
        if ($this->isDraw()) {
1274 19
            $this->getTeamA()->supportsMatchCount() && $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...
1275 19
            $this->getTeamB()->supportsMatchCount() && $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...
1276
        } else {
1277 21
            $this->getWinner()->supportsMatchCount() && $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...
1278 21
            $this->getLoser()->supportsMatchCount()  && $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...
1279
        }
1280 39
    }
1281
1282
    /**
1283
     * Update the Elos for the participating players in a match
1284
     */
1285 41
    private function updatePlayerElo()
1286
    {
1287 41
        if ($this->match_type !== self::OFFICIAL || $this->getPlayerEloDiff() === null) {
1288 3
            return;
1289
        }
1290
1291 39
        $eloDiff = $this->getPlayerEloDiff(false);
1292
1293 39
        foreach ($this->getTeamAPlayers() as $player) {
1294 28
            $player->adjustElo($eloDiff, $this);
1295 28
            $player->setLastMatch($this->getId());
1296
        }
1297
1298 39
        foreach ($this->getTeamBPlayers() as $player) {
1299 29
            $player->adjustElo(-$eloDiff, $this);
1300 29
            $player->setLastMatch($this->getId());
1301
        }
1302 39
    }
1303
}
1304