Completed
Push — v0_9 ( a932eb )
by Vladimir
03:55
created

Match::updateMatchCount()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 16
ccs 10
cts 11
cp 0.9091
rs 9.2
cc 4
eloc 10
nc 5
nop 1
crap 4.0119
1
<?php
2
/**
3
 * This file contains functionality relating to the official matches played in the league
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
use BZIon\Model\Column\Timestamp;
9
10
/**
11
 * A match played between two teams
12
 * @package    BZiON\Models
13
 */
14
class Match extends UrlModel implements NamedModel
15
{
16
    const OFFICIAL = "official";
17
    const SPECIAL  = "special";
18
    const FUN      = "fm";
19
20
    use Timestamp;
21
22
    /**
23
     * The ID of the first team of the match
24
     * @var int
25
     */
26
    protected $team_a;
27
28
    /**
29
     * The ID of the second team of the match
30
     * @var int
31
     */
32
    protected $team_b;
33
34
    /**
35
     * The color of the first team
36
     * @var string
37
     */
38
    protected $team_a_color;
39
40
    /**
41
     * The color of the second team
42
     * @var string
43
     */
44
    protected $team_b_color;
45
46
    /**
47
     * The match points (usually the number of flag captures) Team A scored
48
     * @var int
49
     */
50
    protected $team_a_points;
51
52
    /**
53
     * The match points Team B scored
54
     * @var int
55
     */
56
    protected $team_b_points;
57
58
    /**
59
     * The BZIDs of players part of Team A who participated in the match, separated by commas
60
     * @var string
61
     */
62
    protected $team_a_players;
63
64
    /**
65
     * The BZIDs of players part of Team B who participated in the match, separated by commas
66
     * @var string
67
     */
68
    protected $team_b_players;
69
70
    /**
71
     * The ELO score of Team A after the match
72
     * @var int
73
     */
74
    protected $team_a_elo_new;
75
76
    /**
77
     * The ELO score of Team B after the match
78
     * @var int
79
     */
80
    protected $team_b_elo_new;
81
82
    /**
83
     * The map ID used in the match if the league supports more than one map
84
     * @var int
85
     */
86
    protected $map;
87
88
    /**
89
     * The type of match that occurred. Valid options: official, fm, special
90
     *
91
     * @var string
92
     */
93
    protected $match_type;
94
95
    /**
96
     * A JSON string of events that happened during a match, such as captures and substitutions
97
     * @var string
98
     */
99
    protected $match_details;
100
101
    /**
102
     * The port of the server where the match took place
103
     * @var int
104
     */
105
    protected $port;
106
107
    /**
108
     * The server location of there the match took place
109
     * @var string
110
     */
111
    protected $server;
112
113
    /**
114
     * The file name of the replay file of the match
115
     * @var string
116
     */
117
    protected $replay_file;
118
119
    /**
120
     * The absolute value of the ELO score difference
121
     * @var int
122
     */
123
    protected $elo_diff;
124
125
    /**
126
     * The timestamp representing when the match information was last updated
127
     * @var TimeDate
128
     */
129
    protected $updated;
130
131
    /**
132
     * The duration of the match in minutes
133
     * @var int
134
     */
135
    protected $duration;
136
137
    /**
138
     * The ID of the person (i.e. referee) who last updated the match information
139
     * @var string
140
     */
141
    protected $entered_by;
142
143
    /**
144
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
145
     * @var string
146
     */
147
    protected $status;
148
149
    /**
150
     * The name of the database table used for queries
151
     */
152
    const TABLE = "matches";
153
154
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
155
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
156
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
157
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 9
    protected function assignResult($match)
163
    {
164 9
        $this->team_a = $match['team_a'];
165 9
        $this->team_b = $match['team_b'];
166 9
        $this->team_a_color = $match['team_a_color'];
167 9
        $this->team_b_color = $match['team_b_color'];
168 9
        $this->team_a_points = $match['team_a_points'];
169 9
        $this->team_b_points = $match['team_b_points'];
170 9
        $this->team_a_players = $match['team_a_players'];
171 9
        $this->team_b_players = $match['team_b_players'];
172 9
        $this->team_a_elo_new = $match['team_a_elo_new'];
173 9
        $this->team_b_elo_new = $match['team_b_elo_new'];
174 9
        $this->map = $match['map'];
175 9
        $this->match_type = $match['match_type'];
176 9
        $this->match_details = $match['match_details'];
177 9
        $this->port = $match['port'];
178 9
        $this->server = $match['server'];
179 9
        $this->replay_file = $match['replay_file'];
180 9
        $this->elo_diff = $match['elo_diff'];
181 9
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
182 9
        $this->updated = TimeDate::fromMysql($match['updated']);
183 9
        $this->duration = $match['duration'];
184 9
        $this->entered_by = $match['entered_by'];
185 9
        $this->status = $match['status'];
186 9
    }
187
188
    /**
189
     * Get the name of the route that shows the object
190
     * @param  string $action The route's suffix
191
     * @return string
192
     */
193 1
    public static function getRouteName($action = 'show')
194
    {
195 1
        return "match_$action";
196
    }
197
198
    /**
199
     * Get a one word description of a match relative to a team (i.e. win, loss, or draw)
200
     *
201
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
202
     *
203
     * @return string Either "win", "loss", or "draw" relative to the team
204
     */
205 1
    public function getMatchDescription($teamID)
206
    {
207 1
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
208 1
            return "win";
209 1
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
210 1
            return "loss";
211
        }
212
213 1
        return "tie";
214
    }
215
216
    /**
217
     * Get a one letter description of a match relative to a team (i.e. W, L, or T)
218
     *
219
     * @param int|string|TeamInterface $teamID The team ID we want the noun for
220
     *
221
     * @return string Either "W", "L", or "T" relative to the team
222
     */
223 1
    public function getMatchLetter($teamID)
224
    {
225 1
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
226
    }
227
228
    /**
229
     * Get the score of a specific team
230
     *
231
     * @param int|string|TeamInterface $teamID The team we want the score for
232
     *
233
     * @return int The score that team received
234
     */
235 2 View Code Duplication
    public function getScore($teamID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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