Completed
Pull Request — master (#51)
by Konstantinos
08:16 queued 04:21
created

Match::recalculateElo()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

Let’s take a look at an example:

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

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

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

Let’s take a look at an example:

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

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
753 7
        }
754
755
        return $this;
756
    }
757
758
    /**
759
     * Calculate the match's contribution to the team activity
760
     *
761
     * @return float
762
     */
763
    public function getActivity()
764
    {
765
        $daysPassed = $this->getTimestamp()->diffInSeconds();
766
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
767
768
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
769
770
        if (is_nan($activity) || $activity < 0.0) {
771
            return 0.0;
772
        }
773
774
        return $activity;
775
    }
776
777
    /**
778
     * Enter a new match to the database
779
     * @param  int             $a          Team A's ID
780
     * @param  int             $b          Team B's ID
781
     * @param  int             $a_points   Team A's match points
782
     * @param  int             $b_points   Team B's match points
783
     * @param  int             $duration   The match duration in minutes
784
     * @param  int|null        $entered_by The ID of the player reporting the match
785
     * @param  string|DateTime $timestamp  When the match was played
786
     * @param  int[]           $a_players  The IDs of the first team's players
787
     * @param  int[]           $b_players  The IDs of the second team's players
788
     * @param  string|null     $server     The address of the server where the match was played
789
     * @param  int|null        $port       The port of the server where the match was played
790
     * @param  string          $replayFile The name of the replay file of the match
791
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
792
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
793
     * @param  string          $a_color    Team A's color
794
     * @param  string          $b_color    Team b's color
795
     * @return Match           An object representing the match that was just entered
796
     */
797
    public static function enterMatch(
798 1
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
799
        $a_players = array(), $b_players = array(), $server = null, $port = null,
800 1
        $replayFile = null, $map = null, $matchType = "official", $a_color = null,
801
        $b_color = null
802
    ) {
803
        $matchData = array(
804
            'team_a_color'   => strtolower($a_color),
805
            'team_b_color'   => strtolower($b_color),
806
            'team_a_points'  => $a_points,
807 3
            'team_b_points'  => $b_points,
808
            'team_a_players' => implode(',', $a_players),
809 3
            'team_b_players' => implode(',', $b_players),
810
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
811
            'duration'       => $duration,
812
            'entered_by'     => $entered_by,
813
            'server'         => $server,
814
            'port'           => $port,
815
            'replay_file'    => $replayFile,
816
            'map'            => $map,
817 3
            'status'         => 'entered',
818
            'match_type'     => $matchType
819
        );
820
821
        if ($matchType === 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...
822
            $team_a = Team::get($a);
823
            $team_b = Team::get($b);
824
            $a_elo = $team_a->getElo();
825
            $b_elo = $team_b->getElo();
826
827
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
828
829
            // Update team ELOs
830
            $team_a->changeElo($diff);
831
            $team_b->changeElo(-$diff);
832
833
            $matchData = array_merge($matchData, array(
834 3
                'team_a'         => $a,
835
                'team_b'         => $b,
836 3
                'team_a_elo_new' => $team_a->getElo(),
837
                'team_b_elo_new' => $team_b->getElo(),
838
                'elo_diff'       => $diff
839
            ));
840
        }
841
842 1
        $match = self::create($matchData, 'updated');
843
844 1
        if ($matchType === 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...
845 1
            $match->updateMatchCount();
846 1
        }
847 1
848 1
        return $match;
849 1
    }
850
851
    /**
852
     * Calculate the ELO score difference
853
     *
854
     * Computes the ELO score difference on each team after a match, based on
855
     * GU League's rules.
856
     *
857
     * @param  int $a_elo    Team A's current ELO score
858 9
     * @param  int $b_elo    Team B's current ELO score
859
     * @param  int $a_points Team A's match points
860 9
     * @param  int $b_points Team B's match points
861
     * @param  int $duration The match duration in minutes
862 9
     * @return int The ELO score difference
863 4
     */
