Completed
Pull Request — master (#51)
by Konstantinos
04:39
created

Match::assignResult()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 25
rs 8.8571
ccs 7
cts 7
cp 1
cc 1
eloc 23
nc 1
nop 1
crap 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
    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
            'team_a_elo_new' => $team_a->getElo(),
0 ignored issues
show
Bug introduced by
The variable $team_a seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
811
            'team_b_elo_new' => $team_b->getElo(),
0 ignored issues
show
Bug introduced by
The variable $team_b seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
812
            'elo_diff'       => $diff,
0 ignored issues
show
Bug introduced by
The variable $diff seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
813
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
814
            'duration'       => $duration,
815
            'entered_by'     => $entered_by,
816
            'server'         => $server,
817 3
            'port'           => $port,
818
            'replay_file'    => $replayFile,
819
            'map'            => $map,
820
            'status'         => 'entered',
821
            'match_type'     => $matchType
822
        );
823
824
        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...
825
            $team_a = Team::get($a);
826
            $team_b = Team::get($b);
827
            $a_elo = $team_a->getElo();
828
            $b_elo = $team_b->getElo();
829
830
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
831
832
            // Update team ELOs
833
            $team_a->changeElo($diff);
834 3
            $team_b->changeElo(-$diff);
835
836 3
            $matchData = array_merge($matchData, array(
837
                'team_a'         => $a,
838
                'team_b'         => $b,
839
                'team_a_elo_new' => $team_a->getElo(),
840
                'team_b_elo_new' => $team_b->getElo(),
841
                'elo_diff'       => $diff
842 1
            ));
843
        }
844 1
845 1
        $match = self::create($matchData, 'updated');
846 1
847 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...
848 1
            $match->updateMatchCount();
849 1
        }
850
851
        return $match;
852
    }
853
854
    /**
855
     * Calculate the ELO score difference
856
     *
857
     * Computes the ELO score difference on each team after a match, based on
858 9
     * GU League's rules.
859
     *
860 9
     * @param  int $a_elo    Team A's current ELO score
861
     * @param  int $b_elo    Team B's current ELO score
862 9
     * @param  int $a_points Team A's match points
863 4
     * @param  int $b_points Team B's match points
864 4
     * @param  int $duration The match duration in minutes
865
     * @return int The ELO score difference
866 6
     */
867 6
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
868
    {
869 9
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
870
        if ($a_points > $b_points) {
871
            $diff = 50 * (1 - $prob);
872
        } elseif ($a_points == $b_points) {
873
            $diff = 50 * (0.5 - $prob);
874
        } else {
875
            $diff = 50 * (0 - $prob);
876
        }
877
878
        // Apply ELO modifiers from `config.yml`
879
        $durations = Service::getParameter('bzion.league.duration');
880
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
881
882
        if (abs($diff) < 1 && $diff != 0) {
883
            // ELOs such as 0.75 should round up to 1...
884
            return ($diff > 0) ? 1 : -1;
885
        }
886
887
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
888
        return intval($diff);
889
    }
890
891
    /**
892
     * Find if a match's stored ELO is correct
893
     */
894
    public function isEloCorrect()
895
    {
896
        return $this->elo_diff === $this->calculateEloDiff(
897
            $this->getTeamAEloOld(),
898
            $this->getTeamBEloOld(),
899
            $this->getTeamAPoints(),
900
            $this->getTeamBPoints(),
901
            $this->getDuration()
902
        );
903
    }
904
905
    /**
906
     * Recalculate the match's elo and adjust the team ELO values
907
     */
908
    public function recalculateElo()
909
    {
910
        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...
911
            return;
912
        }
913
914
        $a = $this->getTeamA();
915
        $b = $this->getTeamB();
916
917
        $elo = $this->calculateEloDiff(
918
            $a->getElo(),
919
            $b->getElo(),
920
            $this->getTeamAPoints(),
921
            $this->getTeamBPoints(),
922
            $this->getDuration()
923
        );
924
925
        $this->updateProperty($this->elo_diff, "elo_diff", $elo);
926
927
        $a->changeElo($elo);
928
        $b->changeElo(-$elo);
929
930
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo());
931
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo());
932
    }
933
934
    /**
935
     * Get all the matches in the database
936
     */
937
    public static function getMatches()
938
    {
939
        return self::getQueryBuilder()->active()->getModels();
940
    }
941
942
    /**
943
     * Get a query builder for matches
944
     * @return MatchQueryBuilder
945
     */
946
    public static function getQueryBuilder()
947
    {
948
        return new MatchQueryBuilder('Match', array(
949
            'columns' => array(
950
                'firstTeam'        => 'team_a',
951
                'secondTeam'       => 'team_b',
952
                'firstTeamPoints'  => 'team_a_points',
953
                'secondTeamPoints' => 'team_b_points',
954
                'time'             => 'timestamp',
955
                'type'             => 'match_type',
956
                'status'           => 'status'
957
            ),
958
        ));
959
    }
960
961
    /**
962
     * {@inheritdoc}
963
     */
964
    public function delete()
965
    {
966
        $this->updateMatchCount(true);
967
968
        return parent::delete();
969
    }
970
971
    /**
972
     * {@inheritdoc}
973
     */
974
    public static function getActiveStatuses()
975
    {
976
        return array('entered');
977
    }
978
979
    /**
980
     * {@inheritdoc}
981
     */
982
    public function getName()
983
    {
984
        switch ($this->getMatchType()) {
985
            case self::OFFICIAL:
986
                $description = "(+/- " . $this->getEloDiff() . ") ";
987
                break;
988
            case self::FUN:
989
                $description = "Fun Match:";
990
                break;
991
            case self::SPECIAL:
992
                $description = "Special Match: ";
993
                break;
994
            default:
995
                $description = "";
996
        }
997
998
        return sprintf("%s %s [%d] vs [%d] %s",
999
            $description,
1000
            $this->getWinner()->getName(),
1001
            $this->getScore($this->getWinner()),
1002
            $this->getScore($this->getLoser()),
1003
            $this->getLoser()->getName()
1004
        );
1005
    }
1006
1007
    /**
1008
     * Update the match count of the teams participating in the match
1009
     *
1010
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1011
     */
1012
    private function updateMatchCount($decrement = false)
1013
    {
1014
        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...
1015
            return;
1016
        }
1017
1018
        $diff = ($decrement) ? -1 : 1;
1019
1020
        if ($this->isDraw()) {
1021
            $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...
1022
            $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...
1023
        } else {
1024
            $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...
1025
            $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...
1026
        }
1027
    }
1028
}
1029