Completed
Push — master ( b3aad9...ab68d9 )
by Vladimir
15s
created

Team   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 648
Duplicated Lines 3.4 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 82.02%

Importance

Changes 0
Metric Value
wmc 51
lcom 2
cbo 9
dl 22
loc 648
ccs 146
cts 178
cp 0.8202
rs 7.989
c 0
b 0
f 0

39 Methods

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