Completed
Push — fm-support ( bc372a...c8858b )
by Konstantinos
09:10
created

Match::involvesTeam()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 4
rs 10
ccs 0
cts 0
cp 0
cc 2
eloc 2
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * This file contains functionality relating to the official matches played in the league
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
use BZIon\Model\Column\Timestamp;
9
10
/**
11
 * A match played between two teams
12
 * @package    BZiON\Models
13
 */
14
class Match extends UrlModel implements NamedModel
15
{
16
    const OFFICIAL = "official";
17
    const SPECIAL  = "special";
18
    const FUN      = "fm";
19
20
    use Timestamp;
21
22
    /**
23
     * The ID of the first team of the match
24
     * @var int
25
     */
26
    protected $team_a;
27
28
    /**
29
     * The ID of the second team of the match
30
     * @var int
31
     */
32
    protected $team_b;
33
34
    /**
35
     * The color of the first team
36
     * @var string
37
     */
38
    protected $team_a_color;
39
40
    /**
41
     * The color of the second team
42
     * @var string
43
     */
44
    protected $team_b_color;
45
46
    /**
47
     * The match points (usually the number of flag captures) Team A scored
48
     * @var int
49
     */
50
    protected $team_a_points;
51
52
    /**
53
     * The match points Team B scored
54
     * @var int
55
     */
56
    protected $team_b_points;
57
58
    /**
59
     * The BZIDs of players part of Team A who participated in the match, separated by commas
60
     * @var string
61
     */
62
    protected $team_a_players;
63
64
    /**
65
     * The BZIDs of players part of Team B who participated in the match, separated by commas
66
     * @var string
67
     */
68
    protected $team_b_players;
69
70
    /**
71
     * The ELO score of Team A after the match
72
     * @var int
73
     */
74
    protected $team_a_elo_new;
75
76
    /**
77
     * The ELO score of Team B after the match
78
     * @var int
79
     */
80
    protected $team_b_elo_new;
81
82
    /**
83
     * The map ID used in the match if the league supports more than one map
84
     * @var int
85
     */
86
    protected $map;
87
88
    /**
89
     * The type of match that occurred. Valid options: official, fm, special
90
     *
91
     * @var string
92
     */
93
    protected $match_type;
94
95
    /**
96
     * A JSON string of events that happened during a match, such as captures and substitutions
97
     * @var string
98
     */
99
    protected $match_details;
100
101
    /**
102
     * The port of the server where the match took place
103
     * @var int
104
     */
105
    protected $port;
106
107
    /**
108
     * The server location of there the match took place
109
     * @var string
110
     */
111
    protected $server;
112
113
    /**
114
     * The file name of the replay file of the match
115
     * @var string
116
     */
117
    protected $replay_file;
118
119
    /**
120
     * The absolute value of the ELO score difference
121
     * @var int
122
     */
123
    protected $elo_diff;
124
125
    /**
126
     * The timestamp representing when the match information was last updated
127
     * @var TimeDate
128
     */
129
    protected $updated;
130
131
    /**
132
     * The duration of the match in minutes
133
     * @var int
134
     */
135
    protected $duration;
136
137
    /**
138
     * The ID of the person (i.e. referee) who last updated the match information
139
     * @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 1
            $teamID = $teamID->getId();
240
        } elseif (is_string($teamID)) {
241
            // Make sure we're comparing lowercase strings
242 2
            $teamID = strtolower($teamID);
243 2
        }
244
245
246 1
        if ($this->getTeamA()->getId() == $teamID) {
247
            return $this->getTeamAPoints();
248
        }
249
250
        return $this->getTeamBPoints();
251
    }
252
253
    /**
254
     * Get the score of the opponent relative to a team
255
     *
256 2
     * @param int|string|TeamInterface $teamID The opponent of the team we want the score for
257
     *
258 2
     * @return int The score of the opponent
259
     */
260
    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 1
    }
264
265
    /**
266 2
     * Get the opponent of a match relative to a team ID
267
     *
268
     * @param int|string|TeamInterface $teamID The team who is known in a match
269
     *
270
     * @return TeamInterface The opponent team
271
     */
