Completed
Push — feature/player-elo ( 4c021c...dc0573 )
by Vladimir
07:19
created

Match::enterMatch()   C

Complexity

Conditions 11
Paths 51

Size

Total Lines 88
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 56
CRAP Score 11.0006

Importance

Changes 0
Metric Value
dl 0
loc 88
ccs 56
cts 57
cp 0.9825
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 54
nc 51
nop 15
crap 11.0006

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 server location of there the match took place
103
     * @var string
104
     */
105
    protected $server;
106
107
    /**
108
     * The file name of the replay file of the match
109
     * @var string
110
     */
111
    protected $replay_file;
112
113
    /**
114
     * The value of the ELO score difference
115
     * @var int
116
     */
117
    protected $elo_diff;
118
119
    /**
120
     * The value of the player Elo difference
121
     * @var int
122
     */
123
    protected $player_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 14
    protected function assignResult($match)
163
    {
164 14
        $this->team_a = $match['team_a'];
165 14
        $this->team_b = $match['team_b'];
166 14
        $this->team_a_color = $match['team_a_color'];
167 14
        $this->team_b_color = $match['team_b_color'];
168 14
        $this->team_a_points = $match['team_a_points'];
169 14
        $this->team_b_points = $match['team_b_points'];
170 14
        $this->team_a_players = $match['team_a_players'];
171 14
        $this->team_b_players = $match['team_b_players'];
172 14
        $this->team_a_elo_new = $match['team_a_elo_new'];
173 14
        $this->team_b_elo_new = $match['team_b_elo_new'];
174 14
        $this->map = $match['map'];
175 14
        $this->match_type = $match['match_type'];
176 14
        $this->match_details = $match['match_details'];
177 14
        $this->server = $match['server'];
178 14
        $this->replay_file = $match['replay_file'];
179 14
        $this->elo_diff = $match['elo_diff'];
180 14
        $this->player_elo_diff = $match['player_elo_diff'];
181 14
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
182 14
        $this->updated = TimeDate::fromMysql($match['updated']);
183 14
        $this->duration = $match['duration'];
184 14
        $this->entered_by = $match['entered_by'];
185 14
        $this->status = $match['status'];
186 14
    }
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 3 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 3
        if ($teamID instanceof TeamInterface) {
238
            // Oh no! The caller gave us a Team model instead of an ID!
239 3
            $teamID = $teamID->getId();
240 3
        } elseif (is_string($teamID)) {
241
            // Make sure we're comparing lowercase strings
242
            $teamID = strtolower($teamID);
243
        }
244
245 3
        if ($this->getTeamA()->getId() == $teamID) {
246 3
            return $this->getTeamAPoints();
247
        }
248
249 3
        return $this->getTeamBPoints();
250
    }
251
252
    /**
253
     * Get the score of the opponent relative to a team
254
     *
255
     * @param int|string|TeamInterface $teamID The opponent of the team we want the score for
256
     *
257
     * @return int The score of the opponent
258
     */
259 2
    public function getOpponentScore($teamID)
260
    {
261 2
        return $this->getScore($this->getOpponent($teamID));
262
    }
263
264
    /**
265
     * Get the opponent of a match relative to a team ID
266
     *
267
     * @param int|string|TeamInterface $teamID The team who is known in a match
268
     *
269
     * @return TeamInterface The opponent team
270
     */
271 12 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...
272
    {
273 12
        if ($teamID instanceof TeamInterface) {
274 12
            $teamID = $teamID->getId();
275 12
        } elseif (is_string($teamID)) {
276
            $teamID = strtolower($teamID);
277
        }
278
279 12
        if ($this->getTeamA()->getId() == $teamID) {
280 9
            return $this->getTeamB();
281
        }
282
283 5
        return $this->getTeamA();
284
    }
285
286
    /**
287
     * Get the timestamp of the last update of the match
288
     *
289
     * @return TimeDate The match's update timestamp
290
     */
291 1
    public function getUpdated()
292
    {
293 1
        return $this->updated->copy();
294
    }
295
296
    /**
297
     * Set the timestamp of the match
298
     *
299
     * @param  mixed $timestamp The match's new timestamp
300
     * @return $this
301
     */
