Completed
Push — fm-support ( 3b5d1b...2f8546 )
by Konstantinos
12:56
created

Match::getRouteName()   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 1
crap 1
1
<?php
2
/**
3
 * This file contains functionality relating to the official matches played in the league
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
use BZIon\Model\Column\Timestamp;
9
10
/**
11
 * A match played between two teams
12
 * @package    BZiON\Models
13
 */
14
class Match extends UrlModel implements NamedModel
15
{
16
    const OFFICIAL = "official";
17
    const SPECIAL  = "special";
18
    const FUN      = "fm";
19
20
    use Timestamp;
21
22
    /**
23
     * The ID of the first team of the match
24
     * @var int
25
     */
26
    protected $team_a;
27
28
    /**
29
     * The ID of the second team of the match
30
     * @var int
31
     */
32
    protected $team_b;
33
34
    /**
35
     * The color of the first team
36
     * @var string
37
     */
38
    protected $team_a_color;
39
40
    /**
41
     * The color of the second team
42
     * @var string
43
     */
44
    protected $team_b_color;
45
46
    /**
47
     * The match points (usually the number of flag captures) Team A scored
48
     * @var int
49
     */
50
    protected $team_a_points;
51
52
    /**
53
     * The match points Team B scored
54
     * @var int
55
     */
56
    protected $team_b_points;
57
58
    /**
59
     * The BZIDs of players part of Team A who participated in the match, separated by commas
60
     * @var string
61
     */
62
    protected $team_a_players;
63
64
    /**
65
     * The BZIDs of players part of Team B who participated in the match, separated by commas
66
     * @var string
67
     */
68
    protected $team_b_players;
69
70
    /**
71
     * The ELO score of Team A after the match
72
     * @var int
73
     */
74
    protected $team_a_elo_new;
75
76
    /**
77
     * The ELO score of Team B after the match
78
     * @var int
79
     */
80
    protected $team_b_elo_new;
81
82
    /**
83
     * The map ID used in the match if the league supports more than one map
84
     * @var int
85
     */
86
    protected $map;
87
88
    /**
89
     * The type of match that occurred. Valid options: official, fm, special
90
     *
91
     * @var string
92
     */
93
    protected $match_type;
94
95
    /**
96
     * A JSON string of events that happened during a match, such as captures and substitutions
97
     * @var string
98
     */
99
    protected $match_details;
100
101
    /**
102
     * The port of the server where the match took place
103
     * @var int
104
     */
105
    protected $port;
106
107
    /**
108
     * The server location of there the match took place
109
     * @var string
110
     */
111
    protected $server;
112
113
    /**
114
     * The file name of the replay file of the match
115
     * @var string
116
     */
117
    protected $replay_file;
118
119
    /**
120
     * The absolute value of the ELO score difference
121
     * @var int
122
     */
123
    protected $elo_diff;
124
125
    /**
126
     * The timestamp representing when the match information was last updated
127
     * @var TimeDate
128
     */
129
    protected $updated;
130
131
    /**
132
     * The duration of the match in minutes
133
     * @var int
134
     */
135
    protected $duration;
136
137
    /**
138
     * The ID of the person (i.e. referee) who last updated the match information
139
     * @var string
140
     */
141
    protected $entered_by;
142
143
    /**
144
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
145
     * @var string
146
     */
147
    protected $status;
148
149
    /**
150
     * The name of the database table used for queries
151
     */
152
    const TABLE = "matches";
153
154
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
155
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
156
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
157
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 9
    protected function assignResult($match)
163
    {
164 9
        $this->team_a = $match['team_a'];
165 9
        $this->team_b = $match['team_b'];
166 9
        $this->team_a_color = $match['team_a_color'];
167 9
        $this->team_b_color = $match['team_b_color'];
168 9
        $this->team_a_points = $match['team_a_points'];
169 9
        $this->team_b_points = $match['team_b_points'];
170 9
        $this->team_a_players = $match['team_a_players'];
171 9
        $this->team_b_players = $match['team_b_players'];
172 9
        $this->team_a_elo_new = $match['team_a_elo_new'];
173 9
        $this->team_b_elo_new = $match['team_b_elo_new'];
174 9
        $this->map = $match['map'];
175 9
        $this->match_type = $match['match_type'];
176 9
        $this->match_details = $match['match_details'];
177 9
        $this->port = $match['port'];
178 9
        $this->server = $match['server'];
179 9
        $this->replay_file = $match['replay_file'];
180 9
        $this->elo_diff = $match['elo_diff'];
181 9
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
182 9
        $this->updated = TimeDate::fromMysql($match['updated']);
183 9
        $this->duration = $match['duration'];
184 9
        $this->entered_by = $match['entered_by'];
185 9
        $this->status = $match['status'];
186 9
    }
187
188
    /**
189
     * Get a one word description of a match relative to a team (i.e. win, loss, or draw)
190
     *
191
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
192
     *
193
     * @return string Either "win", "loss", or "draw" relative to the team
194
     */
195 1
    public function getMatchDescription($teamID)
196
    {
197 1
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
198 1
            return "win";
199 1
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
200 1
            return "loss";
201
        }
202
203 1
        return "tie";
204
    }