272 View Code Duplication
    public function getOpponent($teamID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
273
    {
274
        if ($teamID instanceof TeamInterface) {
275
            $teamID = $teamID->getId();
276 8
        } elseif (is_string($teamID)) {
277
            $teamID = strtolower($teamID);
278 8
        }
279 6
280
        if ($this->getTeamA()->getId() == $teamID) {
281
            return $this->getTeamB();
282 4
        }
283
284
        return $this->getTeamA();
285
    }
286
287
    /**
288
     * Get the timestamp of the last update of the match
289
     *
290 1
     * @return TimeDate The match's update timestamp
291
     */
292 1
    public function getUpdated()
293
    {
294
        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 9
     * @return TeamInterface Team A
314
     */
315 9
    public function getTeamA()
316
    {
317
        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
            return Team::get($this->team_a);
319
        }
320
321
        return new ColorTeam($this->team_a_color);
322 9
    }
323
324 9
    /**
325
     * Get the second team involved in the match
326
     * @return TeamInterface Team B
327
     */
328
    public function getTeamB()
329
    {
330
        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
            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 1
     */
350
    public function getTeamBColor()
351 1
    {
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 1
     */
359
    public function getTeamAPlayers()
360 1
    {
361
        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 1
    }
372
373
    /**
374 1
     * Get the list of players for a team in a match
375 1
     * @param  Team|int The team or team ID
376 1
     * @return Player[]|null Returns null if there were no players recorded for this match
377 1
     */
378
    public function getPlayers($team)
379
    {
380
        if ($team instanceof Team) {
381
            $team = $team->getId();
382
        }
383
384
        if ($team == $this->team_a) {
385
            return $this->getTeamAPlayers();
386
        } elseif ($team == $this->team_b) {
387
            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 1
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers), "s");
404
405 1
        return $this;
406 1
    }
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
    private static function parsePlayers($playerString)
414
    {
415
        if ($playerString == null) {
416 4
            return null;
417
        }
418 4
419
        return Player::arrayIdToModel(explode(",", $playerString));
420
    }
421
422
    /**
423
     * Get the first team's points
424
     * @return int Team A's points
425 4
     */
426
    public function getTeamAPoints()
427 4
    {
428
        return $this->team_a_points;
429
    }
430
431
    /**
432
     * Get the second team's points
433
     * @return int Team B's points
434
     */
435
    public function getTeamBPoints()
436
    {
437
        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 7
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints, "i");
450
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints, "i");
451 7
452
        return $this;
453
    }
454
455
    /**
456
     * Get the ELO difference applied to each team's old ELO
457
     * @return int The ELO difference
458 7
     */
459
    public function getEloDiff()
460 7
    {
461
        return abs($this->elo_diff);
462
    }
463
464
    /**
465
     * Get the first team's new ELO
466
     * @return int Team A's new ELO
467 7
     */
468
    public function getTeamAEloNew()
469 7
    {
470
        return $this->team_a_elo_new;
471
    }
472
473
    /**
474
     * Get the second team's new ELO
475
     * @return int Team B's new ELO
476 6
     */
477
    public function getTeamBEloNew()
478 6
    {
479
        return $this->team_b_elo_new;
480
    }
481
482
    /**
483
     * Get the first team's old ELO
484
     * @return int
485 6
     */
486
    public function getTeamAEloOld()
487 6
    {
488
        return $this->team_a_elo_new - $this->elo_diff;
489
    }
490
491
    /**
492
     * Get the second team's old ELO
493
     * @return int
494
     */
495
    public function getTeamBEloOld()
496 1
    {
497
        return $this->team_b_elo_new + $this->elo_diff;
498 1
    }
499 1
500 1
    /**
501 1
     * Get the team's new ELO
502
     * @param  Team $team The team whose new ELO to return
503
     * @return int|null   The new ELO, or null if the team provided has not
504
     *                    participated in the match
505
     */
506
    public function getTeamEloNew(Team $team)
507
    {
508
        if ($team->getId() === $this->team_a) {
509
            return $this->getTeamAEloNew();
510
        } elseif ($team->getId() === $this->team_b) {
511
            return $this->getTeamBEloNew();
512
        }
513 1
514
        return null;
515 1
    }
516 1
517 1
    /**
518 1
     * Get the team's old ELO
519
     * @param  Team $team The team whose old ELO to return
520
     * @return int|null   The old ELO, or null if the team provided has not
521
     *                    participated in the match
522
     */
523
    public function getTeamEloOld(Team $team)
524
    {
525
        if ($team->getId() === $this->team_a) {
526
            return $this->getTeamAEloOld();
527
        } elseif ($team->getId() === $this->team_b) {
528 1
            return $this->getTeamBEloOld();
529
        }
530 1
531
        return null;
532
    }
