Completed
Push — master ( 8158f1...90d7fd )
by Konstantinos
11:17 queued 06:55
created

Match::assignResult()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 25
ccs 24
cts 24
cp 1
rs 8.8571
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
     * @var string
140
     */
141
    protected $entered_by;
142
143
    /**
144
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
145
     * @var string
146
     */
147
    protected $status;
148
149
    /**
150
     * The name of the database table used for queries
151
     */
152
    const TABLE = "matches";
153
154
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
155
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
156
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
157
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 9
    protected function assignResult($match)
163
    {
164 9
        $this->team_a = $match['team_a'];
165 9
        $this->team_b = $match['team_b'];
166 9
        $this->team_a_color = $match['team_a_color'];
167 9
        $this->team_b_color = $match['team_b_color'];
168 9
        $this->team_a_points = $match['team_a_points'];
169 9
        $this->team_b_points = $match['team_b_points'];
170 9
        $this->team_a_players = $match['team_a_players'];
171 9
        $this->team_b_players = $match['team_b_players'];
172 9
        $this->team_a_elo_new = $match['team_a_elo_new'];
173 9
        $this->team_b_elo_new = $match['team_b_elo_new'];
174 9
        $this->map = $match['map'];
175 9
        $this->match_type = $match['match_type'];
176 9
        $this->match_details = $match['match_details'];
177 9
        $this->port = $match['port'];
178 9
        $this->server = $match['server'];
179 9
        $this->replay_file = $match['replay_file'];
180 9
        $this->elo_diff = $match['elo_diff'];
181 9
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
182 9
        $this->updated = TimeDate::fromMysql($match['updated']);
183 9
        $this->duration = $match['duration'];
184 9
        $this->entered_by = $match['entered_by'];
185 9
        $this->status = $match['status'];
186 9
    }
187
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 1
    public static function getRouteName($action = 'show')
194
    {
195 1
        return "match_$action";
196
    }
197
198
    /**
199
     * 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 1
    public function getMatchDescription($teamID)
206
    {
207 1
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
208 1
            return "win";
209 1
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
210 1
            return "loss";
211
        }
212
213 1
        return "tie";
214
    }
215
216
    /**
217
     * 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
     *
221
     * @return string Either "W", "L", or "T" relative to the team
222
     */
223 1
    public function getMatchLetter($teamID)
224
    {
225 1
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
226
    }
227
228
    /**
229
     * Get the score of a specific team
230
     *
231
     * @param int|string|TeamInterface $teamID The team we want the score for
232
     *
233
     * @return int The score that team received
234
     */
235 2 View Code Duplication
    public function getScore($teamID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
    {
237 2
        if ($teamID instanceof TeamInterface) {
238
            // Oh no! The caller gave us a Team model instead of an ID!
239 2
            $teamID = $teamID->getId();
240
        } elseif (is_string($teamID)) {
241
            // Make sure we're comparing lowercase strings
242
            $teamID = strtolower($teamID);
243
        }
244
245
246 2
        if ($this->getTeamA()->getId() == $teamID) {
247 2
            return $this->getTeamAPoints();
248
        }
249
250 2
        return $this->getTeamBPoints();
251
    }
252
253
    /**
254
     * Get the score of the opponent relative to a team
255
     *
256
     * @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 2
    public function getOpponentScore($teamID)
261
    {
262 2
        return $this->getScore($this->getOpponent($teamID));
263
    }
264
265
    /**
266
     * 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 8 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 8
        if ($teamID instanceof TeamInterface) {
275 8
            $teamID = $teamID->getId();
276
        } elseif (is_string($teamID)) {
277
            $teamID = strtolower($teamID);
278
        }
279
280 8
        if ($this->getTeamA()->getId() == $teamID) {
281 6
            return $this->getTeamB();
282
        }
283
284 4
        return $this->getTeamA();
285
    }
286
287
    /**
288
     * Get the timestamp of the last update of the match
289
     *
290
     * @return TimeDate The match's update timestamp
291
     */
292 1
    public function getUpdated()
293
    {
294 1
        return $this->updated->copy();
295
    }
296
297
    /**
298
     * 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
	$this->updateProperty($this->timestamp, "timestamp", TimeDate::from($timestamp));
306
307
        return $this;
308
    }
309
310
    /**
311
     * Get the first team involved in the match
312
     * @return TeamInterface Team A
313
     */
314 9
    public function getTeamA()
315
    {
316 9
        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 9
            return Team::get($this->team_a);
318
        }
319
320 1
        return new ColorTeam($this->team_a_color);
321
    }