205
206
    /**
207
     * Get a one letter description of a match relative to a team (i.e. W, L, or T)
208
     *
209
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
210
     *
211
     * @return string Either "W", "L", or "T" relative to the team
212
     */
213 1
    public function getMatchLetter($teamID)
214
    {
215 1
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
216
    }
217
218
    /**
219
     * Get the score of a specific team
220
     *
221
     * @param int|string|TeamInterface $teamID The team we want the score for
222
     *
223
     * @return int The score that team received
224
     */
225 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...
226
    {
227 2
        if ($teamID instanceof TeamInterface) {
228
            // Oh no! The caller gave us a Team model instead of an ID!
229 2
            $teamID = $teamID->getId();
230
        } elseif (is_string($teamID)) {
231
            // Make sure we're comparing lowercase strings
232
            $teamID = strtolower($teamID);
233
        }
234
235
236 2
        if ($this->getTeamA()->getId() == $teamID) {
237 2
            return $this->getTeamAPoints();
238
        }
239
240 2
        return $this->getTeamBPoints();
241
    }
242
243
    /**
244
     * Get the score of the opponent relative to a team
245
     *
246
     * @param int|string|TeamInterface $teamID The opponent of the team we want the score for
247
     *
248
     * @return int The score of the opponent
249
     */
250 2
    public function getOpponentScore($teamID)
251
    {
252 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...
253
    }
254
255
    /**
256
     * Get the opponent of a match relative to a team ID
257
     *
258
     * @param int|string|TeamInterface $teamID The team who is known in a match
259
     *
260
     * @return TeamInterface The opponent team
261
     */
262 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...
263
    {
264 8
        if ($teamID instanceof TeamInterface) {
265 8
            $teamID = $teamID->getId();
266
        } elseif (is_string($teamID)) {
267
            $teamID = strtolower($teamID);
268
        }
269
270 8
        if ($this->getTeamA()->getId() == $teamID) {
271 6
            return $this->getTeamB();
272
        }
273
274 4
        return $this->getTeamA();
275
    }
276
277
    /**
278
     * Get the timestamp of the last update of the match
279
     *
280
     * @return TimeDate The match's update timestamp
281
     */
282 1
    public function getUpdated()
283
    {
284 1
        return $this->updated->copy();
285
    }
286
287
    /**
288
     * Set the timestamp of the match
289
     *
290
     * @param  mixed $timestamp The match's new timestamp
291
     * @return $this
292
     */
293
    public function setTimestamp($timestamp)
294
    {
295
        $this->timestamp = TimeDate::from($timestamp);
296
        $this->update("timestamp", $this->timestamp->toMysql(), "s");
297
298
        return $this;
299
    }
300
301
    /**
302
     * Get the first team involved in the match
303
     * @return TeamInterface Team A
304
     */
