Completed
Push — master ( 7c2796...90a4f2 )
by Vladimir
02:44
created

Match::getActiveStatuses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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