Completed
Push — feature/player-elo ( 52db17...d7963a )
by Vladimir
12:26
created

Match::getPlayerEloChangelog()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3.3332

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 2
cts 3
cp 0.6667
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 0
crap 3.3332
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
     * @var array
131
     */
132
    protected $player_elo_changelog;
133
134
    /**
135
     * The timestamp representing when the match information was last updated
136
     * @var TimeDate
137
     */
138
    protected $updated;
139
140
    /**
141
     * The duration of the match in minutes
142
     * @var int
143
     */
144
    protected $duration;
145
146
    /**
147
     * The ID of the person (i.e. referee) who last updated the match information
148
     * @var string
149
     */
150
    protected $entered_by;
151
152
    /**
153
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
154
     * @var string
155
     */
156
    protected $status;
157
158
    /**
159
     * The name of the database table used for queries
160
     */
161
    const TABLE = "matches";
162
163
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
164
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
165
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
166 42
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
167
168 42
    /**
169 42
     * {@inheritdoc}
170 42
     */
171 42
    protected function assignResult($match)
172 42
    {
173 42
        $this->team_a = $match['team_a'];
174 42
        $this->team_b = $match['team_b'];
175 42
        $this->team_a_color = $match['team_a_color'];
176 42
        $this->team_b_color = $match['team_b_color'];
177 42
        $this->team_a_points = $match['team_a_points'];
178 42
        $this->team_b_points = $match['team_b_points'];
179 42
        $this->team_a_players = $match['team_a_players'];
180 42
        $this->team_b_players = $match['team_b_players'];
181 42
        $this->team_a_elo_new = $match['team_a_elo_new'];
182 42
        $this->team_b_elo_new = $match['team_b_elo_new'];
183 42
        $this->map = $match['map'];
184 42
        $this->match_type = $match['match_type'];
185 42
        $this->match_details = $match['match_details'];
186 42
        $this->server = $match['server'];
187 42
        $this->replay_file = $match['replay_file'];
188 42
        $this->elo_diff = $match['elo_diff'];
189 42
        $this->player_elo_diff = $match['player_elo_diff'];
190 42
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
191
        $this->updated = TimeDate::fromMysql($match['updated']);
192
        $this->duration = $match['duration'];
193
        $this->entered_by = $match['entered_by'];
194
        $this->status = $match['status'];
195
    }
196
197 1
    /**
198
     * Get the name of the route that shows the object
199 1
     * @param  string $action The route's suffix
200
     * @return string
201
     */
202
    public static function getRouteName($action = 'show')
203
    {
204
        return "match_$action";
205
    }
206
207
    /**
208
     * Get a one word description of a match relative to a team (i.e. win, loss, or draw)
209 1
     *
210
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
211 1
     *
212 1
     * @return string Either "win", "loss", or "draw" relative to the team
213 1
     */
214 1
    public function getMatchDescription($teamID)
215
    {
216
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
217 1
            return "win";
218
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
219
            return "loss";
220
        }
221
222
        return "tie";
223
    }
224
225
    /**
226
     * Get a one letter description of a match relative to a team (i.e. W, L, or T)
227 1
     *
228
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
229 1
     *
230
     * @return string Either "W", "L", or "T" relative to the team
231
     */
232
    public function getMatchLetter($teamID)
233
    {
234
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
235
    }
236
237
    /**
238
     * Get the score of a specific team
239 23
     *
240
     * @param int|string|TeamInterface $teamID The team we want the score for
241 23
     *
242
     * @return int The score that team received
243 23
     */
244 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...
245
    {
246
        if ($teamID instanceof TeamInterface) {
247
            // Oh no! The caller gave us a Team model instead of an ID!
248
            $teamID = $teamID->getId();
249 23
        } elseif (is_string($teamID)) {
250 23
            // Make sure we're comparing lowercase strings
251
            $teamID = strtolower($teamID);
252
        }
253 23
254
        if ($this->getTeamA()->getId() == $teamID) {
255
            return $this->getTeamAPoints();
256
        }
257
258
        return $this->getTeamBPoints();
259
    }
260
261
    /**
262
     * Get the score of the opponent relative to a team
263 2
     *
264
     * @param int|string|TeamInterface $teamID The opponent of the team we want the score for
265 2
     *
266
     * @return int The score of the opponent
267
     */
268
    public function getOpponentScore($teamID)
269
    {
270
        return $this->getScore($this->getOpponent($teamID));
271
    }
272
273
    /**
274
     * Get the opponent of a match relative to a team ID
275 36
     *
276
     * @param int|string|TeamInterface $teamID The team who is known in a match
277 36
     *
278 36
     * @return TeamInterface The opponent team
279 2
     */
280 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...
281
    {
282
        if ($teamID instanceof TeamInterface) {
283 36
            $teamID = $teamID->getId();
284 24
        } elseif (is_string($teamID)) {
285
            $teamID = strtolower($teamID);
286
        }
287 14
288
        if ($this->getTeamA()->getId() == $teamID) {
289
            return $this->getTeamB();
290
        }
291
292
        return $this->getTeamA();
293
    }