305 9
    public function getTeamA()
306
    {
307 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...
308 9
            return Team::get($this->team_a);
309
        }
310
311
        return new ColorTeam($this->team_a_color);
312
    }
313
314
    /**
315
     * Get the second team involved in the match
316
     * @return TeamInterface Team B
317
     */
318 9
    public function getTeamB()
319
    {
320 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...
321 9
            return Team::get($this->team_b);
322
        }
323
324
        return new ColorTeam($this->team_b_color);
325
    }
326
327
    /**
328
     * Get the color of Team A
329
     * @return string
330
     */
331
    public function getTeamAColor()
332
    {
333
        return $this->team_a_color;
334
    }
335
336
    /**
337
     * Get the color of Team B
338
     * @return string
339
     */
340
    public function getTeamBColor()
341
    {
342
        return $this->team_b_color;
343
    }
344
345
    /**
346
     * Get the list of players on Team A who participated in this match
347
     * @return Player[]|null Returns null if there were no players recorded for this match
348
     */
349 1
    public function getTeamAPlayers()
350
    {
351 1
        return $this->parsePlayers($this->team_a_players);
352
    }
353
354
    /**
355
     * Get the list of players on Team B who participated in this match
356
     * @return Player[]|null Returns null if there were no players recorded for this match
357
     */
358 1
    public function getTeamBPlayers()
359
    {
360 1
        return $this->parsePlayers($this->team_b_players);
361
    }
362
363
    /**
364
     * Get the list of players for a team in a match
365
     * @param  Team|int The team or team ID
366
     * @return Player[]|null Returns null if there were no players recorded for this match
367
     */
368 1
    public function getPlayers($team)
369
    {
370 1
        if ($team instanceof TeamInterface) {
371 1
            $team = $team->getId();
372
        }
373
374 1
        if ($team == $this->getTeamA()->getId()) {
375 1
            return $this->getTeamAPlayers();
376 1
        } elseif ($team == $this->getTeamB()->getId()) {
377 1
            return $this->getTeamBPlayers();
378
        }
379
380
        return null;
381
    }
382
383
    /**
384
     * Set the players of the match's teams
385
     *
386
     * @param int[] $teamAPlayers An array of player IDs
387
     * @param int[] $teamBPlayers An array of player IDs
388
     * @return self
389
     */
390
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
391
    {
392
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers), "s");
393
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers), "s");
394
395
        return $this;
396
    }
397
398
    /**
399
     * Get an array of players based on a string representation
400
     * @param string $playerString
401
     * @return Player[]|null Returns null if there were no players recorded for this match
402
     */
403 1
    private static function parsePlayers($playerString)
404
    {
405 1
        if ($playerString == null) {
406 1
            return null;
407
        }
408
409
        return Player::arrayIdToModel(explode(",", $playerString));
410
    }
411
412
    /**
413
     * Get the first team's points
414
     * @return int Team A's points
415
     */
416 4
    public function getTeamAPoints()
417
    {
418 4
        return $this->team_a_points;
419
    }
420
421
    /**
422
     * Get the second team's points
423
     * @return int Team B's points
424
     */
425 4
    public function getTeamBPoints()
426
    {
427 4
        return $this->team_b_points;
428
    }
429
430
    /**
431
     * Set the match team points
432
     *
433
     * @param  int $teamAPoints Team A's points
434
     * @param  int $teamBPoints Team B's points
435
     * @return self
436
     */
437
    public function setTeamPoints($teamAPoints, $teamBPoints)
438
    {
439
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints, "i");
440
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints, "i");
441
442
        return $this;
443
    }
444
445
    /**
446
     * Set the match team colors
447
     *
448
     * @param  ColorTeam|string $teamAColor The color of team A
449
     * @param  ColorTeam|string $teamBColor The color of team B
450
     * @return self
451
     */
452
    public function setTeamColors($teamAColor, $teamBColor)