533
534
    /**
535
     * Get the map where the match was played on
536
     * @return Map Returns an invalid map if no map was found
537
     */
538
    public function getMap()
539
    {
540
        return Map::get($this->map);
541
    }
542
543
    /**
544
     * Set the map where the match was played
545
     * @param  int $map The ID of the map
546
     * @return self
547
     */
548 1
    public function setMap($map)
549
    {
550 1
        $this->updateProperty($this->map, "map", $map, "s");
551
    }
552
553
    /**
554
     * Get the match type
555
     *
556
     * @return string 'official', 'fm', or 'special'
557
     */
558
    public function getMatchType()
559
    {
560
        return $this->match_type;
561
    }
562
563
    /**
564
     * Set the match type
565
     *
566
     * @param  string $matchType A valid match type; official, fm, special
567
     *
568
     * @return static
569
     */
570
    public function setMatchType($matchType)
571
    {
572
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
573
    }
574
575
    /**
576
     * Get a JSON decoded array of events that occurred during the match
577
     * @return mixed|null Returns null if there were no events recorded for the match
578
     */
579
    public function getMatchDetails()
580
    {
581
        return json_decode($this->match_details);
582
    }
583
584
    /**
585
     * Get the server address of the server where this match took place
586
     * @return string|null Returns null if there was no server address recorded
587
     */
588
    public function getServerAddress()
589
    {
590
        if ($this->port == null || $this->server == null) {
591
            return null;
592
        }
593
594
        return $this->server . ":" . $this->port;
595
    }
596
597
    /**
598
     * Set the server address of the server where this match took place
599
     *
600
     * @param  string|null $server The server hostname
601
     * @param  int|null    $port   The server port
602
     * @return self
603
     */
604
    public function setServerAddress($server = null, $port = 5154)
605
    {
606
        $this->updateProperty($this->server, "server", $server, "s");
607
        $this->updateProperty($this->port, "port", $port, "i");
608
609
        return $this;
610
    }
611
612
    /**
613
     * Get the name of the replay file for this specific map
614
     * @param  int    $length The length of the replay file name; it will be truncated
615
     * @return string Returns null if there was no replay file name recorded
616
     */
617
    public function getReplayFileName($length = 0)
618
    {
619
        if ($length > 0) {
620 3
            return substr($this->replay_file, 0, $length);
621
        }
622 3
623
        return $this->replay_file;
624
    }
625
626
    /**
627
     * Get the match duration
628
     * @return int The duration of the match in minutes
629
     */
630
    public function getDuration()
631
    {
632
        return $this->duration;
633
    }
634
635
    /**
636
     * Set the match duration
637
     *
638
     * @param  int  $duration The new duration of the match in minutes
639
     * @return self
640 2
     */
641
    public function setDuration($duration)
642 2
    {
643
        return $this->updateProperty($this->duration, "duration", $duration, "i");
644
    }
645
646
    /**
647
     * Get the user who entered the match
648
     * @return Player
649
     */
650 8
    public function getEnteredBy()
651
    {
652
        return Player::get($this->entered_by);
653 8
    }
654
655
    /**
656 8
     * Get the loser of the match
657
     *
658
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
659
     */
660
    public function getLoser()
661
    {
662
        // Get the winner of the match
663
        $winner = $this->getWinner();
664 8
665
        // Get the team that wasn't the winner... Duh
666
        return $this->getOpponent($winner);
0 ignored issues
show
Bug introduced by
It seems like $winner defined by $this->getWinner() on line 663 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...
667 8
    }
668 6
669 2
    /**
670 2
     * Get the winner of a match
671
     *
672
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
673
     */
674
    public function getWinner()
675
    {
676
        // Get the team that had its ELO increased
677
        if ($this->elo_diff > 0) {
678
            return $this->getTeamA();
679
        } elseif ($this->elo_diff < 0) {
680
            return $this->getTeamB();
681 9
        } elseif ($this->team_a_points > $this->team_b_points) {
682
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
683 9
            return $this->getTeamA();
684
        } elseif ($this->team_a_points < $this->team_b_points) {
685
            return $this->getTeamB();
686
        }
687
688
        // If the scores are the same, return Team A because well, fuck you that's why
689
        return $this->getTeamA();
690
    }
691
692 1
    /**
693
     * Determine whether the match was a draw
694 1
     * @return bool True if the match ended without any winning teams
695
     */