294
295 1
    /**
296
     * Get the timestamp of the last update of the match
297 1
     *
298
     * @return TimeDate The match's update timestamp
299
     */
300
    public function getUpdated()
301
    {
302
        return $this->updated->copy();
303
    }
304
305
    /**
306
     * Set the timestamp of the match
307
     *
308
     * @param  mixed $timestamp The match's new timestamp
309
     * @return $this
310
     */
311
    public function setTimestamp($timestamp)
312
    {
313
        $this->updateProperty($this->timestamp, "timestamp", TimeDate::from($timestamp));
314
315
        return $this;
316
    }
317 41
318
    /**
319 41
     * Get the first team involved in the match
320
     * @return TeamInterface Team A
321 41
     */
322 29
    public function getTeamA()
323
    {
324
        $team = Team::get($this->team_a);
325 13
326
        if ($this->match_type === self::OFFICIAL && $team->isValid()) {
327
            return $team;
328
        }
329
330
        return new ColorTeam($this->team_a_color);
331
    }
332 41
333
    /**
334 41
     * Get the second team involved in the match
335
     * @return TeamInterface Team B
336 41
     */
337 27
    public function getTeamB()
338
    {
339
        $team = Team::get($this->team_b);
340 15
341
        if ($this->match_type === self::OFFICIAL && $team->isValid()) {
342
            return $team;
343
        }
344
345
        return new ColorTeam($this->team_b_color);
346
    }
347
348
    /**
349
     * Get the color of Team A
350
     * @return string
351
     */
352
    public function getTeamAColor()
353
    {
354
        return $this->team_a_color;
355
    }
356
357
    /**
358
     * Get the color of Team B
359
     * @return string
360
     */
361
    public function getTeamBColor()
362
    {
363
        return $this->team_b_color;
364
    }
365 39
366
    /**
367 39
     * Get the list of players on Team A who participated in this match
368
     * @return Player[] Returns null if there were no players recorded for this match
369
     */
370
    public function getTeamAPlayers()
371
    {
372
        return $this->parsePlayers($this->team_a_players);
373
    }
374 39
375
    /**
376 39
     * Get the list of players on Team B who participated in this match
377
     * @return Player[] Returns null if there were no players recorded for this match
378
     */
379
    public function getTeamBPlayers()
380
    {
381
        return $this->parsePlayers($this->team_b_players);
382
    }
383
384 4
    /**
385
     * Get the list of players for a team in a match
386 4
     * @param  Team|int|null The team or team ID
387 1
     * @return Player[]|null Returns null if there were no players recorded for this match
388
     */
389
    public function getPlayers($team = null)
390 4
    {
391 1
        if ($team instanceof TeamInterface) {
392 4
            $team = $team->getId();
393 1
        }
394
395
        if ($this->getTeamA()->isValid() && $team === $this->getTeamA()->getId()) {
396 3
            return $this->getTeamAPlayers();
397
        } elseif ($this->getTeamB()->isValid() && $team === $this->getTeamB()->getId()) {
398
            return $this->getTeamBPlayers();
399
        }
400
401
        return $this->parsePlayers($this->team_a_players . "," . $this->team_b_players);
402
    }
403
404
    /**
405
     * Set the players of the match's teams
406
     *
407
     * @param int[] $teamAPlayers An array of player IDs
408
     * @param int[] $teamBPlayers An array of player IDs
409
     * @return self
410
     */
411
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
412
    {
413
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers));
414
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers));
415
416
        return $this;
417
    }
418
419 39
    /**
420
     * Get an array of players based on a string representation
421 39
     * @param string $playerString
422 12
     * @return Player[] Returns null if there were no players recorded for this match
423
     */
424
    private function parsePlayers($playerString)
425 30
    {
426
        if ($playerString == null) {
427
            return [];
428
        }
429
430
        return Player::arrayIdToModel(explode(",", $playerString));
431
    }
432 28
433
    /**
434 28
     * Get the first team's points
435
     * @return int Team A's points
436
     */
437
    public function getTeamAPoints()
438
    {
439
        return $this->team_a_points;
440
    }
441 28
442
    /**
443 28
     * Get the second team's points
444
     * @return int Team B's points
445
     */
446
    public function getTeamBPoints()
447
    {
448
        return $this->team_b_points;
449
    }
450
451
    /**
452
     * Set the match team points
453 1
     *
454
     * @param  int $teamAPoints Team A's points
455 1
     * @param  int $teamBPoints Team B's points
456 1
     * @return self
457
     */
458 1
    public function setTeamPoints($teamAPoints, $teamBPoints)
459
    {
460
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints);
461
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints);
462
463
        return $this;
464
    }