453
    {
454
        if ($this->isOfficial()) {
455
            throw new \Exception("Cannot change team colors in an official match");
456
        }
457
458
        if ($teamAColor instanceof ColorTeam) {
459
            $teamAColor = $teamAColor->getId();
460
        }
461
        if ($teamBColor instanceof ColorTeam) {
462
            $teamBColor = $teamBColor->getId();
463
        }
464
465
        $this->updateProperty($this->team_a_color, "team_a_color", $teamAColor, "s");
466
        $this->updateProperty($this->team_b_color, "team_b_color", $teamBColor, "s");
467
    }
468
469
    /**
470
     * Get the ELO difference applied to each team's old ELO
471
     * @return int The ELO difference
472
     */
473 7
    public function getEloDiff()
474
    {
475 7
        return abs($this->elo_diff);
476
    }
477
478
    /**
479
     * Get the first team's new ELO
480
     * @return int Team A's new ELO
481
     */
482 7
    public function getTeamAEloNew()
483
    {
484 7
        return $this->team_a_elo_new;
485
    }
486
487
    /**
488
     * Get the second team's new ELO
489
     * @return int Team B's new ELO
490
     */
491 7
    public function getTeamBEloNew()
492
    {
493 7
        return $this->team_b_elo_new;
494
    }
495
496
    /**
497
     * Get the first team's old ELO
498
     * @return int
499
     */
500 6
    public function getTeamAEloOld()
501
    {
502 6
        return $this->team_a_elo_new - $this->elo_diff;
503
    }
504
505
    /**
506
     * Get the second team's old ELO
507
     * @return int
508
     */
509 6
    public function getTeamBEloOld()
510
    {
511 6
        return $this->team_b_elo_new + $this->elo_diff;
512
    }
513
514
    /**
515
     * Get the team's new ELO
516
     * @param  Team $team The team whose new ELO to return
517
     * @return int|null   The new ELO, or null if the team provided has not
518
     *                    participated in the match
519
     */
520 1
    public function getTeamEloNew(Team $team)
521
    {
522 1
        if ($team->getId() === $this->team_a) {
523 1
            return $this->getTeamAEloNew();
524 1
        } elseif ($team->getId() === $this->team_b) {
525 1
            return $this->getTeamBEloNew();
526
        }
527
528
        return null;
529
    }
530
531
    /**
532
     * Get the team's old ELO
533
     * @param  Team $team The team whose old ELO to return
534
     * @return int|null   The old ELO, or null if the team provided has not
535
     *                    participated in the match
536
     */
537 1
    public function getTeamEloOld(Team $team)
538
    {
539 1
        if ($team->getId() === $this->team_a) {
540 1
            return $this->getTeamAEloOld();
541 1
        } elseif ($team->getId() === $this->team_b) {
542 1
            return $this->getTeamBEloOld();
543
        }
544
545
        return null;
546
    }
547
548
    /**
549
     * Get the map where the match was played on
550
     * @return Map Returns an invalid map if no map was found
551
     */
552 1
    public function getMap()
553
    {
554 1
        return Map::get($this->map);
555
    }
556
557
    /**
558
     * Set the map where the match was played
559
     * @param  int $map The ID of the map
560
     * @return self
561
     */
562
    public function setMap($map)
563
    {
564
        $this->updateProperty($this->map, "map", $map, "s");
565
    }
566
567
    /**
568
     * Get the match type
569
     *
570
     * @return string 'official', 'fm', or 'special'
571
     */
572 1
    public function getMatchType()
573
    {
574 1
        return $this->match_type;
575
    }
576
577
    /**
578
     * Set the match type
579
     *
580
     * @param  string $matchType A valid match type; official, fm, special
581
     *
582
     * @return static
583
     */
584
    public function setMatchType($matchType)
585
    {
586
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
587
    }
588
589
    /**
590
     * Get a JSON decoded array of events that occurred during the match
591
     * @return mixed|null Returns null if there were no events recorded for the match
592
     */
593
    public function getMatchDetails()
594
    {
595
        return json_decode($this->match_details);
596
    }