322
323
    /**
324
     * Get the second team involved in the match
325
     * @return TeamInterface Team B
326
     */
327 9
    public function getTeamB()
328
    {
329 9
        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 9
            return Team::get($this->team_b);
331
        }
332
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 1
    public function getTeamAPlayers()
359
    {
360 1
        return $this->parsePlayers($this->team_a_players);
361
    }
362
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 1
    public function getTeamBPlayers()
368
    {
369 1
        return $this->parsePlayers($this->team_b_players);
370
    }
371
372
    /**
373
     * Get the list of players for a team in a match
374
     * @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 1
    public function getPlayers($team)
378
    {
379 1
        if ($team instanceof TeamInterface) {
380 1
            $team = $team->getId();
381
        }
382
383 1
        if ($team == $this->getTeamA()->getId()) {
384 1
            return $this->getTeamAPlayers();
385 1
        } elseif ($team == $this->getTeamB()->getId()) {
386 1
            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
    }
406
407
    /**
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 1
    private static function parsePlayers($playerString)
413
    {
414 1
        if ($playerString == null) {
415 1
            return null;
416
        }
417
418 1
        return Player::arrayIdToModel(explode(",", $playerString));
419
    }
420
421
    /**
422
     * Get the first team's points
423
     * @return int Team A's points
424
     */
425 4
    public function getTeamAPoints()
426
    {
427 4
        return $this->team_a_points;
428
    }
429
430
    /**
431
     * Get the second team's points
432
     * @return int Team B's points
433
     */
434 4
    public function getTeamBPoints()
435
    {
436 4
        return $this->team_b_points;
437
    }
438
439
    /**
440
     * Set the match team points
441
     *
442
     * @param  int $teamAPoints Team A's points
443
     * @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
    }
453
454
    /**
455
     * Set the match team colors
456
     *
457
     * @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
        }
470
        if ($teamBColor instanceof ColorTeam) {
471
            $teamBColor = $teamBColor->getId();
472
        }
473
474
        $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 7
    public function getEloDiff()
483
    {
484 7
        return abs($this->elo_diff);
485
    }
486
487
    /**
488
     * Get the first team's new ELO
489
     * @return int Team A's new ELO
490
     */
491 7
    public function getTeamAEloNew()