864 4
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
865
    {
866 6
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
867 6
        if ($a_points > $b_points) {
868
            $diff = 50 * (1 - $prob);
869 9
        } elseif ($a_points == $b_points) {
870
            $diff = 50 * (0.5 - $prob);
871
        } else {
872
            $diff = 50 * (0 - $prob);
873
        }
874
875
        // Apply ELO modifiers from `config.yml`
876
        $durations = Service::getParameter('bzion.league.duration');
877
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
878
879
        if (abs($diff) < 1 && $diff != 0) {
880
            // ELOs such as 0.75 should round up to 1...
881
            return ($diff > 0) ? 1 : -1;
882
        }
883
884
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
885
        return intval($diff);
886
    }
887
888
    /**
889
     * Find if a match's stored ELO is correct
890
     */
891
    public function isEloCorrect()
892
    {
893
        return $this->elo_diff === $this->calculateEloDiff(
894
            $this->getTeamAEloOld(),
895
            $this->getTeamBEloOld(),
896
            $this->getTeamAPoints(),
897
            $this->getTeamBPoints(),
898
            $this->getDuration()
899
        );
900
    }
901
902
    /**
903
     * Recalculate the match's elo and adjust the team ELO values
904
     */
905
    public function recalculateElo()
906
    {
907
        if ($this->match_type !== 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...
908
            return;
909
        }
910
911
        $a = $this->getTeamA();
912
        $b = $this->getTeamB();
913
914
        $elo = $this->calculateEloDiff(
915
            $a->getElo(),
916
            $b->getElo(),
917
            $this->getTeamAPoints(),
918
            $this->getTeamBPoints(),
919
            $this->getDuration()
920
        );
921
922
        $this->updateProperty($this->elo_diff, "elo_diff", $elo);
923
924
        $a->changeElo($elo);
925
        $b->changeElo(-$elo);
926
927
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo());
928
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo());
929
    }
930
931
    /**
932
     * Get all the matches in the database
933
     */
934
    public static function getMatches()
935
    {
936
        return self::getQueryBuilder()->active()->getModels();
937
    }
938
939
    /**
940
     * Get a query builder for matches
941
     * @return MatchQueryBuilder
942
     */
943
    public static function getQueryBuilder()
944
    {
945
        return new MatchQueryBuilder('Match', array(
946
            'columns' => array(
947
                'firstTeam'        => 'team_a',
948
                'secondTeam'       => 'team_b',
949
                'firstTeamPoints'  => 'team_a_points',
950
                'secondTeamPoints' => 'team_b_points',
951
                'time'             => 'timestamp',
952
                'type'             => 'match_type',
953
                'status'           => 'status'
954
            ),
955
        ));
956
    }
957
958
    /**
959
     * {@inheritdoc}
960
     */
961
    public function delete()
962
    {
963
        $this->updateMatchCount(true);
964
965
        return parent::delete();
966
    }
967
968
    /**
969
     * {@inheritdoc}
970
     */
971
    public static function getActiveStatuses()
972
    {
973
        return array('entered');
974
    }
975
976
    /**
977
     * {@inheritdoc}
978
     */
979
    public function getName()
980
    {
981
        switch ($this->getMatchType()) {
982
            case self::OFFICIAL:
983
                $description = "(+/- " . $this->getEloDiff() . ") ";
984
                break;
985
            case self::FUN:
986
                $description = "Fun Match:";
987
                break;
988
            case self::SPECIAL:
989
                $description = "Special Match: ";
990
                break;
991
            default:
992
                $description = "";
993
        }
994
995
        return sprintf("%s %s [%d] vs [%d] %s",
996
            $description,
997
            $this->getWinner()->getName(),
998
            $this->getScore($this->getWinner()),
999
            $this->getScore($this->getLoser()),
1000
            $this->getLoser()->getName()
1001
        );
1002
    }
1003
1004
    /**
1005
     * Update the match count of the teams participating in the match
1006
     *
1007
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1008
     */
1009
    private function updateMatchCount($decrement = false)
1010
    {
1011
        if ($this->match_type !== 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...
1012
            return;
1013
        }
1014
1015
        $diff = ($decrement) ? -1 : 1;
1016
1017
        if ($this->isDraw()) {
1018
            $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...
1019
            $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...
1020
        } else {
1021
            $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...
1022
            $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...
1023
        }
1024
    }
1025
}
1026