Completed
Push — fm-support ( 17bfcf...3b5d1b )
by Konstantinos
05:55
created

Match::getOpponent()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 8

Duplication

Lines 14
Ratio 100 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 14
loc 14
ccs 6
cts 8
cp 0.75
rs 9.2
cc 4
eloc 8
nc 6
nop 1
crap 4.25
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));
0 ignored issues
show
Bug introduced by
It seems like $this->getOpponent($teamID) targeting Match::getOpponent() can also be of type null; however, Match::getScore() does only seem to accept integer|string|object<TeamInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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 The match's new timestamp
301
     * @return $this
302
     */
303
    public function setTimestamp($timestamp)
304
    {
305
        $this->timestamp = TimeDate::from($timestamp);
306
        $this->update("timestamp", $this->timestamp->toMysql(), "s");
307
308
        return $this;
309
    }
310
311
    /**
312
     * Get the first team involved in the match
313
     * @return TeamInterface Team A
314
     */
315 9
    public function getTeamA()
316
    {
317 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...
318 9
            return Team::get($this->team_a);
319
        }
320
321
        return new ColorTeam($this->team_a_color);
322
    }
323
324
    /**
325
     * Get the second team involved in the match
326
     * @return TeamInterface Team B
327
     */
328 9
    public function getTeamB()
329
    {
330 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...
331 9
            return Team::get($this->team_b);
332
        }
333
334
        return new ColorTeam($this->team_b_color);
335
    }
336
337
    /**
338
     * Get the color of Team A
339
     * @return string
340
     */
341
    public function getTeamAColor()
342
    {
343
        return $this->team_a_color;
344
    }
345
346
    /**
347
     * Get the color of Team B
348
     * @return string
349
     */
350
    public function getTeamBColor()
351
    {
352
        return $this->team_b_color;
353
    }
354
355
    /**
356
     * Get the list of players on Team A who participated in this match
357
     * @return Player[]|null Returns null if there were no players recorded for this match
358
     */
359 1
    public function getTeamAPlayers()
360
    {
361 1
        return $this->parsePlayers($this->team_a_players);
362
    }
363
364
    /**
365
     * Get the list of players on Team B who participated in this match
366
     * @return Player[]|null Returns null if there were no players recorded for this match
367
     */
368 1
    public function getTeamBPlayers()
369
    {
370 1
        return $this->parsePlayers($this->team_b_players);
371
    }
372
373
    /**
374
     * Get the list of players for a team in a match
375
     * @param  Team|int The team or team ID
376
     * @return Player[]|null Returns null if there were no players recorded for this match
377
     */
378 1
    public function getPlayers($team)
379
    {
380 1
        if ($team instanceof Team) {
381 1
            $team = $team->getId();
382
        }
383
384 1
        if ($team == $this->getTeamA()->getId()) {
385 1
            return $this->getTeamAPlayers();
386 1
        } elseif ($team == $this->getTeamB()->getId()) {
387 1
            return $this->getTeamBPlayers();
388
        }
389
390
        return null;
391
    }
392
393
    /**
394
     * Set the players of the match's teams
395
     *
396
     * @param int[] $teamAPlayers An array of player IDs
397
     * @param int[] $teamBPlayers An array of player IDs
398
     * @return self
399
     */
400
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
401
    {
402
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers), "s");
403
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers), "s");
404
405
        return $this;
406
    }
407
408
    /**
409
     * Get an array of players based on a string representation
410
     * @param string $playerString
411
     * @return Player[]|null Returns null if there were no players recorded for this match
412
     */
413 1
    private static function parsePlayers($playerString)
414
    {
415 1
        if ($playerString == null) {
416 1
            return null;
417
        }
418
419
        return Player::arrayIdToModel(explode(",", $playerString));
420
    }
421
422
    /**
423
     * Get the first team's points
424
     * @return int Team A's points
425
     */
426 4
    public function getTeamAPoints()
427
    {
428 4
        return $this->team_a_points;
429
    }
430
431
    /**
432
     * Get the second team's points
433
     * @return int Team B's points
434
     */
