Passed
Branch bigRefactor (c260a1)
by Julien
34:53
created

TreeGen::chunkAndShuffle()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
rs 8.8571
cc 6
eloc 12
nc 5
nop 3
1
<?php
2
3
namespace Xoco70\KendoTournaments\TreeGen;
4
5
use Illuminate\Support\Collection;
6
use Xoco70\KendoTournaments\Contracts\TreeGenerable;
7
use Xoco70\KendoTournaments\Exceptions\TreeGenerationException;
8
use Xoco70\KendoTournaments\Models\Championship;
9
use Xoco70\KendoTournaments\Models\ChampionshipSettings;
10
use Xoco70\KendoTournaments\Models\Competitor;
11
use Xoco70\KendoTournaments\Models\FightersGroup;
12
use Xoco70\KendoTournaments\Models\Team;
13
14
class TreeGen implements TreeGenerable
15
{
16
    protected $groupBy;
17
    protected $tree;
18
    public $championship;
19
    public $settings;
20
    protected $numFighters;
21
22
    /**
23
     * @param Championship $championship
24
     * @param $groupBy
25
     */
26
    public function __construct(Championship $championship, $groupBy)
27
    {
28
        $this->championship = $championship;
29
        $this->groupBy = $groupBy;
30
        $this->settings = $championship->getSettings();
31
        $this->tree = new Collection();
32
    }
33
34
    /**
35
     * Generate tree groups for a championship.
36
     *
37
     * @throws TreeGenerationException
38
     */
39
    public function run()
40
    {
41
        $usersByArea = $this->getFightersByArea();
42
        $numFighters = sizeof($usersByArea->collapse());
43
44
        $this->generateGroupsForRound($usersByArea, $area = 1, $round = 1, $shuffle = 1);
45
        $this->pushEmptyGroupsToTree($numFighters);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xoco70\KendoTournaments\TreeGen\TreeGen as the method pushEmptyGroupsToTree() does only exist in the following sub-classes of Xoco70\KendoTournaments\TreeGen\TreeGen: Xoco70\KendoTournaments\...nationCompetitorTreeGen, Xoco70\KendoTournaments\...irectEliminationTreeGen, Xoco70\KendoTournaments\...layOffCompetitorTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTeamTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTreeGen. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
46
        $this->addParentToChildren($numFighters);
47
        // Now add parents to all
48
    }
49
50
    /**
51
     * Get the biggest entity group
52
     * @param $userGroups
53
     *
54
     * @return int
55
     */
56
    private function getMaxFightersByEntity($userGroups): int
57
    {
58
        return $userGroups
59
            ->sortByDesc(function ($group) {
60
                return $group->count();
61
            })
62
            ->first()
63
            ->count();
64
65
    }
66
67
    /**
68
     * Get Competitor's list ordered by entities
69
     * Countries for Internation Tournament, State for a National Tournament, etc.
70
     *
71
     * @return Collection
72
     */
73
    private function getFightersByEntity($fighters): Collection
74
    {
75
        // Right now, we are treating users and teams as equals.
76
        // It doesn't matter right now, because we only need name attribute which is common to both models
77
78
        // $this->groupBy contains federation_id, association_id, club_id, etc.
79
        if (($this->groupBy) != null) {
80
            $fighterGroups = $fighters->groupBy($this->groupBy); // Collection of Collection
81
        } else {
82
            $fighterGroups = $fighters->chunk(1); // Collection of Collection
83
        }
84
        return $fighterGroups;
85
    }
86
87
    /**
88
     * Get the size the first round will have
89
     * @param $fighterCount
90
     * @return int
91
     */
92
    protected function getTreeSize($fighterCount, $groupSize)
93
    {
94
        $square = collect([1, 2, 4, 8, 16, 32, 64]);
95
        $squareMultiplied = $square->map(function ($item, $key) use ($groupSize) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
96
            return $item * $groupSize;
97
        });
98
99
        foreach ($squareMultiplied as $limit) {
100
            if ($fighterCount <= $limit) {
101
                return $limit;
102
            }
103
        }
104
        return 64 * $groupSize;
105
    }
