Completed
Push — feature/player-elo-v3 ( 8c49df...69db9f )
by Vladimir
03:30
created

Team   D

Complexity

Total Complexity 53

Size/Duplication

Total Lines 649
Duplicated Lines 5.24 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 0
Metric Value
wmc 53
lcom 2
cbo 9
dl 34
loc 649
rs 4.4692
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A assignResult() 0 18 1
A getDescription() 0 4 1
A getElo() 0 4 1
A getLeader() 0 4 1
A getMatches() 0 9 1
A getMatchesDraw() 0 4 1
A getMatchesLost() 0 4 1
A getMatchesURL() 0 4 1
A getMatchesWon() 0 4 1
A getMembers() 0 22 3
A getName() 0 7 2
A getEscapedName() 0 7 2
A getNumMembers() 0 4 1
A getNumTotalMatches() 0 4 1
A getRankValue() 0 4 1
A getRankImageLiteral() 0 4 1
A incrementMatchCount() 0 4 1
A isMember() 0 6 1
A setDescription() 0 4 1
A setStatus() 0 4 1
A setLeader() 0 4 1
A addMember() 11 11 2
A adjustElo() 0 5 1
A setElo() 0 4 1
B changeMatchCount() 0 21 5
A decrementMatchCount() 0 4 1
B getActivity() 0 22 5
A getCreationDate() 0 4 1
A removeMember() 11 11 2
A isLastMatch() 0 9 1
A supportsMatchCount() 0 4 1
B createTeam() 0 25 1
A getTeams() 0 9 1
A getFromName() 0 6 1
A getAlphabeticalSort() 0 6 1
A getActiveStatuses() 0 4 1
A getQueryBuilder() 12 12 1
A isEditor() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Team often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Team, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file contains functionality relating to the teams belonging to the current 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
9
/**
10
 * A league team
11
 * @package    BZiON\Models
12
 */