302
    public function setTimestamp($timestamp)
303
    {
304
        $this->updateProperty($this->timestamp, "timestamp", TimeDate::from($timestamp));
305
306
        return $this;
307
    }
308
309
    /**
310
     * Get the first team involved in the match
311
     * @return TeamInterface Team A
312
     */
313 14
    public function getTeamA()
314
    {
315 14
        $team = Team::get($this->team_a);
316
317 14
        if ($this->match_type === self::OFFICIAL && $team->isValid()) {
318 12
            return $team;
319
        }
320
321 3
        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 13
    public function getTeamB()
329
    {
330 13
        $team = Team::get($this->team_b);
331
332 13
        if ($this->match_type === self::OFFICIAL && $team->isValid()) {
333 11
            return $team;
334
        }
335
336 3
        return new ColorTeam($this->team_b_color);
337
    }
338
339
    /**
340
     * Get the color of Team A
341
     * @return string
342
     */
343
    public function getTeamAColor()
344
    {
345
        return $this->team_a_color;
346
    }
347
348
    /**
349
     * Get the color of Team B
350
     * @return string
351
     */
352
    public function getTeamBColor()
353
    {
354
        return $this->team_b_color;
355
    }
356
357
    /**
358
     * Get the list of players on Team A who participated in this match
359
     * @return Player[]|null Returns null if there were no players recorded for this match
360
     */
361 2
    public function getTeamAPlayers()
362
    {
363 2
        return $this->parsePlayers($this->team_a_players);
364
    }
365
366
    /**
367
     * Get the list of players on Team B who participated in this match
368
     * @return Player[]|null Returns null if there were no players recorded for this match
369
     */
370 1
    public function getTeamBPlayers()
371
    {
372 1
        return $this->parsePlayers($this->team_b_players);
373
    }
374
375
    /**
376
     * Get the list of players for a team in a match
377
     * @param  Team|int|null The team or team ID
378
     * @return Player[]|null Returns null if there were no players recorded for this match
379
     */
380 14
    public function getPlayers($team = null)
381
    {
382 14
        if ($team instanceof TeamInterface) {
383 1
            $team = $team->getId();
384 1
        }
385
386 14
        if ($this->getTeamA()->isValid() && $team == $this->getTeamA()->getId()) {
387 2
            return $this->getTeamAPlayers();
388 13
        } elseif ($this->getTeamB()->isValid() && $team == $this->getTeamB()->getId()) {
389 1
            return $this->getTeamBPlayers();
390
        }
391
392 13
        return $this->parsePlayers($this->team_a_players . "," . $this->team_b_players);
393
    }
394
395
    /**
396
     * Set the players of the match's teams
397
     *
398
     * @param int[] $teamAPlayers An array of player IDs
399
     * @param int[] $teamBPlayers An array of player IDs
400
     * @return self
401
     */
402
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
403
    {
404
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers));
405
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers));
406
407
        return $this;
408
    }
409
410
    /**
411
     * Get an array of players based on a string representation
412
     * @param string $playerString
413
     * @return Player[]|null Returns null if there were no players recorded for this match
414
     */
415 14
    private function parsePlayers($playerString)
416
    {
417 14
        if ($playerString == null) {
418 1
            return null;
419
        }
420
421 14
        return Player::arrayIdToModel(explode(",", $playerString));
422
    }
423
424
    /**
425
     * Get the first team's points
426
     * @return int Team A's points
427
     */
428 5
    public function getTeamAPoints()
429
    {
430 5
        return $this->team_a_points;
431
    }
432
433
    /**
434
     * Get the second team's points
435
     * @return int Team B's points
436
     */
437 5
    public function getTeamBPoints()
438
    {
439 5
        return $this->team_b_points;
440
    }
441
442
    /**
443
     * Set the match team points
444
     *
445
     * @param  int $teamAPoints Team A's points
446
     * @param  int $teamBPoints Team B's points
447
     * @return self
448
     */
449
    public function setTeamPoints($teamAPoints, $teamBPoints)
450
    {
451
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints);
452
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints);
453
454
        return $this;
455
    }