465
466
    /**
467
     * Set the match team colors
468
     *
469
     * @param  ColorTeam|string $teamAColor The color of team A
470
     * @param  ColorTeam|string $teamBColor The color of team B
471
     * @return self
472
     */
473
    public function setTeamColors($teamAColor, $teamBColor)
474
    {
475
        if ($this->isOfficial()) {
476
            throw new \Exception("Cannot change team colors in an official match");
477
        }
478
479
        if ($teamAColor instanceof ColorTeam) {
480
            $teamAColor = $teamAColor->getId();
481
        }
482
        if ($teamBColor instanceof ColorTeam) {
483
            $teamBColor = $teamBColor->getId();
484
        }
485
486
        $this->updateProperty($this->team_a_color, "team_a_color", $teamAColor);
487
        $this->updateProperty($this->team_b_color, "team_b_color", $teamBColor);
488
    }
489
490
    /**
491
     * Get the ELO difference applied to each team's old ELO
492 30
     *
493
     * @param bool $absoluteValue Whether or not to get the absolute value of the Elo difference
494 30
     *
495
     * @return int The ELO difference
496
     */
497
    public function getEloDiff($absoluteValue = true)
498
    {
499
        return ($absoluteValue) ? abs($this->elo_diff) : $this->elo_diff;
500
    }
501
502
    /**
503
     * Get the Elo difference applied to players
504 39
     *
505
     * @param bool $absoluteValue Whether or not to get the absolute value of the Elo difference
506 39
     *
507
     * @return int The Elo difference for players
508
     */
509
    public function getPlayerEloDiff($absoluteValue = true)
510
    {
511
        return ($absoluteValue) ? abs($this->player_elo_diff) : $this->player_elo_diff;
512
    }
513
514
    /**
515
     * Get the changelog for the player Elos for this match and cache them
516
     */
517
    private function getPlayerEloChangelog()
518
    {
519
        if ($this->player_elo_changelog !== null) {
520
            return;
521
        }
522
523 7
        $results = $this->db->query('SELECT * FROM player_elo WHERE match_id = ?', $this->getId());
524
525 7
        foreach ($results as $result) {
526
            $this->player_elo_changelog[$result['user_id']] = [
527
                'before' => $result['elo_previous'],
528
                'after'  => $result['elo_new']
529
            ];
530
        }
531
    }
532 7
533
    /**
534 7
     * Get the Elo for the player before this match occurred
535
     *
536
     * @param Player $player
537
     *
538
     * @return null|int
539
     */
540 View Code Duplication
    public function getPlayerEloBefore(Player $player)
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...
541 7
    {
542
        $this->getPlayerEloChangelog();
543 7
544
        if (isset($this->player_elo_changelog[$player->getId()])) {
545
            return $this->player_elo_changelog[$player->getId()]['before'];
546
        }
547
548
        return null;
549
    }
550 7
551
    /**
552 7
     * Get the Elo for the player after this match occurred
553
     *
554
     * @param Player $player
555
     *
556
     * @return null|int
557
     */
558 View Code Duplication
    public function getPlayerEloAfter(Player $player)
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...
559
    {
560
        $this->getPlayerEloChangelog();
561 1
562
        if (isset($this->player_elo_changelog[$player->getId()])) {
563 1
            return $this->player_elo_changelog[$player->getId()]['after'];
564 1
        }
565 1
566 1
        return null;
567
    }
568
569
    /**
570
     * Set the Elo difference applied to players
571
     *
572
     * @param int $diff
573
     */
574
    public function setPlayerEloDiff($diff)
575
    {
576
        $this->updateProperty($this->player_elo_diff, 'player_elo_diff', $diff);
577
    }
578 1
579
    /**
580 1
     * Get the first team's new ELO
581 1
     * @return int Team A's new ELO
582 1
     */
583 1
    public function getTeamAEloNew()
584
    {
585
        return $this->team_a_elo_new;
586
    }
587
588
    /**
589
     * Get the second team's new ELO
590
     * @return int Team B's new ELO
591
     */
592
    public function getTeamBEloNew()
593 1
    {
594
        return $this->team_b_elo_new;
595 1
    }
596
597
    /**
598
     * Get the first team's old ELO
599
     * @return int
600
     */
601
    public function getTeamAEloOld()
602
    {
603
        return $this->team_a_elo_new - $this->elo_diff;
604
    }
605
606
    /**
607
     * Get the second team's old ELO
608
     * @return int
609
     */
610
    public function getTeamBEloOld()
611
    {
612
        return $this->team_b_elo_new + $this->elo_diff;
613
    }
614
615
    /**
616
     * Get the team's new ELO
617
     * @param  Team $team The team whose new ELO to return
618
     * @return int|null   The new ELO, or null if the team provided has not
619
     *                    participated in the match
620
     */
621
    public function getTeamEloNew(Team $team)
622 23
    {
623
        if ($team->getId() == $this->team_a) {
624 23
            return $this->getTeamAEloNew();
625 9
        } elseif ($team->getId() == $this->team_b) {
626 14
            return $this->getTeamBEloNew();
627 9
        }
628
629
        return null;
630 5
    }