597
598
    /**
599
     * Get the server address of the server where this match took place
600
     * @return string|null Returns null if there was no server address recorded
601
     */
602
    public function getServerAddress()
603
    {
604
        if ($this->port == null || $this->server == null) {
605
            return null;
606
        }
607
608
        return $this->server . ":" . $this->port;
609
    }
610
611
    /**
612
     * Set the server address of the server where this match took place
613
     *
614
     * @param  string|null $server The server hostname
615
     * @param  int|null    $port   The server port
616
     * @return self
617
     */
618
    public function setServerAddress($server = null, $port = 5154)
619
    {
620
        $this->updateProperty($this->server, "server", $server, "s");
621
        $this->updateProperty($this->port, "port", $port, "i");
622
623
        return $this;
624
    }
625
626
    /**
627
     * Get the name of the replay file for this specific map
628
     * @param  int    $length The length of the replay file name; it will be truncated
629
     * @return string Returns null if there was no replay file name recorded
630
     */
631
    public function getReplayFileName($length = 0)
632
    {
633
        if ($length > 0) {
634
            return substr($this->replay_file, 0, $length);
635
        }
636
637
        return $this->replay_file;
638
    }
639
640
    /**
641
     * Get the match duration
642
     * @return int The duration of the match in minutes
643
     */
644 3
    public function getDuration()
645
    {
646 3
        return $this->duration;
647
    }
648
649
    /**
650
     * Set the match duration
651
     *
652
     * @param  int  $duration The new duration of the match in minutes
653
     * @return self
654
     */
655
    public function setDuration($duration)
656
    {
657
        return $this->updateProperty($this->duration, "duration", $duration, "i");
658
    }
659
660
    /**
661
     * Get the user who entered the match
662
     * @return Player
663
     */
664 2
    public function getEnteredBy()
665
    {
666 2
        return Player::get($this->entered_by);
667
    }
668
669
    /**
670
     * Get the loser of the match
671
     *
672
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
673
     */
674 8
    public function getLoser()
675
    {
676
        // Get the winner of the match
677 8
        $winner = $this->getWinner();
678
679
        // Get the team that wasn't the winner... Duh
680 8
        return $this->getOpponent($winner);
0 ignored issues
show
Bug introduced by
It seems like $winner defined by $this->getWinner() on line 677 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...
681
    }
682
683
    /**
684
     * Get the winner of a match
685
     *
686
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
687
     */
688 8
    public function getWinner()
689
    {
690
        // Get the team that had its ELO increased
691 8
        if ($this->elo_diff > 0) {
692 6
            return $this->getTeamA();
693 2
        } elseif ($this->elo_diff < 0) {
694 2
            return $this->getTeamB();
695
        } elseif ($this->team_a_points > $this->team_b_points) {
696
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
697
            return $this->getTeamA();
698
        } elseif ($this->team_a_points < $this->team_b_points) {
699
            return $this->getTeamB();
700
        }
701
702
        // If the scores are the same, return Team A because well, fuck you that's why
703
        return $this->getTeamA();
704
    }
705
706
    /**
707
     * Determine whether the match was a draw
708
     * @return bool True if the match ended without any winning teams
709
     */
710 9
    public function isDraw()
711
    {
712 9
        return $this->team_a_points == $this->team_b_points;
713
    }
714
715
    /**
716
     * Find out whether the match involves a team
717
     *
718
     * @param  TeamInterface $team The team to check
719
     * @return bool
720
     */
721 1
    public function involvesTeam($team)
722
    {
723 1
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
724
    }
725
726
    /**
727
     * Find out if the match is played between official teams
728
     */
729 1
    public function isOfficial()
730
    {
731 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...
732
    }
733
734
    /**
735
     * Reset the ELOs of the teams participating in the match
736
     *
737
     * @return self
738
     */
739
    public function resetELOs()
740
    {
741
        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...
742
            $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...
743
            $this->getTeamB()->changeELO(+$this->elo_diff);
744
        }
745
746
        return $this;
747
    }