456
457
    /**
458
     * Set the match team colors
459
     *
460
     * @param  ColorTeam|string $teamAColor The color of team A
461
     * @param  ColorTeam|string $teamBColor The color of team B
462
     * @return self
463
     */
464
    public function setTeamColors($teamAColor, $teamBColor)
465
    {
466
        if ($this->isOfficial()) {
467
            throw new \Exception("Cannot change team colors in an official match");
468
        }
469
470
        if ($teamAColor instanceof ColorTeam) {
471
            $teamAColor = $teamAColor->getId();
472
        }
473
        if ($teamBColor instanceof ColorTeam) {
474
            $teamBColor = $teamBColor->getId();
475
        }
476
477
        $this->updateProperty($this->team_a_color, "team_a_color", $teamAColor);
478
        $this->updateProperty($this->team_b_color, "team_b_color", $teamBColor);
479
    }
480
481
    /**
482
     * Get the ELO difference applied to each team's old ELO
483
     * @return int The ELO difference
484
     */
485 8
    public function getEloDiff()
486
    {
487 8
        return abs($this->elo_diff);
488
    }
489
490
    /**
491
     * Get the Elo difference applied to players
492
     *
493
     * @return int The Elo difference for players
494
     */
495 1
    public function getPlayerEloDiff()
496
    {
497 1
        return abs($this->player_elo_diff);
498
    }
499
500
    /**
501
     * Get the first team's new ELO
502
     * @return int Team A's new ELO
503
     */
504 7
    public function getTeamAEloNew()
505
    {
506 7
        return $this->team_a_elo_new;
507
    }
508
509
    /**
510
     * Get the second team's new ELO
511
     * @return int Team B's new ELO
512
     */
513 7
    public function getTeamBEloNew()
514
    {
515 7
        return $this->team_b_elo_new;
516
    }
517
518
    /**
519
     * Get the first team's old ELO
520
     * @return int
521
     */
522 6
    public function getTeamAEloOld()
523
    {
524 6
        return $this->team_a_elo_new - $this->elo_diff;
525
    }
526
527
    /**
528
     * Get the second team's old ELO
529
     * @return int
530
     */
531 6
    public function getTeamBEloOld()
532
    {
533 6
        return $this->team_b_elo_new + $this->elo_diff;
534
    }
535
536
    /**
537
     * Get the team's new ELO
538
     * @param  Team $team The team whose new ELO to return
539
     * @return int|null   The new ELO, or null if the team provided has not
540
     *                    participated in the match
541
     */
542 1
    public function getTeamEloNew(Team $team)
543
    {
544 1
        if ($team->getId() == $this->team_a) {
545 1
            return $this->getTeamAEloNew();
546 1
        } elseif ($team->getId() == $this->team_b) {
547 1
            return $this->getTeamBEloNew();
548
        }
549
550
        return null;
551
    }
552
553
    /**
554
     * Get the team's old ELO
555
     * @param  Team $team The team whose old ELO to return
556
     * @return int|null   The old ELO, or null if the team provided has not
557
     *                    participated in the match
558
     */
559 1
    public function getTeamEloOld(Team $team)
560
    {
561 1
        if ($team->getId() == $this->team_a) {
562 1
            return $this->getTeamAEloOld();
563 1
        } elseif ($team->getId() == $this->team_b) {
564 1
            return $this->getTeamBEloOld();
565
        }
566
567
        return null;
568
    }
569
570
    /**
571
     * Get the map where the match was played on
572
     * @return Map Returns an invalid map if no map was found
573
     */
574 1
    public function getMap()
575
    {
576 1
        return Map::get($this->map);
577
    }
578
579
    /**
580
     * Set the map where the match was played
581
     * @param  int $map The ID of the map
582
     * @return self
583
     */
584
    public function setMap($map)
585
    {
586
        $this->updateProperty($this->map, "map", $map, "s");
587
    }
588
589
    /**
590
     * Get the match type
591
     *
592
     * @return string 'official', 'fm', or 'special'
593
     */
594 14
    public function getMatchType()
595
    {
596 14
        return $this->match_type;
597
    }
598
599
    /**
600
     * Set the match type
601
     *
602
     * @param  string $matchType A valid match type; official, fm, special
603
     *
604
     * @return static
605
     */
606
    public function setMatchType($matchType)