435 4
    public function getTeamBPoints()
436
    {
437 4
        return $this->team_b_points;
438
    }
439
440
    /**
441
     * Set the match team points
442
     *
443
     * @param  int $teamAPoints Team A's points
444
     * @param  int $teamBPoints Team B's points
445
     * @return self
446
     */
447
    public function setTeamPoints($teamAPoints, $teamBPoints)
448
    {
449
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints, "i");
450
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints, "i");
451
452
        return $this;
453
    }
454
455
    /**
456
     * Set the match team colors
457
     *
458
     * @param  ColorTeam|string $teamAColor The color of team A
459
     * @param  ColorTeam|string $teamBColor The color of team B
460
     * @return self
461
     */
462
    public function setTeamColors($teamAColor, $teamBColor)
463
    {
464
        if ($this->isOfficial()) {
465
            throw new \Exception("Cannot change team colors in an official match");
466
        }
467
468
        if ($teamAColor instanceof ColorTeam) {
469
            $teamAColor = $teamAColor->getId();
470
        }
471
        if ($teamBColor instanceof ColorTeam) {
472
            $teamBColor = $teamBColor->getId();
473
        }
474
475
        $this->updateProperty($this->team_a_color, "team_a_color", $teamAColor, "s");
476
        $this->updateProperty($this->team_b_color, "team_b_color", $teamBColor, "s");
477
    }
478
479
    /**
480
     * Get the ELO difference applied to each team's old ELO
481
     * @return int The ELO difference
482
     */
483 7
    public function getEloDiff()
484
    {
485 7
        return abs($this->elo_diff);
486
    }
487
488
    /**
489
     * Get the first team's new ELO
490
     * @return int Team A's new ELO
491
     */
492 7
    public function getTeamAEloNew()
493
    {
494 7
        return $this->team_a_elo_new;
495
    }
496
497
    /**
498
     * Get the second team's new ELO
499
     * @return int Team B's new ELO
500
     */
501 7
    public function getTeamBEloNew()
502
    {
503 7
        return $this->team_b_elo_new;
504
    }
505
506
    /**
507
     * Get the first team's old ELO
508
     * @return int
509
     */
510 6
    public function getTeamAEloOld()
511
    {
512 6
        return $this->team_a_elo_new - $this->elo_diff;
513
    }
514
515
    /**
516
     * Get the second team's old ELO
517
     * @return int
518
     */
519 6
    public function getTeamBEloOld()
520
    {
521 6
        return $this->team_b_elo_new + $this->elo_diff;
522
    }
523
524
    /**
525
     * Get the team's new ELO
526
     * @param  Team $team The team whose new ELO to return
527
     * @return int|null   The new ELO, or null if the team provided has not
528
     *                    participated in the match
529
     */
530 1
    public function getTeamEloNew(Team $team)
531
    {
532 1
        if ($team->getId() === $this->team_a) {
533 1
            return $this->getTeamAEloNew();
534 1
        } elseif ($team->getId() === $this->team_b) {
535 1
            return $this->getTeamBEloNew();
536
        }
537
538
        return null;
539
    }
540
541
    /**
542
     * Get the team's old ELO
543
     * @param  Team $team The team whose old ELO to return
544
     * @return int|null   The old ELO, or null if the team provided has not
545
     *                    participated in the match
546
     */
547 1
    public function getTeamEloOld(Team $team)
548
    {
549 1
        if ($team->getId() === $this->team_a) {
550 1
            return $this->getTeamAEloOld();
551 1
        } elseif ($team->getId() === $this->team_b) {
552 1
            return $this->getTeamBEloOld();
553
        }
554
555
        return null;
556
    }
557
558
    /**
559
     * Get the map where the match was played on
560
     * @return Map Returns an invalid map if no map was found
561
     */
562 1
    public function getMap()
563
    {
564 1
        return Map::get($this->map);
565
    }
566
567
    /**
568
     * Set the map where the match was played
569
     * @param  int $map The ID of the map
570
     * @return self
571
     */
572
    public function setMap($map)
573
    {
574
        $this->updateProperty($this->map, "map", $map, "s");
575
    }