696
    public function isDraw()
697
    {
698
        return $this->team_a_points == $this->team_b_points;
699
    }
700
701
    /**
702
     * Find out whether the match involves a team
703
     *
704
     * @param  TeamInterface $team The team to check
705
     * @return bool
706
     */
707
    public function involvesTeam($team)
708
    {
709
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
710
    }
711
712
    /**
713 1
     * Reset the ELOs of the teams participating in the match
714
     *
715 1
     * @return self
716 1
     */
717
    public function resetELOs()
718 1
    {
719
        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...
720 1
            $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...
721
            $this->getTeamB()->changeELO(+$this->elo_diff);
722
        }
723
724 1
        return $this;
725
    }
726
727
    /**
728
     * Calculate the match's contribution to the team activity
729
     *
730
     * @return float
731
     */
732
    public function getActivity()
733
    {
734
        $daysPassed = $this->getTimestamp()->diffInSeconds();
735
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
736
737
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
738
739
        if (is_nan($activity) || $activity < 0.0) {
740
            return 0.0;
741
        }
742
743
        return $activity;
744
    }
745
746
    /**
747 9
     * Enter a new match to the database
748
     * @param  int             $a          Team A's ID
749
     * @param  int             $b          Team B's ID
750
     * @param  int             $a_points   Team A's match points
751
     * @param  int             $b_points   Team B's match points
752
     * @param  int             $duration   The match duration in minutes
753
     * @param  int|null        $entered_by The ID of the player reporting the match
754 9
     * @param  string|DateTime $timestamp  When the match was played
755 9
     * @param  int[]           $a_players  The IDs of the first team's players
756 9
     * @param  int[]           $b_players  The IDs of the second team's players
757 9
     * @param  string|null     $server     The address of the server where the match was played
758 9
     * @param  int|null        $port       The port of the server where the match was played
759 9
     * @param  string          $replayFile The name of the replay file of the match
760 9
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
761 9
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
762 9
     * @param  string          $a_color    Team A's color
763 9
     * @param  string          $b_color    Team b's color
764 9
     * @return Match           An object representing the match that was just entered
765 9
     */
766 9
    public static function enterMatch(
767 9
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
768 9
        $a_players = array(), $b_players = array(), $server = null, $port = null,
769
        $replayFile = null, $map = null, $matchType = "official", $a_color = null,
770 9
        $b_color = null
771
    ) {
772 9
        $matchData = array(
773 9
            'team_a_color'   => strtolower($a_color),
774 9
            'team_b_color'   => strtolower($b_color),
775 9
            'team_a_points'  => $a_points,
776 9
            'team_b_points'  => $b_points,
777
            'team_a_players' => implode(',', $a_players),
778 9
            'team_b_players' => implode(',', $b_players),
779
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
780
            'duration'       => $duration,
781 9
            'entered_by'     => $entered_by,
782 9
            'server'         => $server,
783
            'port'           => $port,
784 9
            'replay_file'    => $replayFile,
785 9
            'map'            => $map,
786 9
            'status'         => 'entered',
787 9
            'match_type'     => $matchType
788 9
        );
789 9
        $matchDataTypes = 'ssiisssiisisiss';
790
791 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...
792
            $team_a = Team::get($a);
793
            $team_b = Team::get($b);
794 9
            $a_elo = $team_a->getElo();
795
            $b_elo = $team_b->getElo();
796 9
797 9
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
798
799
            // Update team ELOs
800 9
            $team_a->changeElo($diff);
801
            $team_b->changeElo(-$diff);
802
803
            $matchData = array_merge($matchData, array(
804
                'team_a'         => $a,
805
                'team_b'         => $b,
806
                'team_a_elo_new' => $team_a->getElo(),
807
                'team_b_elo_new' => $team_b->getElo(),
808
                'elo_diff'       => $diff
809
            ));
810
            $matchDataTypes .= 'iiiii';
811
        }
812
813
        $match = self::create($matchData, $matchDataTypes, 'updated');
814
815
        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...
816 9
            $match->updateMatchCount();
817
        }
818 9
819 9
        return $match;
820 5
    }