106
107
    /**
108
     * Repart BYE in the tree,
109
     * @param $fighterGroups
110
     * @param int $max
111
     *
112
     * @return Collection
113
     */
114
    private function repart($fighterGroups, $max)
115
    {
116
        $fighters = new Collection();
117
        for ($i = 0; $i < $max; $i++) {
118
            foreach ($fighterGroups as $fighterGroup) {
119
                $fighter = $fighterGroup->values()->get($i);
120
                if ($fighter != null) {
121
                    $fighters->push($fighter);
122
                }
123
            }
124
        }
125
126
        return $fighters;
127
    }
128
129
    /**
130
     * Insert byes in an homogen way.
131
     *
132
     * @param Collection $fighters
133
     * @param Collection $byeGroup
134
     *
135
     * @return Collection
136
     */
137
    private function insertByes(Collection $fighters, Collection $byeGroup)
138
    {
139
        $bye = count($byeGroup) > 0 ? $byeGroup[0] : [];
140
        $sizeFighters = count($fighters);
141
        $sizeGroupBy = count($byeGroup);
142
143
        $frequency = $sizeGroupBy != 0
144
            ? (int)floor($sizeFighters / $sizeGroupBy)
145
            : -1;
146
147
        // Create Copy of $competitors
148
        $newFighters = new Collection();
149
        $i = 0;
150
        $byeCount = 0;
151
        foreach ($fighters as $fighter) {
152
            if ($frequency != -1 && $i % $frequency == 0 && $byeCount < $sizeGroupBy) {
153
                $newFighters->push($bye);
154
                $byeCount++;
155
            }
156
            $newFighters->push($fighter);
157
            $i++;
158
        }
159
160
        return $newFighters;
161
    }
162
163
    /**
164
     * @param Collection $usersByArea
165
     * @param integer $area
166
     * @param integer $round
167
     * @param integer $shuffle
168
     *
169
     */
170
    public function generateGroupsForRound($usersByArea, $area, $round, $shuffle)
171
    {
172
        foreach ($usersByArea as $fightersByEntity) {
173
            // Chunking to make small round robin groups
174
            $fightersGroup = $this->chunkAndShuffle($round, $shuffle, $fightersByEntity);
175
            $order = 1;
176
            foreach ($fightersGroup as $value => $fighters) {
177
                $fighters = $fighters->pluck('id');
178
                if ($shuffle) {
179
                    $fighters = $fighters->shuffle();
180
                }
181
                $group = $this->saveGroup($area, $order, $round, $parent = null);
182
                $this->syncGroup($group, $fighters);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xoco70\KendoTournaments\TreeGen\TreeGen as the method syncGroup() does only exist in the following sub-classes of Xoco70\KendoTournaments\TreeGen\TreeGen: Xoco70\KendoTournaments\...nationCompetitorTreeGen, Xoco70\KendoTournaments\...tEliminationTeamTreeGen, Xoco70\KendoTournaments\...layOffCompetitorTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTeamTreeGen. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
183
                $order++;
184
            }
185
            $area++;
186
        }
187
    }
188
189
    /**
190
     * @param $area
191
     * @param $order
192
     * @param $round
193
     * @param $parent
194
     * @return FightersGroup
195
     */
196
    protected function saveGroup($area, $order, $round, $parent): FightersGroup
197
    {
198
        $group = new FightersGroup();
199
        $group->area = $area;
200
        $group->order = $order;
201
        $group->round = $round;
202
        $group->championship_id = $this->championship->id;
203
        if ($parent != null) {
204
            $group->parent_id = $parent->id;
205
        }
206
        $group->save();
207
        return $group;
208
    }
209
210
211
    /**
212
     * @param integer $groupSize
213
     * @return Collection
214
     */