748
749
    /**
750
     * Calculate the match's contribution to the team activity
751
     *
752
     * @return float
753
     */
754 1
    public function getActivity()
755
    {
756 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
757 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
758
759 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
760
761 1
        if (is_nan($activity) || $activity < 0.0) {
762
            return 0.0;
763
        }
764
765 1
        return $activity;
766
    }
767
768
    /**
769
     * Enter a new match to the database
770
     * @param  int             $a          Team A's ID
771
     * @param  int             $b          Team B's ID
772
     * @param  int             $a_points   Team A's match points
773
     * @param  int             $b_points   Team B's match points
774
     * @param  int             $duration   The match duration in minutes
775
     * @param  int|null        $entered_by The ID of the player reporting the match
776
     * @param  string|DateTime $timestamp  When the match was played
777
     * @param  int[]           $a_players  The IDs of the first team's players
778
     * @param  int[]           $b_players  The IDs of the second team's players
779
     * @param  string|null     $server     The address of the server where the match was played
780
     * @param  int|null        $port       The port of the server where the match was played
781
     * @param  string          $replayFile The name of the replay file of the match
782
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
783
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
784
     * @param  string          $a_color    Team A's color
785
     * @param  string          $b_color    Team b's color
786
     * @return Match           An object representing the match that was just entered
787
     */
788 9
    public static function enterMatch(
789
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
790
        $a_players = array(), $b_players = array(), $server = null, $port = null,
791
        $replayFile = null, $map = null, $matchType = "official", $a_color = null,
792
        $b_color = null
793
    ) {
794
        $matchData = array(
795 9
            'team_a_color'   => strtolower($a_color),
796 9
            'team_b_color'   => strtolower($b_color),
797 9
            'team_a_points'  => $a_points,
798 9
            'team_b_points'  => $b_points,
799 9
            'team_a_players' => implode(',', $a_players),
800 9
            'team_b_players' => implode(',', $b_players),
801 9
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
802 9
            'duration'       => $duration,
803 9
            'entered_by'     => $entered_by,
804 9
            'server'         => $server,
805 9
            'port'           => $port,
806 9
            'replay_file'    => $replayFile,
807 9
            'map'            => $map,
808 9
            'status'         => 'entered',
809 9
            'match_type'     => $matchType
810
        );
811 9
        $matchDataTypes = 'ssiisssiisisiss';
812
813 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...
814 9
            $team_a = Team::get($a);
815 9
            $team_b = Team::get($b);
816 9
            $a_elo = $team_a->getElo();
817 9
            $b_elo = $team_b->getElo();
818
819 9
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
820
821
            // Update team ELOs
822 9
            $team_a->changeElo($diff);
823 9
            $team_b->changeElo(-$diff);
824
825 9
            $matchData = array_merge($matchData, array(
826 9
                'team_a'         => $a,
827 9
                'team_b'         => $b,
828 9
                'team_a_elo_new' => $team_a->getElo(),
829 9
                'team_b_elo_new' => $team_b->getElo(),
830 9
                'elo_diff'       => $diff
831
            ));
832 9
            $matchDataTypes .= 'iiiii';
833
        }
834
835 9
        $match = self::create($matchData, $matchDataTypes, 'updated');
836
837 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...
838 9
            $match->updateMatchCount();
839
        }
840
841 9
        return $match;
842
    }
843
844
    /**
845
     * Calculate the ELO score difference
846
     *
847
     * Computes the ELO score difference on each team after a match, based on
848
     * GU League's rules.
849
     *
850
     * @param  int $a_elo    Team A's current ELO score
851
     * @param  int $b_elo    Team B's current ELO score
852
     * @param  int $a_points Team A's match points
853
     * @param  int $b_points Team B's match points
854
     * @param  int $duration The match duration in minutes
855
     * @return int The ELO score difference
856
     */