13
class Team extends AvatarModel implements TeamInterface, DuplexUrlInterface, EloInterface
14
{
15
    /**
16
     * The description of the team written in markdown
17
     *
18
     * @var string
19
     */
20
    protected $description;
21
22
    /**
23
     * The creation date of the team
24
     *
25
     * @var TimeDate
26
     */
27
    protected $created;
28
29
    /**
30
     * The team's current elo
31
     *
32
     * @var int
33
     */
34
    protected $elo;
35
36
    /**
37
     * The team's activity
38
     *
39
     * null if we haven't calculated it yet
40
     *
41
     * @var float|null
42
     */
43
    protected $activity = null;
44
45
    /**
46
     * The id of the team leader
47
     *
48
     * @var int
49
     */
50
    protected $leader;
51
52
    /**
53
     * The number of matches won
54
     *
55
     * @var int
56
     */
57
    protected $matches_won;
58
59
    /**
60
     * The number of matches lost
61
     *
62
     * @var int
63
     */
64
    protected $matches_lost;
65
66
    /**
67
     * The number of matches tied
68
     *
69
     * @var int
70
     */
71
    protected $matches_draw;
72
73
    /**
74
     * The total number of matches
75
     *
76
     * @var int
77
     */
78
    protected $matches_total;
79
80
    /**
81
     * The number of members
82
     *
83
     * @var int
84
     */
85
    protected $members;
86
87
    /**
88
     * The team's status
89
     *
90
     * @var string
91
     */
92
    protected $status;
93
94
    /**
95
     * A list of cached matches to calculate team activity
96
     *
97
     * @var Match[]|null
98
     */
99
    public static $cachedMatches = null;
100
101
    /**
102
     * The name of the database table used for queries
103
     */
104
    const TABLE = "teams";
105
106
    /**
107
     * The location where avatars will be stored
108
     */
109
    const AVATAR_LOCATION = "/web/assets/imgs/avatars/teams/";
110
111
    const CREATE_PERMISSION = Permission::CREATE_TEAM;
112
    const EDIT_PERMISSION = Permission::EDIT_TEAM;
113
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_TEAM;
114
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_TEAM;
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    protected function assignResult($team)
120
    {
121
        $this->name = $team['name'];
122
        $this->alias = $team['alias'];
123
        $this->description = $team['description'];
124
        $this->avatar = $team['avatar'];
125
        $this->created = TimeDate::fromMysql($team['created']);
126
        $this->elo = $team['elo'];
127
        $this->activity = null;
128
        $this->leader = $team['leader'];
129
        $this->matches_won = $team['matches_won'];
130
        $this->matches_lost = $team['matches_lost'];
131
        $this->matches_draw = $team['matches_draw'];
132
        $this->members = $team['members'];
133
        $this->status = $team['status'];
134
135
        $this->matches_total = $this->matches_won + $this->matches_lost + $this->matches_draw;
136
    }
137
138
    /**
139
     * Adds a new member to the team
140
     *
141
     * @param int $id The id of the player to add to the team
142
     *
143
     * @return bool|null True if both the player was added to the team AND the team member count was incremented
144
     */
145 View Code Duplication
    public function addMember($id)
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...
146
    {
147
        $player = Player::get($id);
148
149
        if (!$player->isTeamless()) {
150
            throw new Exception("The player already belongs in a team");
151
        }
152
153
        $player->setTeam($this->getId());
154
        $this->update('members', ++$this->members);
155
    }
156
157
    /**
158
     * Increase or decrease the ELO of the team
159
     *
160
     * @param int   $adjust The value to be added to the current ELO (negative to subtract)
161
     * @param Match $match  The match where this Elo change took place
162
     */
163
    public function adjustElo($adjust, Match $match = null)
164
    {
165
        $this->elo += $adjust;
166
        $this->update("elo", $this->elo);
167
    }
168
169
    /**
170
     * Change the ELO of the team
171
     *
172
     * @param int $elo The new team ELO
173
     */
174
    public function setElo($elo)
175
    {
176
        $this->updateProperty($this->elo, "elo", $elo);
177
    }
178
179
    /**
180
     * Increment the team's match count
181
     *
182
     * @param int    $adjust The number to add to the current matches number (negative to substract)
183
     * @param string $type   The match count that should be changed. Can be 'win', 'draw' or 'loss'
184
     */
185
    public function changeMatchCount($adjust, $type)
186
    {
187
        $this->matches_total += $adjust;
188
189
        switch ($type) {
190
            case "win":
191
            case "won":
192
                $this->update("matches_won", $this->matches_won += $adjust);
193
194
                return;
195
            case "loss":
196
            case "lost":
197
                $this->update("matches_lost", $this->matches_lost += $adjust);
198
199
                return;
200
            default:
201
                $this->update("matches_draw", $this->matches_draw += $adjust);
202
203
                return;
204
        }
205
    }
206
207
    /**
208
     * Decrement the team's match count by one
209
     *
210
     * @param string $type The type of the match. Can be 'win', 'draw' or 'loss'
211
     */
212
    public function decrementMatchCount($type)
213
    {
214
        $this->changeMatchCount(-1, $type);
215
    }
216
217
    /**
218
     * Get the activity of the team
219
     *
220
     * @return float The team's activity
221
     */
222
    public function getActivity()
223
    {
224
        if ($this->activity === null) {
225
            // We don't have a cached activity value
226
            if (self::$cachedMatches === null) {
227
                self::$cachedMatches = Match::getQueryBuilder()
228
                    ->active()
229
                    ->with($this)
230
                    ->where('time')->isAfter(TimeDate::from('45 days ago'))
231
                    ->getModels($fast = true);
232
            }
233
234
            $this->activity = 0.0;
235
            foreach (self::$cachedMatches as $match) {
236
                if ($match->involvesTeam($this)) {
237
                    $this->activity += $match->getActivity();
238
                }
239
            }
240
        }
241
242
        return $this->activity;
243
    }
244
245
    /**
246
     * Get the creation date of the team
247
     *
248
     * @return TimeDate The creation date of the team
249
     */
250
    public function getCreationDate()
251
    {
252
        return $this->created->copy();
253
    }
254
255
    /**
256
     * Get the description of the team
257
     *
258
     * @return string  The description of the team
259
     */
260
    public function getDescription()
261
    {
262
        return $this->description;
263
    }
264
265
    /**
266
     * Get the current elo of the team
267
     *
268
     * @return int The elo of the team
269
     */
270
    public function getElo()
271
    {
272
        return $this->elo;
273
    }
274
275
    /**
276
     * Get the leader of the team
277
     *
278
     * @return Player The object representing the team leader
279
     */
280
    public function getLeader()
281
    {
282
        return Player::get($this->leader);
283
    }
284
285
    /**
286
     * Get the matches this team has participated in
287
     *
288
     * @param string $matchType The filter for match types: "all", "wins", "losses", or "draws"
289
     * @param int    $count     The amount of matches to be retrieved
290
     * @param int    $page      The number of the page to return
291
     *
292
     * @return Match[] The array of match IDs this team has participated in
293
     */
294
    public function getMatches($matchType = "all", $count = 5, $page = 1)
295
    {
296
        return Match::getQueryBuilder()
297
             ->active()
298
             ->with($this, $matchType)
299
             ->sortBy('time')->reverse()
300
             ->limit($count)->fromPage($page)
301
             ->getModels($fast = true);
302
    }
303
304
    /**
305
     * Get the number of matches that resulted as a draw
306
     *
307
     * @return int The number of matches, respectively
308
     */
309
    public function getMatchesDraw()
310
    {
311
        return $this->matches_draw;
312
    }
313
314
    /**
315
     * Get the number of matches that the team has lost
316
     *
317
     * @return int The number of matches, respectively
318
     */
319
    public function getMatchesLost()
320
    {
321
        return $this->matches_lost;
322
    }
323
324
    /**
325
     * Get the URL that points to the team's list of matches
326
     *
327
     * @return string The team's list of matches
328
     */
329
    public function getMatchesURL()
330
    {
331
        return Service::getGenerator()->generate("match_by_team_list", array("team" => $this->getAlias()));
332
    }
333
334
    /**
335
     * Get the number of matches that the team has won
336
     *
337
     * @return int The number of matches, respectively
338
     */
339
    public function getMatchesWon()
340
    {
341
        return $this->matches_won;
342
    }
343
344
    /**
345
     * Get the members on the team
346
     *
347
     * @return Player[] The members on the team
348
     */
349
    public function getMembers()
350
    {
351
        $leader = $this->leader;
352
        $members = Player::getTeamMembers($this->id);
353
354
        usort($members, function ($a, $b) use ($leader) {
355
            // Leader always goes first
356
            if ($a->getId() == $leader) {
357
                return -1;
358
            }
359
            if ($b->getId() == $leader) {
360
                return 1;
361
            }
362
363
            // Sort the rest of the players alphabetically
364
            $sort = Player::getAlphabeticalSort();
365
366
            return $sort($a, $b);
367
        });
368
369
        return $members;
370
    }
371
372
    /**
373
     * Get the name of the team
374
     *
375
     * @return string The name of the team
376
     */
377
    public function getName()
378
    {
379
        if ($this->name === null) {
380
            return "None";
381
        }
382
        return $this->name;
383
    }
384
385
    /**
386
     * Get the name of the team, safe for use in your HTML
387
     *
388
     * @return string The name of the team
389
     */
390
    public function getEscapedName()
391
    {
392
        if (!$this->valid) {
393
            return "<em>None</em>";
394
        }
395
        return $this->escape($this->name);
396
    }
397
398
    /**
399
     * Get the number of members on the team
400
     *
401
     * @return int The number of members on the team
402
     */
403
    public function getNumMembers()
404
    {
405
        return $this->members;
406
    }
407
408
    /**
409
     * Get the total number of matches this team has played
410
     *
411
     * @return int The total number of matches this team has played
412
     */
413
    public function getNumTotalMatches()
414
    {
415
        return $this->matches_total;
416
    }
417
418
    /**
419
     * Get the rank category a team belongs too based on their ELO
420
     *
421
     * This value is always a multiple of 100 and less than or equal to 2000
422
     *
423
     * @return int The rank category a team belongs to
424
     */
425
    public function getRankValue()
426
    {
427
        return min(2000, floor($this->getElo() / 100) * 100);
428
    }
429
430
    /**
431
     * Get the HTML for an image with the rank symbol
432
     *
433
     * @return string The HTML for a rank image
434
     */
435
    public function getRankImageLiteral()
436
    {
437
        return '<div class="c-rank c-rank--' . $this->getRankValue() . '" aria-hidden="true"></div>';
438
    }
439
440
    /**
441
     * Increment the team's match count by one
442
     *
443
     * @param string $type The type of the match. Can be 'win', 'draw' or 'loss'
444
     */
445
    public function incrementMatchCount($type)
446
    {
447
        $this->changeMatchCount(1, $type);
448
    }
449
450
    /**
451
     * Check if a player is part of this team
452
     *
453
     * @param int $playerID The player to check
454
     *
455
     * @return bool True if the player belongs to this team
456
     */
457
    public function isMember($playerID)
458
    {
459
        $player = Player::get($playerID);
460
461
        return $player->getTeam()->isSameAs($this);
462
    }
463
464
    /**
465
     * Removes a member from the team
466
     *
467
     * @param  int  $id The id of the player to remove
468
     * @return void
469
     */
470 View Code Duplication
    public function removeMember($id)
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...
471
    {
472
        if (!$this->isMember($id)) {
473
            throw new Exception("The player is not a member of that team");
474
        }
475
476
        $player = Player::get($id);
477
478
        $player->update("team", null);
479
        $this->update('members', --$this->members);
480
    }
481
482
    /**
483
     * Update the description of the team
484
     *
485
     * @param  string $description The description of the team written as markdown
486
     * @return void
487
     */
488
    public function setDescription($description)
489
    {
490
        $this->update("description", $description);
491
    }
492
493
    /**
494
     * Change the status of the team
495
     *
496
     * @param  string $newStatus The new status of the team (open, closed, disabled or deleted)
497
     * @return self
498
     */
499
    public function setStatus($newStatus)
500
    {
501
        return $this->updateProperty($this->status, 'status', $newStatus);
502
    }
503
504
    /**
505
     * Change the leader of the team
506
     *
507
     * @param  int  $leader The ID of the new leader of the team
508
     * @return self
509
     */
510
    public function setLeader($leader)
511
    {
512
        return $this->updateProperty($this->leader, 'leader', $leader);
513
    }
514
515
    /**
516
     * Find if a specific match is the team's last one
517
     *
518
     * @param  int|Match $match The match
519
     * @return bool
520
     */
521
    public function isLastMatch($match)
522
    {
523
        // Find if this team participated in any matches after the current match
524
        return !Match::getQueryBuilder()
525
            ->with($this)
526
            ->where('status')->notEquals('deleted')
527
            ->where('time')->isAfter(Match::get($match)->getTimestamp())
528
            ->any();
529
    }
530
531
    /**
532
     * {@inheritdoc}
533
     */
534
    public function delete()
535
    {
536
        parent::delete();
537
538
        // Remove all the members of a deleted team
539
        $this->updateProperty($this->members, 'members', 0);
540
        $this->db->execute("UPDATE `players` SET `team` = NULL WHERE `team` = ?",
541
            $this->id);
542
    }
543
544
    /**
545
     * {@inheritdoc}
546
     */
547
    public function supportsMatchCount()
548
    {
549
        return true;
550
    }
551
552
    /**
553
     * Create a new team
554
     *
555
     * @param  string           $name        The name of the team
556
     * @param  int              $leader      The ID of the person creating the team, also the leader
557
     * @param  string           $avatar      The URL to the team's avatar
558
     * @param  string           $description The team's description
559
     * @param  string           $status      The team's status (open, closed, disabled or deleted)
560
     * @param  string|\TimeDate $created     The date the team was created
561
     *
562
     * @return Team   An object that represents the newly created team
563
     */
564
    public static function createTeam($name, $leader, $avatar, $description, $status = 'closed', $created = "now")
565
    {
566
        $created = TimeDate::from($created);
567
568
        $team = self::create(array(
569
            'name'         => $name,
570
            'alias'        => self::generateAlias($name),
571
            'description'  => $description,
572
            'elo'          => 1200,
573
            'activity'     => 0.00,
574
            'matches_won'  => 0,
575
            'matches_draw' => 0,
576
            'matches_lost' => 0,
577
            'members'      => 0,
578
            'avatar'       => $avatar,
579
            'leader'       => $leader,
580
            'status'       => $status,
581
            'created'      => $created->toMysql(),
582
        ));
583
584
        $team->addMember($leader);
585
        $team->getIdenticon($team->getId());
586
587
        return $team;
588
    }
589
590
    /**
591
     * Get all the teams in the database that are not disabled or deleted
592
     *
593
     * @return Team[] An array of Team IDs
594
     */
595
    public static function getTeams()
596
    {
597
        return self::arrayIdToModel(
598
            self::fetchIdsFrom(
599
                "status", array("disabled", "deleted"),
600
                true, "ORDER BY elo DESC"
601
            )
602
        );
603
    }
604
605
    /**
606
     * Get a single team by its name
607
     *
608
     * @param  string $name The team name to look for
609
     * @return Team
610
     */
611
    public static function getFromName($name)
612
    {
613
        $team = static::get(self::fetchIdFrom($name, 'name'));
614
615
        return $team->inject('name', $name);
616
    }
617
618
    /**
619
     * Alphabetical order function for use in usort (case-insensitive)
620
     * @return Closure The sort function
621
     */
622
    public static function getAlphabeticalSort()
623
    {
624
        return function (Team $a, Team $b) {
625
            return strcasecmp($a->getName(), $b->getName());
626
        };
627
    }
628
629
    /**
630
     * {@inheritdoc}
631
     */
632
    public static function getActiveStatuses()
633
    {
634
        return array('open', 'closed');
635
    }
636
637
    /**
638
     * Get a query builder for teams
639
     * @return QueryBuilder
640
     */
641 View Code Duplication
    public static function getQueryBuilder()
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...
642
    {
643
        return new QueryBuilder('Team', array(
644
            'columns' => array(
645
                'name'    => 'name',
646
                'elo'     => 'elo',
647
                'members' => 'members',
648
                'status'  => 'status'
649
            ),
650
            'name' => 'name',
651
        ));
652
    }
653
654
    /**
655
     * {@inheritdoc}
656
     */
657
    protected function isEditor($player)
658
    {
659
        return $player->isSameAs($this->getLeader());
660
    }
661
}
662