Completed
Push — master ( 8158f1...90d7fd )
by Konstantinos
11:17 queued 06:55
created

Team   C

Complexity

Total Complexity 52

Size/Duplication

Total Lines 640
Duplicated Lines 5.31 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 80.68%

Importance

Changes 11
Bugs 3 Features 4
Metric Value
wmc 52
c 11
b 3
f 4
lcom 2
cbo 9
dl 34
loc 640
ccs 142
cts 176
cp 0.8068
rs 5.102

37 Methods

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