857 9
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
858
    {
859 9
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
860 9
        if ($a_points > $b_points) {
861 5
            $diff = 50 * (1 - $prob);
862 5
        } elseif ($a_points == $b_points) {
863 4
            $diff = 50 * (0.5 - $prob);
864
        } else {
865 1
            $diff = 50 * (0 - $prob);
866
        }
867
868
        // Apply ELO modifiers from `config.yml`
869 9
        $durations = Service::getParameter('bzion.league.duration');
870 9
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
871
872 9
        if (abs($diff) < 1 && $diff != 0) {
873
            // ELOs such as 0.75 should round up to 1...
874 2
            return ($diff > 0) ? 1 : -1;
875
        }
876
877
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
878 7
        return intval($diff);
879
    }
880
881
    /**
882
     * Find if a match's stored ELO is correct
883
     */
884
    public function isEloCorrect()
885
    {
886
        return $this->elo_diff === $this->calculateEloDiff(
887
            $this->getTeamAEloOld(),
888
            $this->getTeamBEloOld(),
889
            $this->getTeamAPoints(),
890
            $this->getTeamBPoints(),
891
            $this->getDuration()
892
        );
893
    }
894
895
    /**
896
     * Recalculate the match's elo and adjust the team ELO values
897
     */
898
    public function recalculateElo()
899
    {
900
        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...
901
            return;
902
        }
903
904
        $a = $this->getTeamA();
905
        $b = $this->getTeamB();
906
907
        $elo = $this->calculateEloDiff(
908
            $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...
909
            $b->getElo(),
910
            $this->getTeamAPoints(),
911
            $this->getTeamBPoints(),
912
            $this->getDuration()
913
        );
914
915
        $this->updateProperty($this->elo_diff, "elo_diff", $elo, "i");
916
917
        $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...
918
        $b->changeElo(-$elo);
919
920
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo(), "i");
921
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo(), "i");
922
    }
923
924
    /**
925
     * Get all the matches in the database
926
     */
927 1
    public static function getMatches()
928
    {
929 1
        return self::getQueryBuilder()->active()->getModels();
930
    }
931
932
    /**
933
     * Get a query builder for matches
934
     * @return MatchQueryBuilder
935
     */
936 3
    public static function getQueryBuilder()
937
    {
938 3
        return new MatchQueryBuilder('Match', array(
939
            'columns' => array(
940
                'firstTeam'        => 'team_a',
941
                'secondTeam'       => 'team_b',
942
                'firstTeamPoints'  => 'team_a_points',
943
                'secondTeamPoints' => 'team_b_points',
944
                'time'             => 'timestamp',
945
                'type'             => 'match_type',
946
                'status'           => 'status'
947 3
            ),
948
        ));
949
    }
950
951
    /**
952
     * {@inheritdoc}
953
     */
954
    public function delete()
955
    {
956
        $this->updateMatchCount(true);
957
958
        return parent::delete();
959
    }
960
961
    /**
962
     * {@inheritdoc}
963
     */
964 3
    public static function getActiveStatuses()
965
    {
966 3
        return array('entered');
967
    }
968
969
    /**
970
     * {@inheritdoc}
971
     */
972 1
    public function getName()
973
    {
974 1
        return sprintf("(+/- %d) %s [%d] vs [%d] %s",
975 1
            $this->getEloDiff(),
976 1
            $this->getWinner()->getName(),
977 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...
978 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...
979 1
            $this->getLoser()->getName()
980
        );
981
    }
982
983
    /**
984
     * Update the match count of the teams participating in the match
985
     *
986
     * @param bool $decrement Whether to decrement instead of incrementing the match count
987
     */
988 9
    private function updateMatchCount($decrement = false)
989
    {
990 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...
991
            return;
992
        }
993
994 9
        $diff = ($decrement) ? -1 : 1;
995
996 9
        if ($this->isDraw()) {
997 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...
998 4
            $this->getTeamB()->changeMatchCount($diff, 'draw');
999
        } else {
1000 6
            $this->getWinner()->changeMatchCount($diff, 'win');
1001 6
            $this->getLoser()->changeMatchCount($diff, 'loss');
1002
        }
1003 9
    }
1004
}
1005