631
632
    /**
633
     * Get the team's old ELO
634
     * @param  Team $team The team whose old ELO to return
635
     * @return int|null   The old ELO, or null if the team provided has not
636
     *                    participated in the match
637
     */
638 23
    public function getTeamEloOld(Team $team)
639
    {
640 23
        if ($team->getId() == $this->team_a) {
641
            return $this->getTeamAEloOld();
642
        } elseif ($team->getId() == $this->team_b) {
643
            return $this->getTeamBEloOld();
644
        }
645
646
        return null;
647
    }
648
649
    /**
650
     * Get the map where the match was played on
651
     * @return Map Returns an invalid map if no map was found
652
     */
653
    public function getMap()
654
    {
655
        return Map::get($this->map);
656
    }
657
658
    /**
659
     * Set the map where the match was played
660
     * @param  int $map The ID of the map
661
     * @return self
662
     */
663
    public function setMap($map)
664
    {
665
        $this->updateProperty($this->map, "map", $map, "s");
666
    }
667
668 1
    /**
669
     * Get the type of official match this is. Whether it has just traditional teams or has mixed teams.
670 1
     *
671
     * Possible official match types:
672
     *   - Team vs Team
673
     *   - Team vs Mixed
674
     *   - Mixed vs Mixed
675
     *
676
     * @see Match::TEAM_V_TEAM
677
     * @see Match::TEAM_V_MIXED
678
     * @see Match::MIXED_V_MIXED
679
     *
680
     * @return int
681
     */
682
    public function getTeamMatchType()
683
    {
684
        if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) {
685
            return self::TEAM_V_TEAM;
686
        } elseif ($this->getTeamA()->supportsMatchCount() xor $this->getTeamB()->supportsMatchCount()) {
687
            return self::TEAM_V_MIXED;
688
        }
689
690
        return self::MIXED_V_MIXED;
691
    }
692 1
693
    /**
694 1
     * Get the match type
695
     *
696
     * @return string 'official', 'fm', or 'special'
697
     */
698 1
    public function getMatchType()
699
    {
700
        return $this->match_type;
701
    }
702
703
    /**
704
     * Set the match type
705 6
     *
706
     * @param  string $matchType A valid match type; official, fm, special
707 6
     *
708
     * @return static
709
     */
710
    public function setMatchType($matchType)
711
    {
712
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
713
    }
714
715
    /**
716
     * Get a JSON decoded array of events that occurred during the match
717
     * @return mixed|null Returns null if there were no events recorded for the match
718
     */
719
    public function getMatchDetails()
720
    {
721
        return json_decode($this->match_details);
722
    }
723
724
    /**
725 2
     * Get the server address of the server where this match took place
726
     * @return string|null Returns null if there was no server address recorded
727 2
     */
728
    public function getServerAddress()
729
    {
730
        return $this->server;
731
    }
732
733
    /**
734
     * Set the server address of the server where this match took place
735 36
     *
736
     * @param  string|null $server The server hostname
737
     * @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...
738 36
     * @return self
739
     */
740
    public function setServerAddress($server = null)
741 36
    {
742
        $this->updateProperty($this->server, "server", $server);
743
744
        return $this;
745
    }
746
747
    /**
748
     * Get the name of the replay file for this specific map
749 36
     * @param  int    $length The length of the replay file name; it will be truncated
750
     * @return string Returns null if there was no replay file name recorded
751
     */
752
    public function getReplayFileName($length = 0)
753
    {
754
        if ($length > 0) {
755 36
            return substr($this->replay_file, 0, $length);
756 22
        }
757 15
758 12
        return $this->replay_file;
759 3
    }
760
761 2
    /**
762 1
     * Get the match duration
763
     * @return int The duration of the match in minutes
764
     */
765
    public function getDuration()
766
    {
767 1
        return $this->duration;
768
    }
769
770
    /**
771
     * Set the match duration
772
     *
773
     * @param  int  $duration The new duration of the match in minutes
774 39
     * @return self
775
     */
776 39
    public function setDuration($duration)
777
    {
778
        return $this->updateProperty($this->duration, "duration", $duration);
779
    }
780
781
    /**
782
     * Get the user who entered the match
783
     * @return Player
784
     */
785 1
    public function getEnteredBy()
786
    {
787 1
        return Player::get($this->entered_by);
788
    }
789
790
    /**
791
     * Get the loser of the match
792
     *
793 22
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
794
     */
795 22
    public function getLoser()
796
    {
797
        // Get the winner of the match
798
        $winner = $this->getWinner();
799
800
        // Get the team that wasn't the winner... Duh
801
        return $this->getOpponent($winner);
802
    }
803
804
    /**
805
     * Get the winner of a match
806
     *
807
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
808
     */
809
    public function getWinner()