576
577
    /**
578
     * Get the match type
579
     *
580
     * @return string 'official', 'fm', or 'special'
581
     */
582 1
    public function getMatchType()
583
    {
584 1
        return $this->match_type;
585
    }
586
587
    /**
588
     * Set the match type
589
     *
590
     * @param  string $matchType A valid match type; official, fm, special
591
     *
592
     * @return static
593
     */
594
    public function setMatchType($matchType)
595
    {
596
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
597
    }
598
599
    /**
600
     * Get a JSON decoded array of events that occurred during the match
601
     * @return mixed|null Returns null if there were no events recorded for the match
602
     */
603
    public function getMatchDetails()
604
    {
605
        return json_decode($this->match_details);
606
    }
607
608
    /**
609
     * Get the server address of the server where this match took place
610
     * @return string|null Returns null if there was no server address recorded
611
     */
612
    public function getServerAddress()
613
    {
614
        if ($this->port == null || $this->server == null) {
615
            return null;
616
        }
617
618
        return $this->server . ":" . $this->port;
619
    }
620
621
    /**
622
     * Set the server address of the server where this match took place
623
     *
624
     * @param  string|null $server The server hostname
625
     * @param  int|null    $port   The server port
626
     * @return self
627
     */
628
    public function setServerAddress($server = null, $port = 5154)
629
    {
630
        $this->updateProperty($this->server, "server", $server, "s");
631
        $this->updateProperty($this->port, "port", $port, "i");
632
633
        return $this;
634
    }
635
636
    /**
637
     * Get the name of the replay file for this specific map
638
     * @param  int    $length The length of the replay file name; it will be truncated
639
     * @return string Returns null if there was no replay file name recorded
640
     */
641
    public function getReplayFileName($length = 0)
642
    {
643
        if ($length > 0) {
644
            return substr($this->replay_file, 0, $length);
645
        }
646
647
        return $this->replay_file;
648
    }
649
650
    /**
651
     * Get the match duration
652
     * @return int The duration of the match in minutes
653
     */
654 3
    public function getDuration()
655
    {
656 3
        return $this->duration;
657
    }
658
659
    /**
660
     * Set the match duration
661
     *
662
     * @param  int  $duration The new duration of the match in minutes
663
     * @return self
664
     */
665
    public function setDuration($duration)
666
    {
667
        return $this->updateProperty($this->duration, "duration", $duration, "i");
668
    }
669
670
    /**
671
     * Get the user who entered the match
672
     * @return Player
673
     */
674 2
    public function getEnteredBy()
675
    {
676 2
        return Player::get($this->entered_by);
677
    }
678
679
    /**
680
     * Get the loser of the match
681
     *
682
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
683
     */
684 8
    public function getLoser()
685
    {
686
        // Get the winner of the match
687 8
        $winner = $this->getWinner();
688
689
        // Get the team that wasn't the winner... Duh
690 8
        return $this->getOpponent($winner);
0 ignored issues
show
Bug introduced by
It seems like $winner defined by $this->getWinner() on line 687 can also be of type null; however, Match::getOpponent() does only seem to accept integer|string|object<TeamInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
691
    }
692
693
    /**
694
     * Get the winner of a match
695
     *
696
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
697
     */
698 8
    public function getWinner()
699
    {
700
        // Get the team that had its ELO increased
701 8
        if ($this->elo_diff > 0) {
702 6
            return $this->getTeamA();
703 2
        } elseif ($this->elo_diff < 0) {
704 2
            return $this->getTeamB();
705
        } elseif ($this->team_a_points > $this->team_b_points) {
706
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
707
            return $this->getTeamA();
708
        } elseif ($this->team_a_points < $this->team_b_points) {
709
            return $this->getTeamB();
710
        }
711
712
        // If the scores are the same, return Team A because well, fuck you that's why
713
        return $this->getTeamA();
714
    }
715
716
    /**
717
     * Determine whether the match was a draw
718
     * @return bool True if the match ended without any winning teams
719
     */
720 9
    public function isDraw()
721
    {
722 9
        return $this->team_a_points == $this->team_b_points;
723
    }
