Completed
Push — fm-support ( a04a80...17125a )
by Vladimir
08:12 queued 03:08
created

Match::getTeamBPoints()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
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));
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->team_a) {
385 1
            return $this->getTeamAPlayers();
386 1
        } elseif ($team == $this->team_b) {
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
     * Get the ELO difference applied to each team's old ELO
457
     * @return int The ELO difference
458
     */
459 7
    public function getEloDiff()
460
    {
461 7
        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
     */
468 7
    public function getTeamAEloNew()
469
    {
470 7
        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
     */
477 7
    public function getTeamBEloNew()
478
    {
479 7
        return $this->team_b_elo_new;
480
    }
481
482
    /**
483
     * Get the first team's old ELO
484
     * @return int
485
     */
486 6
    public function getTeamAEloOld()
487
    {
488 6
        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 6
    public function getTeamBEloOld()
496
    {
497 6
        return $this->team_b_elo_new + $this->elo_diff;
498
    }
499
500
    /**
501
     * 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 1
    public function getTeamEloNew(Team $team)
507
    {
508 1
        if ($team->getId() === $this->team_a) {
509 1
            return $this->getTeamAEloNew();
510 1
        } elseif ($team->getId() === $this->team_b) {
511 1
            return $this->getTeamBEloNew();
512
        }
513
514
        return null;
515
    }
516
517
    /**
518
     * 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 1
    public function getTeamEloOld(Team $team)
524
    {
525 1
        if ($team->getId() === $this->team_a) {
526 1
            return $this->getTeamAEloOld();
527 1
        } elseif ($team->getId() === $this->team_b) {
528 1
            return $this->getTeamBEloOld();
529
        }
530
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 1
    public function getMap()
539
    {
540 1
        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
    public function setMap($map)
549
    {
550
        $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 1
    public function getMatchType()
559
    {
560 1
        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
            return substr($this->replay_file, 0, $length);
621
        }
622
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 3
    public function getDuration()
631
    {
632 3
        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
     */
641
    public function setDuration($duration)
642
    {
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 2
    public function getEnteredBy()
651
    {
652 2
        return Player::get($this->entered_by);
653
    }
654
655
    /**
656
     * 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 8
    public function getLoser()
661
    {
662
        // Get the winner of the match
663 8
        $winner = $this->getWinner();
664
665
        // Get the team that wasn't the winner... Duh
666 8
        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
    }
668
669
    /**
670
     * 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 8
    public function getWinner()
675
    {
676
        // Get the team that had its ELO increased
677 8
        if ($this->elo_diff > 0) {
678 6
            return $this->getTeamA();
679 2
        } elseif ($this->elo_diff < 0) {
680 2
            return $this->getTeamB();
681
        } 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
            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
    /**
693
     * Determine whether the match was a draw
694
     * @return bool True if the match ended without any winning teams
695
     */
696 9
    public function isDraw()
697
    {
698 9
        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 1
    public function involvesTeam($team)
708
    {
709 1
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
710
    }
711
712
    /**
713
     * Reset the ELOs of the teams participating in the match
714
     *
715
     * @return self
716
     */
717
    public function resetELOs()
718
    {
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
            $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
        return $this;
725
    }
726
727
    /**
728
     * Calculate the match's contribution to the team activity
729
     *
730
     * @return float
731
     */
732 1
    public function getActivity()
733
    {
734 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
735 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
736
737 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
738
739 1
        if (is_nan($activity) || $activity < 0.0) {
740
            return 0.0;
741
        }
742
743 1
        return $activity;
744
    }
745
746
    /**
747
     * 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
     * @param  string|DateTime $timestamp  When the match was played
755
     * @param  int[]           $a_players  The IDs of the first team's players
756
     * @param  int[]           $b_players  The IDs of the second team's players
757
     * @param  string|null     $server     The address of the server where the match was played
758
     * @param  int|null        $port       The port of the server where the match was played
759
     * @param  string          $replayFile The name of the replay file of the match
760
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
761
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
762
     * @param  string          $a_color    Team A's color
763
     * @param  string          $b_color    Team b's color
764
     * @return Match           An object representing the match that was just entered
765
     */
766 9
    public static function enterMatch(
767
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
768
        $a_players = array(), $b_players = array(), $server = null, $port = null,
769
        $replayFile = null, $map = null, $matchType = "official", $a_color = null,
770
        $b_color = null
771
    ) {
772
        $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 9
            'team_a_players' => implode(',', $a_players),
778 9
            'team_b_players' => implode(',', $b_players),
779 9
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
780 9
            'duration'       => $duration,
781 9
            'entered_by'     => $entered_by,
782 9
            'server'         => $server,
783 9
            'port'           => $port,
784 9
            'replay_file'    => $replayFile,
785 9
            'map'            => $map,
786 9
            'status'         => 'entered',
787 9
            'match_type'     => $matchType
788
        );
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 9
            $team_a = Team::get($a);
793 9
            $team_b = Team::get($b);
794 9
            $a_elo = $team_a->getElo();
795 9
            $b_elo = $team_b->getElo();
796
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 9
            $team_b->changeElo(-$diff);
802
803 9
            $matchData = array_merge($matchData, array(
804 9
                'team_a'         => $a,
805 9
                'team_b'         => $b,
806 9
                'team_a_elo_new' => $team_a->getElo(),
807 9
                'team_b_elo_new' => $team_b->getElo(),
808 9
                'elo_diff'       => $diff
809
            ));
810 9
            $matchDataTypes .= 'iiiii';
811
        }
812
813 9
        $match = self::create($matchData, $matchDataTypes, 'updated');
814
815 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...
816 9
            $match->updateMatchCount();
817
        }
818
819 9
        return $match;
820
    }
821
822
    /**
823
     * Calculate the ELO score difference
824
     *
825
     * Computes the ELO score difference on each team after a match, based on
826
     * GU League's rules.
827
     *
828
     * @param  int $a_elo    Team A's current ELO score
829
     * @param  int $b_elo    Team B's current ELO score
830
     * @param  int $a_points Team A's match points
831
     * @param  int $b_points Team B's match points
832
     * @param  int $duration The match duration in minutes
833
     * @return int The ELO score difference
834
     */
835 9
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
836
    {
837 9
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
838 9
        if ($a_points > $b_points) {
839 5
            $diff = 50 * (1 - $prob);
840 5
        } elseif ($a_points == $b_points) {
841 4
            $diff = 50 * (0.5 - $prob);
842
        } else {
843 1
            $diff = 50 * (0 - $prob);
844
        }
845
846
        // Apply ELO modifiers from `config.yml`
847 9
        $durations = Service::getParameter('bzion.league.duration');
848 9
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
849
850 9
        if (abs($diff) < 1 && $diff != 0) {
851
            // ELOs such as 0.75 should round up to 1...
852 2
            return ($diff > 0) ? 1 : -1;
853
        }
854
855
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
856 7
        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
        $a = $this->getTeamA();
883
        $b = $this->getTeamB();
884
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
        );
892
893
        $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
902
    /**
903
     * Get all the matches in the database
904
     */
905 1
    public static function getMatches()
906
    {
907 1
        return self::getQueryBuilder()->active()->getModels();
908
    }
909
910
    /**
911
     * Get a query builder for matches
912
     * @return MatchQueryBuilder
913
     */
914 3
    public static function getQueryBuilder()
915
    {
916 3
        return new MatchQueryBuilder('Match', array(
917
            'columns' => array(
918
                'firstTeam'        => 'team_a',
919
                'secondTeam'       => 'team_b',
920
                'firstTeamPoints'  => 'team_a_points',
921
                'secondTeamPoints' => 'team_b_points',
922
                'time'             => 'timestamp',
923
                'status'           => 'status'
924 3
            ),
925
        ));
926
    }
927
928
    /**
929
     * {@inheritdoc}
930
     */
931
    public function delete()
932
    {
933
        $this->updateMatchCount(true);
934
935
        return parent::delete();
936
    }
937
938
    /**
939
     * {@inheritdoc}
940
     */
941 3
    public static function getActiveStatuses()
942
    {
943 3
        return array('entered');
944
    }
945
946
    /**
947
     * {@inheritdoc}
948
     */
949 1
    public function getName()
950
    {
951 1
        return sprintf("(+/- %d) %s [%d] vs [%d] %s",
952 1
            $this->getEloDiff(),
953 1
            $this->getWinner()->getName(),
954 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...
955 1
            $this->getScore($this->getLoser()),
956 1
            $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 9
    private function updateMatchCount($decrement = false)
966
    {
967 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...
968
            return;
969
        }
970
971 9
        $diff = ($decrement) ? -1 : 1;
972
973 9
        if ($this->isDraw()) {
974 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...
975 4
            $this->getTeamB()->changeMatchCount($diff, 'draw');
976
        } else {
977 6
            $this->getWinner()->changeMatchCount($diff, 'win');
978 6
            $this->getLoser()->changeMatchCount($diff, 'loss');
979
        }
980 9
    }
981
}
982