810
    {
811
        // "As Mother Teresa once said, it's not enough if you win. Others must lose."
812
        //   -Stephen Colbert
813
814
        // Get the team that had its Elo increased or the team whose players had their Elo increased
815
        if ($this->elo_diff > 0 || $this->player_elo_diff > 0) {
816
            return $this->getTeamA();
817
        } elseif ($this->elo_diff < 0 || $this->player_elo_diff < 0) {
818 1
            return $this->getTeamB();
819
        } elseif ($this->team_a_points > $this->team_b_points) {
820 1
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
821 1
            return $this->getTeamA();
822
        } elseif ($this->team_a_points < $this->team_b_points) {
823 1
            return $this->getTeamB();
824
        }
825 1
826
        // If the scores are the same, return Team A because well, fuck you that's why
827
        return $this->getTeamA();
828
    }
829 1
830
    /**
831
     * Determine whether the match was a draw
832
     * @return bool True if the match ended without any winning teams
833
     */
834
    public function isDraw()
835
    {
836
        return $this->team_a_points == $this->team_b_points;
837
    }
838
839
    /**
840
     * Find out whether the match involves a team
841
     *
842
     * @param  TeamInterface $team The team to check
843
     * @return bool
844
     */
845
    public function involvesTeam($team)
846
    {
847 42
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
848
    }
849
850 42
    /**
851
     * Find out if the match is played between official teams
852 42
     */
853 23
    public function isOfficial()
854 20
    {
855 14
        return self::OFFICIAL === $this->getMatchType();
856
    }
857
858 42
    /**
859 42
     * Reset the ELOs of the teams participating in the match
860 3
     *
861
     * @return self
862
     */
863
    public function resetELOs()
864
    {
865
        if ($this->match_type === self::OFFICIAL) {
866
            $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...
867
            $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...
868 39
        }
869
870 39
        return $this;
871 39
    }
872
873
    /**
874 39
     * Calculate the match's contribution to the team activity
875 28
     *
876 28
     * @return float
877
     */
878 28
    public function getActivity()
879
    {
880
        $daysPassed = $this->getTimestamp()->diffInSeconds();
881
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
882
883
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
884
885
        if (is_nan($activity) || $activity < 0.0) {
886 39
            return 0.0;
887
        }
888
889
        return $activity;
890
    }
891 39
892 23
    /**
893 17
     * Calculate the Elo differences for players and teams for a given match.
894 11
     *
895 11
     * @param  Team $a
896
     * @param  Team $b
897 11
     * @param  int  $a_points
898
     * @param  int  $b_points
899
     * @param  int[]|Player[] $a_players
900
     * @param  int[]|Player[] $b_players
901 39
     * @param  int  $duration
902 39
     *
903 39
     * @throws InvalidArgumentException When a "Mixed" team is entered without a player roster
904
     *
905
     * @return array
906
     */
907
    private static function calculateElos($a, $b, $a_points, $b_points, $a_players, $b_players, $duration)
908
    {
909
        // Get the type of official match
910
        $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...
911
912
        if ($a->supportsMatchCount() && $b->supportsMatchCount()) {
913
            $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...
914
        } elseif ($a->supportsMatchCount() xor $b->supportsMatchCount()) {
915
            $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...
916
        }
917
918
        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...
919
            ((!$a->isValid() && empty($a_players)) || (!$b->isValid() && empty($b_players)))) {
920
            throw new InvalidArgumentException('A Mixed team must have a player roster to calculate the Elo for team Elo differences');
921
        }
922
923
        //
924
        // Handle Player Elo Diff Calculations
925
        //
926
927
        // By default, we won't have a player Elo difference since we won't force matches to have a roster
928
        $playerEloDiff = null;
929
930 45
        $a_players_elo = 1200;
931
        $b_players_elo = 1200;
932
933
        // Only bother to calculate a player Elo diff if we have players reported for both teams
934
        if (!empty($a_players) && !empty($b_players)) {
935
            $a_players_elo = self::getAveragePlayerElo($a_players);
936 45
            $b_players_elo = self::getAveragePlayerElo($b_players);
937 45
938 45
            $playerEloDiff = self::calculateEloDiff($a_players_elo, $b_players_elo, $a_points, $b_points, $duration);
939 45
        }
940 45
941 45
        //
942 45
        // Handle Team Elo Diff Calculations
943 45
        //
944 45
945 45
        // By default, we'll assume a Mixed vs Mixed official match where Elos do not change for teams
946 45
        $teamEloDiff = null;
947 45
948 45
        // Work with calculations for team Elos to handle the following situations:
949 45
        //   - Team vs Team  :: Use team Elos for calculations
950
        //   - Team vs Mixed :: Use team Elo and the player average Elo for the "Mixed" team
951
        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...
952
            $teamEloDiff = self::calculateEloDiff($a->getElo(), $b->getElo(), $a_points, $b_points, $duration);
953 45
        } 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...