607
    {
608
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
609
    }
610
611
    /**
612
     * Get a JSON decoded array of events that occurred during the match
613
     * @return mixed|null Returns null if there were no events recorded for the match
614
     */
615
    public function getMatchDetails()
616
    {
617
        return json_decode($this->match_details);
618
    }
619
620
    /**
621
     * Get the server address of the server where this match took place
622
     * @return string|null Returns null if there was no server address recorded
623
     */
624 1
    public function getServerAddress()
625
    {
626 1
        return $this->server;
627
    }
628
629
    /**
630
     * Set the server address of the server where this match took place
631
     *
632
     * @param  string|null $server The server hostname
633
     * @param  int|null    $port   The server port
0 ignored issues
show
Bug introduced by
There is no parameter named $port. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
634
     * @return self
635
     */
636
    public function setServerAddress($server = null)
637
    {
638
        $this->updateProperty($this->server, "server", $server);
639
640
        return $this;
641
    }
642
643
    /**
644
     * Get the name of the replay file for this specific map
645
     * @param  int    $length The length of the replay file name; it will be truncated
646
     * @return string Returns null if there was no replay file name recorded
647
     */
648 1
    public function getReplayFileName($length = 0)
649
    {
650 1
        if ($length > 0) {
651
            return substr($this->replay_file, 0, $length);
652
        }
653
654 1
        return $this->replay_file;
655
    }
656
657
    /**
658
     * Get the match duration
659
     * @return int The duration of the match in minutes
660
     */
661 3
    public function getDuration()
662
    {
663 3
        return $this->duration;
664
    }
665
666
    /**
667
     * Set the match duration
668
     *
669
     * @param  int  $duration The new duration of the match in minutes
670
     * @return self
671
     */
672
    public function setDuration($duration)
673
    {
674
        return $this->updateProperty($this->duration, "duration", $duration);
675
    }
676
677
    /**
678
     * Get the user who entered the match
679
     * @return Player
680
     */
681 2
    public function getEnteredBy()
682
    {
683 2
        return Player::get($this->entered_by);
684
    }
685
686
    /**
687
     * Get the loser of the match
688
     *
689
     * @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw
690
     */
691 12
    public function getLoser()
692
    {
693
        // Get the winner of the match
694 12
        $winner = $this->getWinner();
695
696
        // Get the team that wasn't the winner... Duh
697 12
        return $this->getOpponent($winner);
698
    }
699
700
    /**
701
     * Get the winner of a match
702
     *
703
     * @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw
704
     */
705 12
    public function getWinner()
706
    {
707
        // "As Mother Teresa once said, it's not enough if you win. Others must lose."
708 12
        //   -Stephen Colbert
709 9
710 4
        // Get the team that had its Elo increased or the team whose players had their Elo increased
711 3
        if ($this->elo_diff > 0 || $this->player_elo_diff > 0) {
712 1
            return $this->getTeamA();
713
        } elseif ($this->elo_diff < 0 || $this->player_elo_diff < 0) {
714 1
            return $this->getTeamB();
715
        } elseif ($this->team_a_points > $this->team_b_points) {
716
            // In case we're dealing with a match such an FM that doesn't have an ELO difference
717
            return $this->getTeamA();
718
        } elseif ($this->team_a_points < $this->team_b_points) {
719
            return $this->getTeamB();
720
        }
721
722
        // If the scores are the same, return Team A because well, fuck you that's why
723
        return $this->getTeamA();
724
    }
725
726
    /**
727 13
     * Determine whether the match was a draw
728
     * @return bool True if the match ended without any winning teams
729 13
     */
730
    public function isDraw()
731
    {
732
        return $this->team_a_points == $this->team_b_points;
733
    }
734
735
    /**
736
     * Find out whether the match involves a team
737
     *
738 1
     * @param  TeamInterface $team The team to check
739
     * @return bool
740 1
     */
741
    public function involvesTeam($team)
742
    {
743
        return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId();
744
    }
745
746 14
    /**
747
     * Find out if the match is played between official teams
748 14
     */
749
    public function isOfficial()
750
    {
751
        return self::OFFICIAL === $this->getMatchType();
752
    }