724
725
    /**
726
     * Find out whether the match involves a team
727
     *
728
     * @param  TeamInterface $team The team to check
729
     * @return bool
730
     */
731 1
    public function involvesTeam($team)
732
    {
733 1
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
734
    }
735
736
    /**
737
     * Find out if the match is played between official teams
738
     */
739 1
    public function isOfficial()
740
    {
741 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...
742
    }
743
744
    /**
745
     * Reset the ELOs of the teams participating in the match
746
     *
747
     * @return self
748
     */
749
    public function resetELOs()
750
    {
751
        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...
752
            $this->getTeamA()->changeELO(-$this->elo_diff);
0 ignored issues
show
Bug introduced by
The method changeELO does only exist in Team, but not in ColorTeam.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
753
            $this->getTeamB()->changeELO(+$this->elo_diff);
754
        }
755
756
        return $this;
757
    }
758
759
    /**
760
     * Calculate the match's contribution to the team activity
761
     *
762
     * @return float
763
     */
764 1
    public function getActivity()
765
    {
766 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
767 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
768
769 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
770
771 1
        if (is_nan($activity) || $activity < 0.0) {
772
            return 0.0;
773
        }
774
775 1
        return $activity;
776
    }
777
778
    /**
779
     * Enter a new match to the database
780
     * @param  int             $a          Team A's ID
781
     * @param  int             $b          Team B's ID
782
     * @param  int             $a_points   Team A's match points
783
     * @param  int             $b_points   Team B's match points
784
     * @param  int             $duration   The match duration in minutes
785
     * @param  int|null        $entered_by The ID of the player reporting the match
786
     * @param  string|DateTime $timestamp  When the match was played
787
     * @param  int[]           $a_players  The IDs of the first team's players
788
     * @param  int[]           $b_players  The IDs of the second team's players
789
     * @param  string|null     $server     The address of the server where the match was played
790
     * @param  int|null        $port       The port of the server where the match was played
791
     * @param  string          $replayFile The name of the replay file of the match
792
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
793
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
794
     * @param  string          $a_color    Team A's color
795
     * @param  string          $b_color    Team b's color
796
     * @return Match           An object representing the match that was just entered
797
     */
798 9
    public static function enterMatch(
799
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
800
        $a_players = array(), $b_players = array(), $server = null, $port = null,
801
        $replayFile = null, $map = null, $matchType = "official", $a_color = null,
802
        $b_color = null
803
    ) {
804
        $matchData = array(
805 9
            'team_a_color'   => strtolower($a_color),
806 9
            'team_b_color'   => strtolower($b_color),
807 9
            'team_a_points'  => $a_points,
808 9
            'team_b_points'  => $b_points,
809 9
            'team_a_players' => implode(',', $a_players),
810 9
            'team_b_players' => implode(',', $b_players),
811 9
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
812 9
            'duration'       => $duration,
813 9
            'entered_by'     => $entered_by,
814 9
            'server'         => $server,
815 9
            'port'           => $port,
816 9
            'replay_file'    => $replayFile,
817 9
            'map'            => $map,
818 9
            'status'         => 'entered',
819 9
            'match_type'     => $matchType
820
        );
821 9
        $matchDataTypes = 'ssiisssiisisiss';
822
823 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...
824 9
            $team_a = Team::get($a);
825 9
            $team_b = Team::get($b);
826 9
            $a_elo = $team_a->getElo();
827 9
            $b_elo = $team_b->getElo();
828
829 9
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
830
831
            // Update team ELOs
832 9
            $team_a->changeElo($diff);
833 9
            $team_b->changeElo(-$diff);
834
835 9
            $matchData = array_merge($matchData, array(
836 9
                'team_a'         => $a,
837 9
                'team_b'         => $b,
838 9
                'team_a_elo_new' => $team_a->getElo(),
839 9
                'team_b_elo_new' => $team_b->getElo(),
840 9
                'elo_diff'       => $diff
841
            ));
842 9
            $matchDataTypes .= 'iiiii';
843
        }
844
845 9
        $match = self::create($matchData, $matchDataTypes, 'updated');