954
            $a_team_elo = ($a->supportsMatchCount()) ? $a->getElo() : $a_players_elo;
955 45
            $b_team_elo = ($b->supportsMatchCount()) ? $b->getElo() : $b_players_elo;
956 42
957 42
            $teamEloDiff = self::calculateEloDiff($a_team_elo, $b_team_elo, $a_points, $b_points, $duration);
958
        }
959 42
960
        return [
961 39
            'match_type' => $matchType,
962 39
            'team_elo'   => $teamEloDiff,
963
            'player_elo' => $playerEloDiff
964
        ];
965 39
    }
966 29
967
    /**
968 29
     * Enter a new match to the database
969 29
     * @param  int             $a          Team A's ID
970
     * @param  int             $b          Team B's ID
971 39
     * @param  int             $a_points   Team A's match points
972 27
     * @param  int             $b_points   Team B's match points
973
     * @param  int             $duration   The match duration in minutes
974 27
     * @param  int|null        $entered_by The ID of the player reporting the match
975 27
     * @param  string|DateTime $timestamp  When the match was played
976
     * @param  int[]           $a_players  The IDs of the first team's players
977
     * @param  int[]           $b_players  The IDs of the second team's players
978
     * @param  string|null     $server     The address of the server where the match was played
979 42
     * @param  string          $replayFile The name of the replay file of the match
980 42
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
981 42
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
982
     * @param  string          $a_color    Team A's color
983 42
     * @param  string          $b_color    Team b's color
984
     *
985
     * @throws InvalidArgumentException When a ColorTeam is selected for an official match and no players are defined
986
     *                                  for that team
987
     *
988
     * @return Match           An object representing the match that was just entered
989
     */
990
    public static function enterMatch(
991
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
992
        $a_players = array(), $b_players = array(), $server = null, $replayFile = null,
993
        $map = null, $matchType = "official", $a_color = null, $b_color = null
994
    ) {
995
        $matchData = array(
996
            'team_a_color'   => strtolower($a_color),
997
            'team_b_color'   => strtolower($b_color),
998
            'team_a_points'  => $a_points,
999 39
            'team_b_points'  => $b_points,
1000
            'team_a_players' => implode(',', $a_players),
1001 39
            'team_b_players' => implode(',', $b_players),
1002 39
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
1003 14
            'duration'       => $duration,
1004 27
            'entered_by'     => $entered_by,
1005 19
            'server'         => $server,
1006
            'replay_file'    => $replayFile,
1007 8
            'map'            => $map,
1008
            'status'         => 'entered',
1009
            'match_type'     => $matchType
1010
        );
1011 39
1012 39
        // (P)layer Elo Diff and (T)eam Elo Diff; respectively
1013
        $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...
1014 39
1015
        if ($matchType === self::OFFICIAL) {
1016 2
            $team_a = Team::get($a);
1017
            $team_b = Team::get($b);
1018
1019
            $eloCalcs = self::calculateElos($team_a, $team_b, $a_points, $b_points, $a_players, $b_players, $duration);
1020 37
1021
            $matchData['elo_diff'] = $tEloDiff = $eloCalcs['team_elo'];
1022
            $matchData['player_elo_diff'] = $eloCalcs['player_elo'];
1023
1024
            // Update team ELOs
1025
            if ($team_a->isValid()) {
1026
                $team_a->adjustElo($tEloDiff);
1027
1028
                $matchData['team_a'] = $a;
1029
                $matchData['team_a_elo_new'] = $team_a->getElo();
1030
            }
1031
            if ($team_b->isValid()) {
1032
                $team_b->adjustElo(-$tEloDiff);
1033
1034
                $matchData['team_b'] = $b;
1035
                $matchData['team_b_elo_new'] = $team_b->getElo();
1036
            }
1037
        }
1038
1039
        $match = self::create($matchData, 'updated');
1040 3
        $match->updateMatchCount();
1041
        $match->updatePlayerElo();
1042 3
1043
        return $match;
1044
    }
1045
1046 3
    /**
1047 3
     * Calculate the ELO score difference
1048
     *
1049 3
     * Computes the ELO score difference on each team after a match, based on
1050
     * GU League's rules.
1051 3
     *
1052 3
     * @param  int $a_elo    Team A's current ELO score
1053
     * @param  int $b_elo    Team B's current ELO score
1054
     * @param  int $a_points Team A's match points
1055 3
     * @param  int $b_points Team B's match points
1056 3
     * @param  int $duration The match duration in minutes
1057 3
     * @return int The ELO score difference
1058 3
     */
1059 3
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
1060
    {
1061
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
1062 3
        if ($a_points > $b_points) {
1063
            $diff = 50 * (1 - $prob);
1064 3
        } elseif ($a_points == $b_points) {
1065 3
            $diff = 50 * (0.5 - $prob);
1066
        } else {
1067 3
            $diff = 50 * (0 - $prob);
1068 3
        }
1069 3
1070
        // Apply ELO modifiers from `config.yml`
1071
        $durations = Service::getParameter('bzion.league.duration');
1072 3
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
1073 3
1074 3
        if (abs($diff) < 1 && $diff != 0) {
1075
            // ELOs such as 0.75 should round up to 1...
1076
            return ($diff > 0) ? 1 : -1;
1077 3
        }
1078 3
1079
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
1080
        return intval($diff);
1081
    }