753
754
    /**
755
     * Reset the ELOs of the teams participating in the match
756
     *
757
     * @return self
758
     */
759
    public function resetELOs()
760
    {
761
        if ($this->match_type === self::OFFICIAL) {
762
            $this->getTeamA()->changeELO(-$this->elo_diff);
0 ignored issues
show
Bug introduced by
The method changeELO() does not seem to exist on object<TeamInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
763
            $this->getTeamB()->changeELO(+$this->elo_diff);
0 ignored issues
show
Bug introduced by
The method changeELO() does not seem to exist on object<TeamInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
764
        }
765
766
        return $this;
767
    }
768
769
    /**
770
     * Calculate the match's contribution to the team activity
771 1
     *
772
     * @return float
773 1
     */
774 1
    public function getActivity()
775
    {
776 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
777
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
778 1
779
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
780
781
        if (is_nan($activity) || $activity < 0.0) {
782 1
            return 0.0;
783
        }
784
785
        return $activity;
786
    }
787
788
    /**
789
     * Enter a new match to the database
790
     * @param  int             $a          Team A's ID
791
     * @param  int             $b          Team B's ID
792
     * @param  int             $a_points   Team A's match points
793
     * @param  int             $b_points   Team B's match points
794
     * @param  int             $duration   The match duration in minutes
795
     * @param  int|null        $entered_by The ID of the player reporting the match
796
     * @param  string|DateTime $timestamp  When the match was played
797
     * @param  int[]           $a_players  The IDs of the first team's players
798
     * @param  int[]           $b_players  The IDs of the second team's players
799
     * @param  string|null     $server     The address of the server where the match was played
800
     * @param  int|null        $port       The port of the server where the match was played
0 ignored issues
show
Bug introduced by
There is no parameter named $port. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
801
     * @param  string          $replayFile The name of the replay file of the match
802
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
803
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
804
     * @param  string          $a_color    Team A's color
805 14
     * @param  string          $b_color    Team b's color
806
     * @return Match           An object representing the match that was just entered
807
     */
808
    public static function enterMatch(
809
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
810
        $a_players = array(), $b_players = array(), $server = null, $replayFile = null,
811 14
        $map = null, $matchType = "official", $a_color = null, $b_color = null
812 14
    ) {
813 14
        $matchData = array(
814 14
            'team_a_color'   => strtolower($a_color),
815 14
            'team_b_color'   => strtolower($b_color),
816 14
            'team_a_points'  => $a_points,
817 14
            'team_b_points'  => $b_points,
818 14
            'team_a_players' => implode(',', $a_players),
819 14
            'team_b_players' => implode(',', $b_players),
820 14
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
821 14
            'duration'       => $duration,
822 14
            'entered_by'     => $entered_by,
823 14
            'server'         => $server,
824
            'replay_file'    => $replayFile,
825 14
            'map'            => $map,
826
            'status'         => 'entered',
827 14
            'match_type'     => $matchType
828
        );
829 14
830 13
        $playerEloDiff = null;
831 13
832
        if ($matchType === self::OFFICIAL) {
833 13
            $team_a = Team::get($a);
834 13
            $team_b = Team::get($b);
835
836
            $a_players_elo = null;
0 ignored issues
show
Unused Code introduced by
$a_players_elo is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
837 13
            $b_players_elo = null;
0 ignored issues
show
Unused Code introduced by
$b_players_elo is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
838 4
839 4
            // Only bother if we have players reported for both teams
840
            if (!empty($a_players) && !empty($b_players)) {
841 4
                $a_players_elo = self::getAveragePlayerElo($a_players);
842 4
                $b_players_elo = self::getAveragePlayerElo($b_players);
843
844
                $playerEloDiff = self::calculateEloDiff($a_players_elo, $b_players_elo, $a_points, $b_points, $duration);
845 13
            }
846 13
847
            // If it's a Team vs Team official match, we need to calculate the Elo diff between the two teams. Otherwise,
848 13
            // we'll be using the player Elo diff as the "team" Elo diff for future calculations and database persistence
849
            $teamEloDiff = $playerEloDiff;
850
851
            if ($team_a->isValid() && $team_b->isValid()) {
852 13
                $teamEloDiff = self::calculateEloDiff($team_a->getElo(), $team_b->getElo(), $a_points, $b_points, $duration);
853
            }
854 13
855 13
            $matchData['elo_diff'] = $teamEloDiff;
856
            $matchData['player_elo_diff'] = $playerEloDiff;
857
858 13
            // Update team ELOs
859 12
            if ($team_a->isValid()) {
860
                $team_a->adjustElo($teamEloDiff);
861 12
862 12
                $matchData['team_a'] = $a;
863 12
                $matchData['team_a_elo_new'] = $team_a->getElo();
864 13
            }
865 11
            if ($team_b->isValid()) {
866
                $team_b->adjustElo(-$teamEloDiff);
867 11
868 11
                $matchData['team_b'] = $b;
869 11
                $matchData['team_b_elo_new'] = $team_b->getElo();
870 13
            }
871
        }
872 14
873 14
        $match = self::create($matchData, 'updated');
874
        $match->updateMatchCount();
875 14
876
        $players = $match->getPlayers();
877 14
878 14
        $db = Database::getInstance();
879
        $db->startTransaction();
880
881 14
        /** @var Player $player */
882 14
        foreach ($players as $player) {
0 ignored issues
show
Bug introduced by
The expression $players of type null|array<integer,object<Model>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
883
            $diff = $playerEloDiff;
884 14
885 4
            if ($playerEloDiff !== null && !in_array($player->getId(), $a_players)) {
886 4
                $diff = -$playerEloDiff;
887
            }
888 14
889 14
            $player->setMatchParticipation($match, $diff);
890
        }
891 14
892
        $db->finishTransaction();
893 14
894
        return $match;
895
    }