492
    {
493 7
        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 7
    public function getTeamBEloNew()
501
    {
502 7
        return $this->team_b_elo_new;
503
    }
504
505
    /**
506
     * Get the first team's old ELO
507
     * @return int
508
     */
509 6
    public function getTeamAEloOld()
510
    {
511 6
        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 6
    public function getTeamBEloOld()
519
    {
520 6
        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 1
    public function getTeamEloNew(Team $team)
530
    {
531 1
        if ($team->getId() == $this->team_a) {
532 1
            return $this->getTeamAEloNew();
533 1
        } elseif ($team->getId() == $this->team_b) {
534 1
            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 1
    public function getTeamEloOld(Team $team)
547
    {
548 1
        if ($team->getId() == $this->team_a) {
549 1
            return $this->getTeamAEloOld();
550 1
        } elseif ($team->getId() == $this->team_b) {
551 1
            return $this->getTeamBEloOld();
552
        }
553
554
        return null;
555
    }
556
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 1
    public function getMap()
562
    {
563 1
        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
    }
575
576
    /**
577
     * Get the match type
578
     *
579
     * @return string 'official', 'fm', or 'special'
580
     */
581 1
    public function getMatchType()
582
    {
583 1
        return $this->match_type;
584
    }
585
586
    /**
587
     * Set the match type
588
     *
589
     * @param  string $matchType A valid match type; official, fm, special
590
     *
591
     * @return static
592
     */
593
    public function setMatchType($matchType)
594
    {
595
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
596
    }
597
598
    /**
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
     */
602
    public function getMatchDetails()
603
    {
604
        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
        }
616
617
        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
     */
627
    public function setServerAddress($server = null, $port = 5154)
628
    {
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
    }
648
649
    /**
650
     * Get the match duration
651
     * @return int The duration of the match in minutes
652
     */
653 3
    public function getDuration()
654
    {
655 3
        return $this->duration;
656
    }
657
658
    /**
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 2
    public function getEnteredBy()
674
    {
675 2
        return Player::get($this->entered_by);
676
    }
677
678
    /**
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 8
    public function getLoser()
684
    {
685
        // Get the winner of the match
686 8
        $winner = $this->getWinner();
687
688
        // Get the team that wasn't the winner... Duh
689 8
        return $this->getOpponent($winner);
690
    }
691
692
    /**
693
     * Get the winner of a match
694
     *
695
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
696
     */
697 8
    public function getWinner()
698
    {
699
        // Get the team that had its ELO increased
700 8
        if ($this->elo_diff > 0) {
701 6
            return $this->getTeamA();
702 3
        } elseif ($this->elo_diff < 0) {
703 2
            return $this->getTeamB();
704 1
        } elseif ($this->team_a_points > $this->team_b_points) {
705
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
706 1
            return $this->getTeamA();
707
        } elseif ($this->team_a_points < $this->team_b_points) {
708
            return $this->getTeamB();
709
        }
710
711
        // If the scores are the same, return Team A because well, fuck you that's why
712
        return $this->getTeamA();
713
    }
714
715
    /**
716
     * Determine whether the match was a draw
717
     * @return bool True if the match ended without any winning teams
718
     */
719 9
    public function isDraw()
720
    {
721 9
        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 1
    public function involvesTeam($team)
731
    {
732 1
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
733
    }
734
735
    /**
736
     * Find out if the match is played between official teams
737
     */
738 1
    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
     * Reset the ELOs of the teams participating in the match
745
     *
746
     * @return self
747
     */
748
    public function resetELOs()
749
    {
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
        }
754
755
        return $this;
756
    }
757
758
    /**
759
     * Calculate the match's contribution to the team activity
760
     *
761
     * @return float
762
     */
763 1
    public function getActivity()
764
    {
765 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
766 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
767
768 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
769
770 1
        if (is_nan($activity) || $activity < 0.0) {
771
            return 0.0;
772
        }
773
774 1
        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 9
    public static function enterMatch(
798
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
799
        $a_players = array(), $b_players = array(), $server = null, $port = null,
800
        $replayFile = null, $map = null, $matchType = "official", $a_color = null,
801
        $b_color = null
802
    ) {
803
        $matchData = array(
804 9
            'team_a_color'   => strtolower($a_color),
805 9
            'team_b_color'   => strtolower($b_color),
806 9
            'team_a_points'  => $a_points,
807 9
            'team_b_points'  => $b_points,
808 9
            'team_a_players' => implode(',', $a_players),
809 9
            'team_b_players' => implode(',', $b_players),
810 9
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
811 9
            'duration'       => $duration,
812 9
            'entered_by'     => $entered_by,
813 9
            'server'         => $server,
814 9
            'port'           => $port,
815 9
            'replay_file'    => $replayFile,
816 9
            'map'            => $map,
817 9
            'status'         => 'entered',
818 9
            'match_type'     => $matchType
819
        );
820
821 9
        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 9
            $team_a = Team::get($a);
823 9
            $team_b = Team::get($b);
824 9
            $a_elo = $team_a->getElo();
825 9
            $b_elo = $team_b->getElo();
826
827 9
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
828
829
            // Update team ELOs
830 9
            $team_a->changeElo($diff);
831 9
            $team_b->changeElo(-$diff);
832
833 9
            $matchData = array_merge($matchData, array(
834 9
                'team_a'         => $a,
835 9
                'team_b'         => $b,
836 9
                'team_a_elo_new' => $team_a->getElo(),
837 9
                'team_b_elo_new' => $team_b->getElo(),
838 9
                'elo_diff'       => $diff
839
            ));
840
        }
841
842 9
        $match = self::create($matchData, 'updated');
843
844 9
        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 9
            $match->updateMatchCount();
846
        }
847
848 9
        return $match;
849
    }
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
     * @param  int $b_elo    Team B's current ELO score
859
     * @param  int $a_points Team A's match points
860
     * @param  int $b_points Team B's match points
861
     * @param  int $duration The match duration in minutes
862
     * @return int The ELO score difference
863
     */
864 9
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
865
    {
866 9
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
867 9
        if ($a_points > $b_points) {
868 5
            $diff = 50 * (1 - $prob);
869 5
        } elseif ($a_points == $b_points) {
870 4
            $diff = 50 * (0.5 - $prob);
871
        } else {
872 1
            $diff = 50 * (0 - $prob);
873
        }
874
875
        // Apply ELO modifiers from `config.yml`
876 9
        $durations = Service::getParameter('bzion.league.duration');
877 9
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
878
879 9
        if (abs($diff) < 1 && $diff != 0) {
880
            // ELOs such as 0.75 should round up to 1...
881 2
            return ($diff > 0) ? 1 : -1;
882
        }
883
884
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
885 7
        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 1
    public static function getMatches()
935
    {
936 1
        return self::getQueryBuilder()->active()->getModels();
937
    }
938
939
    /**
940
     * Get a query builder for matches
941
     * @return MatchQueryBuilder
942
     */
943 3
    public static function getQueryBuilder()
944
    {
945 3
        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
                'map'              => 'map',
953
                'type'             => 'match_type',
954
                'status'           => 'status'
955 3
            ),
956
        ));
957
    }
958
959
    /**
960
     * {@inheritdoc}
961
     */
962
    public function delete()
963
    {
964
        $this->updateMatchCount(true);
965
966
        return parent::delete();
967
    }
968
969
    /**
970
     * {@inheritdoc}
971
     */
972 3
    public static function getActiveStatuses()
973
    {
974 3
        return array('entered');
975
    }
976
977
    /**
978
     * {@inheritdoc}
979
     */
980 1
    public function getName()
981
    {
982 1
        switch ($this->getMatchType()) {
983 1
            case self::OFFICIAL:
984 1
                $description = "(+/- " . $this->getEloDiff() . ") ";
985 1
                break;
986 1
            case self::FUN:
987 1
                $description = "Fun Match:";
988 1
                break;
989
            case self::SPECIAL:
990
                $description = "Special Match: ";
991
                break;
992
            default:
993
                $description = "";
994
        }
995
996 1
        return sprintf("%s %s [%d] vs [%d] %s",
997
            $description,
998 1
            $this->getWinner()->getName(),
999 1
            $this->getScore($this->getWinner()),
1000 1
            $this->getScore($this->getLoser()),
1001 1
            $this->getLoser()->getName()
1002
        );
1003
    }
1004
1005
    /**
1006
     * Update the match count of the teams participating in the match
1007
     *
1008
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1009
     */
1010 9
    private function updateMatchCount($decrement = false)
1011
    {
1012 9
        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...
1013
            return;
1014
        }
1015
1016 9
        $diff = ($decrement) ? -1 : 1;
1017
1018 9
        if ($this->isDraw()) {
1019 4
            $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...
1020 4
            $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...
1021
        } else {
1022 6
            $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...
1023 6
            $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...
1024
        }
1025 9
    }
1026
}
1027