1082
1083 1
    /**
1084
     * Find if a match's stored ELO is correct
1085 1
     */
1086
    public function isEloCorrect()
1087
    {
1088
        return $this->elo_diff === $this->calculateEloDiff(
1089
            $this->getTeamAEloOld(),
1090
            $this->getTeamBEloOld(),
1091
            $this->getTeamAPoints(),
1092 26
            $this->getTeamBPoints(),
1093
            $this->getDuration()
1094 26
        );
1095 26
    }
1096
1097
    /**
1098
     * Recalculate the match's elo and adjust the team ELO values
1099
     */
1100
    public function recalculateElo()
1101
    {
1102
        if ($this->match_type !== self::OFFICIAL) {
1103
            return;
1104
        }
1105
1106
        $a = $this->getTeamA();
1107
        $b = $this->getTeamB();
1108
1109
        $this->db->execute('DELETE FROM player_elo WHERE match_id = ?', [$this->getId()]);
1110
1111 1
        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...
1112
            $player->invalidateMatchFromCache($this);
1113 1
        }
1114
1115 1
        $eloCalcs = self::calculateElos(
1116 1
            $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...
1117
            $this->getTeamAPoints(), $this->getTeamBPoints(),
1118
            $this->getTeamAPlayers(), $this->getTeamBPlayers(),
1119
            $this->getDuration()
1120
        );
1121 3
1122
        $elo = $eloCalcs['team_elo'];
1123 3
1124
        $this->updateProperty($this->elo_diff, 'elo_diff', $elo);
1125
        $this->updateProperty($this->player_elo_diff, 'player_elo_diff', $eloCalcs['player_elo']);
1126
1127
        if ($a->supportsMatchCount()) {
1128
            $a->adjustElo($elo);
1129 22
            $this->updateProperty($this->team_a_elo_new, 'team_a_elo_new', $a->getElo());
1130
        }
1131 22
1132
        if ($b->supportsMatchCount()) {
1133 22
            $b->adjustElo(-$elo);
1134 22
            $this->updateProperty($this->team_b_elo_new, 'team_b_elo_new', $b->getElo());
1135
        }
1136 21
1137 9
        $this->updatePlayerElo();
1138
    }
1139 21
1140
    /**
1141 2
     * Get all the matches in the database
1142 2
     */
1143 2
    public static function getMatches()
1144
    {
1145
        return self::getQueryBuilder()->active()->getModels();
1146
    }
1147
1148
    /**
1149
     * Get a query builder for matches
1150
     * @return MatchQueryBuilder
1151
     */
1152
    public static function getQueryBuilder()
1153 22
    {
1154 22
        return new MatchQueryBuilder('Match', array(
1155 22
            'columns' => array(
1156 22
                'firstTeam'        => 'team_a',
1157 22
                'secondTeam'       => 'team_b',
1158 22
                'firstTeamPoints'  => 'team_a_points',
1159
                'secondTeamPoints' => 'team_b_points',
1160
                'time'             => 'timestamp',
1161
                'map'              => 'map',
1162
                'type'             => 'match_type',
1163
                'status'           => 'status'
1164
            ),
1165
        ));
1166
    }
1167
1168
    /**
1169
     * {@inheritdoc}
1170
     */
1171
    public function delete()
1172
    {
1173
        $this->updateMatchCount(true);
1174
1175
        parent::delete();
1176
    }
1177 1
1178
    /**
1179
     * {@inheritdoc}
1180
     */
1181
    public static function getActiveStatuses()
1182
    {
1183 1
        return array('entered');
1184 1
    }
1185 1
1186 1
    /**
1187 1
     * {@inheritdoc}
1188
     */
1189
    public function getName()
1190 1
    {
1191
        $description = '';
1192
1193 1
        switch ($this->getMatchType()) {
1194
            case self::OFFICIAL:
1195
                // Only show Elo diff if both teams are actual teams
1196
                if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) {
1197 1
                    $description = "(+/- {$this->getEloDiff()})";
1198
                }
1199 1
                break;
1200
1201
            case self::FUN:
1202
                $description = 'Fun Match:';
1203 1
                break;
1204 1
1205 1
            case self::SPECIAL:
1206
                $description = 'Special Match:';
1207 1
                break;
1208 1
1209 1
            default:
1210
                break;
1211
        }
1212 1
1213
        return trim(sprintf('%s %s [%d] vs [%d] %s',
1214 1
            $description,
1215
            $this->getWinner()->getName(),
1216
            $this->getScore($this->getWinner()),
1217
            $this->getScore($this->getLoser()),
1218 1
            $this->getLoser()->getName()
1219
        ));