896
897
    /**
898
     * Calculate the ELO score difference
899
     *
900
     * Computes the ELO score difference on each team after a match, based on
901
     * GU League's rules.
902
     *
903
     * @param  int $a_elo    Team A's current ELO score
904
     * @param  int $b_elo    Team B's current ELO score
905
     * @param  int $a_points Team A's match points
906
     * @param  int $b_points Team B's match points
907
     * @param  int $duration The match duration in minutes
908
     * @return int The ELO score difference
909 13
     */
910
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
911 13
    {
912 13
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
913 8
        if ($a_points > $b_points) {
914 13
            $diff = 50 * (1 - $prob);
915 4
        } elseif ($a_points == $b_points) {
916 4
            $diff = 50 * (0.5 - $prob);
917 2
        } else {
918
            $diff = 50 * (0 - $prob);
919
        }
920
921 13
        // Apply ELO modifiers from `config.yml`
922 13
        $durations = Service::getParameter('bzion.league.duration');
923
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
924 13
925
        if (abs($diff) < 1 && $diff != 0) {
926 2
            // ELOs such as 0.75 should round up to 1...
927
            return ($diff > 0) ? 1 : -1;
928
        }
929
930 11
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
931
        return intval($diff);
932
    }
933
934
    /**
935
     * Find if a match's stored ELO is correct
936
     */
937
    public function isEloCorrect()
938
    {
939
        return $this->elo_diff === $this->calculateEloDiff(
940
            $this->getTeamAEloOld(),
941
            $this->getTeamBEloOld(),
942
            $this->getTeamAPoints(),
943
            $this->getTeamBPoints(),
944
            $this->getDuration()
945
        );
946
    }
947
948
    /**
949
     * Recalculate the match's elo and adjust the team ELO values
950
     */
951
    public function recalculateElo()
952
    {
953
        if ($this->match_type !== self::OFFICIAL) {
954
            return;
955
        }
956
957
        $a = $this->getTeamA();
958
        $b = $this->getTeamB();
959
960
        $elo = $this->calculateEloDiff(
961
            $a->getElo(),
962
            $b->getElo(),
963
            $this->getTeamAPoints(),
964
            $this->getTeamBPoints(),
965
            $this->getDuration()
966
        );
967
968
        $this->updateProperty($this->elo_diff, "elo_diff", $elo);
969
970
        $a->changeElo($elo);
0 ignored issues
show
Bug introduced by
The method changeElo() does not seem to exist on object<TeamInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
971
        $b->changeElo(-$elo);
0 ignored issues
show
Bug introduced by
The method changeElo() does not seem to exist on object<TeamInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
972
973
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo());
974
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo());
975
    }