846
847 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...
848 9
            $match->updateMatchCount();
849
        }
850
851 9
        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
     * GU League's rules.
859
     *
860
     * @param  int $a_elo    Team A's current ELO score
861
     * @param  int $b_elo    Team B's current ELO score
862
     * @param  int $a_points Team A's match points
863
     * @param  int $b_points Team B's match points
864
     * @param  int $duration The match duration in minutes
865
     * @return int The ELO score difference
866
     */
867 9
    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 9
        if ($a_points > $b_points) {
871 5
            $diff = 50 * (1 - $prob);
872 5
        } elseif ($a_points == $b_points) {
873 4
            $diff = 50 * (0.5 - $prob);
874
        } else {
875 1
            $diff = 50 * (0 - $prob);
876
        }
877
878
        // Apply ELO modifiers from `config.yml`
879 9
        $durations = Service::getParameter('bzion.league.duration');
880 9
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
881
882 9
        if (abs($diff) < 1 && $diff != 0) {
883
            // ELOs such as 0.75 should round up to 1...
884 2
            return ($diff > 0) ? 1 : -1;
885
        }
886
887
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
888 7
        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(),
0 ignored issues
show
Bug introduced by
The method getElo does only exist in Team, but not in ColorTeam.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
919
            $b->getElo(),
920
            $this->getTeamAPoints(),
921
            $this->getTeamBPoints(),
922
            $this->getDuration()
923
        );
924
925
        $this->updateProperty($this->elo_diff, "elo_diff", $elo, "i");
926
927
        $a->changeElo($elo);
0 ignored issues
show
Bug introduced by
The method changeElo does only exist in Team, but not in ColorTeam.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
928
        $b->changeElo(-$elo);
929
930
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo(), "i");
931
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo(), "i");
932
    }
933
934
    /**
935
     * Get all the matches in the database
936
     */
937 1
    public static function getMatches()
938
    {
939 1
        return self::getQueryBuilder()->active()->getModels();
940
    }
941
942
    /**
943
     * Get a query builder for matches
944
     * @return MatchQueryBuilder
945
     */
946 3
    public static function getQueryBuilder()
947
    {
948 3
        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 3
            ),
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 3
    public static function getActiveStatuses()
975
    {
976 3
        return array('entered');
977
    }
978
979
    /**
980
     * {@inheritdoc}
981
     */
982 1
    public function getName()
983
    {
984 1
        return sprintf("(+/- %d) %s [%d] vs [%d] %s",
985 1
            $this->getEloDiff(),
986 1
            $this->getWinner()->getName(),
987 1
            $this->getScore($this->getWinner()),
0 ignored issues
show
Bug introduced by
It seems like $this->getWinner() targeting Match::getWinner() can also be of type null; however, Match::getScore() does only seem to accept integer|string|object<TeamInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
988 1
            $this->getScore($this->getLoser()),
0 ignored issues
show
Bug introduced by
It seems like $this->getLoser() targeting Match::getLoser() can also be of type null; however, Match::getScore() does only seem to accept integer|string|object<TeamInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
989 1
            $this->getLoser()->getName()
990
        );
991
    }
992
993
    /**
994
     * Update the match count of the teams participating in the match
995
     *
996
     * @param bool $decrement Whether to decrement instead of incrementing the match count
997
     */
998 9
    private function updateMatchCount($decrement = false)
999
    {
1000 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...
1001
            return;
1002
        }
1003
1004 9
        $diff = ($decrement) ? -1 : 1;
1005
1006 9
        if ($this->isDraw()) {
1007 4
            $this->getTeamA()->changeMatchCount($diff, 'draw');
0 ignored issues
show
Bug introduced by
The method changeMatchCount does only exist in Team, but not in ColorTeam.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1008 4
            $this->getTeamB()->changeMatchCount($diff, 'draw');
1009
        } else {
1010 6
            $this->getWinner()->changeMatchCount($diff, 'win');
1011 6
            $this->getLoser()->changeMatchCount($diff, 'loss');
1012
        }
1013 9
    }
1014
}
1015