1220
    }
1221
1222
    /**
1223 1
     * Recalculates match history for all teams and matches
1224
     *
1225
     * Recalculation is done as follows:
1226
     * 1. A match is chosen as a starting point - it's stored old team ELOs are
1227 1
     *    considered correct
1228
     * 2. Team ELOs are reset to their values at the starting point
1229
     * 3. Each match that occurred since the first specified match has its ELO
1230
     *    recalculated based on the current team values, and the new match data
1231
     *    and team ELOs are stored in the database
1232
     *
1233
     * @param Match $match The first match
1234
     *
1235 1
     * @throws Exception
1236
     */
1237 1
    public static function recalculateMatchesSince(Match $match)
1238 1
    {
1239
        try {
1240
            // Commented out to prevent ridiculously large recalculations
1241
            //set_time_limit(0);
1242
1243
            $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...
1244
                ->where('status')->notEquals('deleted')
1245
                ->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...
1246
                ->where('time')->isAfter($match->getTimestamp(), $inclusive = true)
1247
                ->sortBy('time');
1248
1249 28
            /** @var Match[] $matches */
1250 28
            $matches = $query->getModels($fast = true);
1251 2
1252
            // Send the total count to client-side javascript
1253
            echo count($matches) . "\n";
1254 28
1255 28
            // Start a transaction so tables are locked and we don't stay with
1256
            // messed up data if something goes wrong
1257 28
            Database::getInstance()->startTransaction();
1258
1259
            $teamsReset = [];
1260
1261
            // Reset match teams, in case the selected match is deleted and does
1262
            // not show up in the list of matches to recalculate
1263
            if ($match->getTeamA()->supportsMatchCount()) {
1264
                $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...
1265 42
                $teamsReset[ $match->getTeamA()->getId() ] = true;
1266
            }
1267 42
            if ($match->getTeamB()->supportsMatchCount()) {
1268 4
                $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...
1269
                $teamsReset[ $match->getTeamB()->getId() ] = true;
1270
            }
1271 39
1272
            foreach ($matches as $i => &$match) {
1273 39
                // Reset teams' ELOs if they haven't been reset already
1274 19 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...
1275 19
                    $teamsReset[ $match->getTeamA()->getId() ] = true;
1276
                    $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...
1277 21
                }
1278 21 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...
1279
                    $teamsReset[ $match->getTeamB()->getId() ] = true;
1280 39
                    $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...
1281
                }
1282
1283
                $match->recalculateElo();
1284
1285 42
                // Send an update to the client-side javascript, so that a
1286
                // progress bar can be updated
1287 42
                echo "m";
1288 4
            }
1289
        } catch (Exception $e) {
1290
            Database::getInstance()->rollback();
1291 39
            Database::getInstance()->finishTransaction();
1292
            throw $e;
1293 39
        }
1294 28
1295 28
        Database::getInstance()->finishTransaction();
1296
1297
        echo "\n\nCalculation successful\n";
1298 39
    }
1299 29
1300 29
    /**
1301
     * Get the average ELO for an array of players
1302 39
     *
1303
     * @param int[]|Player[] $players
1304
     *
1305
     * @return float|int
1306
     */
1307
    private static function getAveragePlayerElo($players)
1308
    {
1309
        $getElo = function ($n) {
1310
            if ($n instanceof Player) {
1311
                return $n->getElo();
1312
            }
1313
1314
            return Player::get($n)->getElo();
1315
        };
1316
1317
        return array_sum(array_map($getElo, $players)) / count($players);
1318
    }
1319
1320
    /**
1321
     * Update the match count of the teams participating in the match
1322
     *
1323
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1324
     */
1325
    private function updateMatchCount($decrement = false)
1326
    {
1327
        if ($this->match_type !== self::OFFICIAL) {
1328
            return;
1329
        }
1330
1331
        $diff = ($decrement) ? -1 : 1;
1332
1333
        if ($this->isDraw()) {
1334
            $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...
1335
            $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...
1336
        } else {
1337
            $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...
1338
            $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...
1339
        }
1340
    }
1341
1342
    /**
1343
     * Update the Elos for the participating players in a match
1344
     */
1345
    private function updatePlayerElo()
1346
    {
1347
        if ($this->match_type !== self::OFFICIAL || $this->getPlayerEloDiff() === null) {
1348
            return;
1349
        }
1350
1351
        $eloDiff = $this->getPlayerEloDiff(false);
1352
1353
        foreach ($this->getTeamAPlayers() as $player) {
1354
            $player->adjustElo($eloDiff, $this);
1355
            $player->setLastMatch($this->getId());
1356
        }
1357
1358
        foreach ($this->getTeamBPlayers() as $player) {
1359
            $player->adjustElo(-$eloDiff, $this);
1360
            $player->setLastMatch($this->getId());
1361
        }
1362
    }
1363
}
1364