976
977
    /**
978
     * Get all the matches in the database
979 1
     */
980
    public static function getMatches()
981 1
    {
982
        return self::getQueryBuilder()->active()->getModels();
983
    }
984
985
    /**
986
     * Get a query builder for matches
987
     * @return MatchQueryBuilder
988 4
     */
989
    public static function getQueryBuilder()
990 4
    {
991
        return new MatchQueryBuilder('Match', array(
992 4
            'columns' => array(
993 4
                'firstTeam'        => 'team_a',
994 4
                'secondTeam'       => 'team_b',
995 4
                'firstTeamPoints'  => 'team_a_points',
996 4
                'secondTeamPoints' => 'team_b_points',
997 4
                'time'             => 'timestamp',
998 4
                'map'              => 'map',
999
                'type'             => 'match_type',
1000 4
                'status'           => 'status'
1001 4
            ),
1002
        ));
1003
    }
1004
1005
    /**
1006
     * {@inheritdoc}
1007
     */
1008
    public function delete()
1009
    {
1010
        $this->updateMatchCount(true);
1011
1012
        parent::delete();
1013
    }
1014
1015
    /**
1016
     * {@inheritdoc}
1017 3
     */
1018
    public static function getActiveStatuses()
1019 3
    {
1020
        return array('entered');
1021
    }
1022
1023
    /**
1024
     * {@inheritdoc}
1025 2
     */
1026
    public function getName()
1027 2
    {
1028 2
        $description = '';
1029 2
1030 2
        switch ($this->getMatchType()) {
1031 1
            case self::OFFICIAL:
1032 1
                // Only show Elo diff if both teams are actual teams
1033 1
                if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) {
1034
                    $description = "(+/- {$this->getEloDiff()})";
1035
                }
1036
                break;
1037
1038
            case self::FUN:
1039 2
                $description = 'Fun Match:';
1040
                break;
1041 2
1042 2
            case self::SPECIAL:
1043 2
                $description = 'Special Match:';
1044 2
                break;
1045 2
1046 2
            default:
1047 2
                break;
1048
        }
1049
1050
        return trim(sprintf('%s %s [%d] vs [%d] %s',
1051
            $description,
1052
            $this->getWinner()->getName(),
1053
            $this->getScore($this->getWinner()),
1054
            $this->getScore($this->getLoser()),
1055
            $this->getLoser()->getName()
1056
        ));
1057
    }
1058
1059 4
    /**
1060 4
     * Get the average ELO for an array of players
1061 4
     *
1062
     * @param int[] $players
1063 4
     *
1064
     * @return float|int
1065
     */
1066
    private static function getAveragePlayerElo($players)
1067
    {
1068
        $getElo = function ($n) {
1069
            return Player::get($n)->getElo();
1070
        };
1071 14
1072
        return array_sum(array_map($getElo, $players)) / count($players);
1073 14
    }
1074 2
1075
    /**
1076
     * Update the match count of the teams participating in the match
1077 13
     *
1078
     * @param bool $decrement Whether to decrement instead of incrementing the match count
1079 13
     */
1080 4
    private function updateMatchCount($decrement = false)
1081 4
    {
1082 4
        if ($this->match_type !== self::OFFICIAL) {
1083 10
            return;
1084 10
        }
1085
1086 13
        $diff = ($decrement) ? -1 : 1;
1087
1088
        if ($this->isDraw()) {
1089
            $this->getTeamA()->supportsMatchCount() && $this->getTeamA()->changeMatchCount($diff, 'draw');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1090
            $this->getTeamB()->supportsMatchCount() && $this->getTeamB()->changeMatchCount($diff, 'draw');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1091
        } else {
1092
            $this->getWinner()->supportsMatchCount() && $this->getWinner()->changeMatchCount($diff, 'win');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1093
            $this->getLoser()->supportsMatchCount()  && $this->getLoser()->changeMatchCount($diff, 'loss');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TeamInterface as the method changeMatchCount() does only exist in the following implementations of said interface: Team.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1094
        }
1095
    }
1096
}
1097