215 View Code Duplication
    public function createByeGroup($groupSize): Collection
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...
216
    {
217
        $byeFighter = $this->createByeFighter();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xoco70\KendoTournaments\TreeGen\TreeGen as the method createByeFighter() does only exist in the following sub-classes of Xoco70\KendoTournaments\TreeGen\TreeGen: Xoco70\KendoTournaments\...nationCompetitorTreeGen, Xoco70\KendoTournaments\...tEliminationTeamTreeGen, Xoco70\KendoTournaments\...layOffCompetitorTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTeamTreeGen. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
218
        $group = new Collection();
219
        for ($i = 0; $i < $groupSize; $i++) {
220
            $group->push($byeFighter);
221
        }
222
        return $group;
223
    }
224
225
    /**
226
     * @param $fighters
227
     * @param Collection $fighterGroups
228
     * @return Collection
229
     */
230
    public function adjustFightersGroupWithByes($fighters, $fighterGroups): Collection
231
    {
232
        $tmpFighterGroups = clone $fighterGroups;
233
        $byeGroup = $this->getByeGroup($this->championship, $fighters);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xoco70\KendoTournaments\TreeGen\TreeGen as the method getByeGroup() does only exist in the following sub-classes of Xoco70\KendoTournaments\TreeGen\TreeGen: Xoco70\KendoTournaments\...nationCompetitorTreeGen, Xoco70\KendoTournaments\...irectEliminationTreeGen, Xoco70\KendoTournaments\...layOffCompetitorTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTeamTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTreeGen. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
234
235
        // Get biggest competitor's group
236
        $max = $this->getMaxFightersByEntity($tmpFighterGroups);
237
238
        // We reacommodate them so that we can mix them up and they don't fight with another competitor of his entity.
239
240
        $fighters = $this->repart($fighterGroups, $max);
241
        $fighters = $this->insertByes($fighters, $byeGroup);
242
243
        return $fighters;
244
    }
245
246
    /**
247
     * Get All Groups on previous round
248
     * @param $currentRound
249
     * @return Collection
250
     */
251
    private function getPreviousRound($currentRound)
252
    {
253
        $previousRound = $this->championship->groupsByRound($currentRound + 1)->get();
254
        return $previousRound;
255
    }
256
257
    /**
258
     * Get the next group on the right ( parent ), final round being the ancestor
259
     * @param $matchNumber
260
     * @param Collection $previousRound
261
     * @return mixed
262
     */
263
    private function getParentGroup($matchNumber, $previousRound)
264
    {
265
        $parentIndex = intval(($matchNumber + 1) / 2);
266
        $parent = $previousRound->get($parentIndex - 1);
267
        return $parent;
268
    }
269
270
    /**
271
     * Save Groups with their parent info
272
     * @param integer $numRounds
273
     * @param $numFightersEliminatory
274
     */
275
    protected function pushGroups($numRounds, $numFightersEliminatory, $shuffle = true)
0 ignored issues
show
Unused Code introduced by
The parameter $shuffle is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
276
    {
277
        for ($roundNumber = 2; $roundNumber <= $numRounds; $roundNumber++) {
278
            // From last match to first match
279
            for ($matchNumber = 1; $matchNumber <= ($numFightersEliminatory / pow(2, $roundNumber)); $matchNumber++) {
280
                $fighters = $this->createByeGroup(2);
281
                $group = $this->saveGroup($area = 1, $order = $matchNumber, $roundNumber, $parent = null);
282
                $this->syncGroup($group, $fighters);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xoco70\KendoTournaments\TreeGen\TreeGen as the method syncGroup() does only exist in the following sub-classes of Xoco70\KendoTournaments\TreeGen\TreeGen: Xoco70\KendoTournaments\...nationCompetitorTreeGen, Xoco70\KendoTournaments\...tEliminationTeamTreeGen, Xoco70\KendoTournaments\...layOffCompetitorTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTeamTreeGen. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
283
            }
284
        }
285
    }
286
287
    /**
288
     * Group Fighters by area
289
     * @return Collection
290
     * @throws TreeGenerationException
291
     */
292
    private function getFightersByArea()
