Completed
Push — feature/servers-redesign ( 2ca029...06aadf )
by Vladimir
03:58
created

Match::setServer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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