821 5
822 4
    /**
823
     * Calculate the ELO score difference
824 1
     *
825
     * Computes the ELO score difference on each team after a match, based on
826
     * GU League's rules.
827
     *
828 9
     * @param  int $a_elo    Team A's current ELO score
829 9
     * @param  int $b_elo    Team B's current ELO score
830
     * @param  int $a_points Team A's match points
831 9
     * @param  int $b_points Team B's match points
832
     * @param  int $duration The match duration in minutes
833 2
     * @return int The ELO score difference
834
     */
835
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
836
    {
837 7
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
838
        if ($a_points > $b_points) {
839
            $diff = 50 * (1 - $prob);
840
        } elseif ($a_points == $b_points) {
841
            $diff = 50 * (0.5 - $prob);
842
        } else {
843
            $diff = 50 * (0 - $prob);
844
        }
845
846
        // Apply ELO modifiers from `config.yml`
847
        $durations = Service::getParameter('bzion.league.duration');
848
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
849
850
        if (abs($diff) < 1 && $diff != 0) {
851
            // ELOs such as 0.75 should round up to 1...
852
            return ($diff > 0) ? 1 : -1;
853
        }
854
855
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
856
        return intval($diff);
857
    }
858
859
    /**
860
     * Find if a match's stored ELO is correct
861
     */
862
    public function isEloCorrect()
863
    {
864
        return $this->elo_diff === $this->calculateEloDiff(
865
            $this->getTeamAEloOld(),
866
            $this->getTeamBEloOld(),
867
            $this->getTeamAPoints(),
868
            $this->getTeamBPoints(),
869
            $this->getDuration()
870
        );
871
    }
872
873
    /**
874
     * Recalculate the match's elo and adjust the team ELO values
875
     */
876
    public function recalculateElo()
877
    {
878
        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...
879
            return;
880
        }
881
882 1
        $a = $this->getTeamA();
883
        $b = $this->getTeamB();
884 1
885
        $elo = $this->calculateEloDiff(
886
            $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...
887
            $b->getElo(),
888
            $this->getTeamAPoints(),
889
            $this->getTeamBPoints(),
890
            $this->getDuration()
891 3
        );
892
893 3
        $this->updateProperty($this->elo_diff, "elo_diff", $elo, "i");
894
895
        $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...
896
        $b->changeElo(-$elo);
897
898
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo(), "i");
899
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo(), "i");
900
    }
901 3
902
    /**
903
     * Get all the matches in the database
904
     */
905
    public static function getMatches()
906
    {
907
        return self::getQueryBuilder()->active()->getModels();
908
    }
909
910
    /**
911
     * Get a query builder for matches
912
     * @return MatchQueryBuilder
913
     */
914
    public static function getQueryBuilder()
915
    {
916
        return new MatchQueryBuilder('Match', array(
917
            'columns' => array(
918 3
                'firstTeam'        => 'team_a',
919
                'secondTeam'       => 'team_b',
920 3
                'firstTeamPoints'  => 'team_a_points',
921
                'secondTeamPoints' => 'team_b_points',
922
                'time'             => 'timestamp',
923
                'status'           => 'status'
924
            ),
925
        ));
926 1
    }
927
928 1
    /**
929 1
     * {@inheritdoc}
930 1
     */
931 1
    public function delete()
932 1
    {
933 1
        $this->updateMatchCount(true);
934
935
        return parent::delete();
936
    }
937
938
    /**
939
     * {@inheritdoc}
940
     */
941
    public static function getActiveStatuses()
942 9
    {
943
        return array('entered');
944 9
    }
945
946 9
    /**
947 4
     * {@inheritdoc}
948 4
     */
949
    public function getName()
950 6
    {
951 6
        return sprintf("(+/- %d) %s [%d] vs [%d] %s",
952
            $this->getEloDiff(),
953 9
            $this->getWinner()->getName(),
954
            $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...
955
            $this->getScore($this->getLoser()),
956
            $this->getLoser()->getName()
957
        );
958
    }
959
960
    /**
961
     * Update the match count of the teams participating in the match
962
     *
963
     * @param bool $decrement Whether to decrement instead of incrementing the match count
964
     */
965
    private function updateMatchCount($decrement = false)
966
    {
967
        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...
968
            return;
969
        }
970
971
        $diff = ($decrement) ? -1 : 1;
972
973
        if ($this->isDraw()) {
974
            $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...
975
            $this->getTeamB()->changeMatchCount($diff, 'draw');
976
        } else {
977
            $this->getWinner()->changeMatchCount($diff, 'win');
978
            $this->getLoser()->changeMatchCount($diff, 'loss');
979
        }
980
    }
981
}
982