293
    {
294
        // If previous trees already exists, delete all
295
        $this->championship->fightersGroups()->delete();
296
        $areas = $this->settings->fightingAreas;
297
        $fighters = $this->getFighters();
0 ignored issues
show
Bug introduced by
The method getFighters() does not exist on Xoco70\KendoTournaments\TreeGen\TreeGen. Did you maybe mean getFightersByEntity()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
298
299
        if ($fighters->count() / $areas < ChampionshipSettings::MIN_COMPETITORS_BY_AREA) {
300
            throw new TreeGenerationException();
301
        }
302
        // Get Competitor's / Team list ordered by entities ( Federation, Assoc, Club, etc...)
303
        $fighterByEntity = $this->getFightersByEntity($fighters); // Chunk(1)
304
        $fightersWithBye = $this->adjustFightersGroupWithByes($fighters, $fighterByEntity);
305
306
        // Chunk user by areas
307
        return $fightersWithBye->chunk(count($fightersWithBye) / $areas);
308
    }
309
310
    /**
311
     * Chunk Fighters into groups for fighting, and optionnaly shuffle
312
     * @param $round
313
     * @param $shuffle
314
     * @param $fightersByEntity
315
     * @return mixed
316
     */
317
    private function chunkAndShuffle($round, $shuffle, $fightersByEntity)
318
    {
319
        if ($this->championship->hasPreliminary()) {
320
            $fightersGroup = $fightersByEntity->chunk($this->settings->preliminaryGroupSize);
321
            if ($shuffle) {
322
                $fightersGroup = $fightersGroup->shuffle();
323
            }
324
        } elseif ($this->championship->isDirectEliminationType() || $round > 1) {
325
            $fightersGroup = $fightersByEntity->chunk(2);
326
            if ($shuffle) {
327
                $fightersGroup = $fightersGroup->shuffle();
328
            }
329
        } else { // Round Robin
330
            $fightersGroup = $fightersByEntity->chunk($fightersByEntity->count());
331
        }
332
        return $fightersGroup;
333
    }
334
335
    /**
336
     * Attach a parent to every child for nestedSet Navigation
337
     */
338
    private function addParentToChildren($numFightersEliminatory)
339
    {
340
        $numRounds = intval(log($numFightersEliminatory, 2));
341
342
        $groupsDesc = $this->championship
343
            ->fightersGroups()
344
            ->where('round', '<', $numRounds)
345
            ->orderByDesc('id')->get();
346
347
        $groupsDescByRound = $groupsDesc->groupBy('round');
348
349
        foreach ($groupsDescByRound as $round => $groups) {
350
            $previousRound = $this->getPreviousRound($round, $numRounds);
0 ignored issues
show
Unused Code introduced by
The call to TreeGen::getPreviousRound() has too many arguments starting with $numRounds.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
351
            foreach ($groups->reverse()->values() as $matchNumber => $group) {
352
                $parent = $this->getParentGroup($matchNumber + 1, $previousRound);
353
                $group->parent_id = $parent->id;
354
                $group->save();
355
            }
356
        }
357
    }
358
359
    /**
360
     * Create Bye Groups to adjust tree
361
     * @param $byeCount
362
     * @return Collection
363
     */
364 View Code Duplication
    protected function createNullsGroup($byeCount): Collection
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...
365
    {
366
        $byeFighter = $this->createByeFighter();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xoco70\KendoTournaments\TreeGen\TreeGen as the method createByeFighter() does only exist in the following sub-classes of Xoco70\KendoTournaments\TreeGen\TreeGen: Xoco70\KendoTournaments\...nationCompetitorTreeGen, Xoco70\KendoTournaments\...tEliminationTeamTreeGen, Xoco70\KendoTournaments\...layOffCompetitorTreeGen, Xoco70\KendoTournaments\TreeGen\PlayOffTeamTreeGen. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
367
        $byeGroup = new Collection();
368
        for ($i = 0; $i < $byeCount; $i++) {
369
            $byeGroup->push($byeFighter);
370
        }
371
        return $byeGroup;
372
    }
373
374
}
375