Completed
Pull Request — master (#172)
by De Cramer
03:09
created

RecordHandler   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 324
Duplicated Lines 3.4 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 93.64%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 31
c 2
b 1
f 0
lcom 1
cbo 3
dl 11
loc 324
rs 9.8
ccs 103
cts 110
cp 0.9364

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 11 11 1
A getRecords() 0 4 1
A getPlayerPosition() 0 4 2
A getPlayerRecord() 0 4 2
A loadForMap() 0 17 2
A loadForPlayers() 0 12 3
A save() 0 14 2
D addRecord() 0 89 13
A getNewRecord() 0 10 1
A updateRecordStats() 0 7 1
A getScoreOrdering() 0 4 1
A compareNewScore() 0 8 2

How to fix   Duplicated Code   

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:

1
<?php
2
3
namespace eXpansion\Bundle\LocalRecords\Services;
4
5
use eXpansion\Bundle\LocalRecords\Model\Map\RecordTableMap;
6
use eXpansion\Bundle\LocalRecords\Model\Record;
7
use eXpansion\Bundle\LocalRecords\Model\RecordQueryBuilder;
8
use eXpansion\Framework\PlayersBundle\Storage\PlayerDb;
9
use Propel\Runtime\Propel;
10
11
/**
12
 * Class RecordHandler
13
 *
14
 * @package eXpansion\Bundle\LocalRecords\Model;
15
 * @author  oliver de Cramer <[email protected]>
16
 */
17
class RecordHandler
18
{
19
    /**
20
     * Available order logic for score.
21
     */
22
    const ORDER_ASC = "ASC";
23
    const ORDER_DESC = "DESC";
24
25
    /**
26
     * List of event types
27
     */
28
    const EVENT_TYPE_FIRST_TIME = 'first_time';
29
    const EVENT_TYPE_SAME_SCORE = 'same_score';
30
    const EVENT_TYPE_SAME_POS = 'same_position';
31
    const EVENT_TYPE_BETTER_POS = 'better_position';
32
33
    /**
34
     * List of data in the associative array returned.
35
     */
36
    const COL_EVENT = 'event';
37
    const COL_RECORD = 'record';
38
    const COL_OLD_RECORD = 'old_record';
39
    const COL_POS = 'position';
40
    const COL_OLD_POS = 'old_position';
41
    const COL_RECORDS = 'records';
42
43
    /** @var int */
44
    protected $nbRecords;
45
46
    /** @var string */
47
    protected $ordering;
48
49
    /** @var RecordQueryBuilder */
50
    protected $recordQueryBuilder;
51
52
    /** @var PlayerDb */
53
    protected $playerDb;
54
55
    /** @var Record[] */
56
    protected $records;
57
58
    /** @var Record[] */
59
    protected $recordsPerPlayer;
60
61
    /** @var int[] */
62
    protected $positionPerPlayer;
63
64
    /** @var int */
65
    protected $currentNbLaps;
66
67
    /** @var string */
68
    protected $currentMapUid;
69
70
    /**
71
     * RecordHandler constructor.
72
     *
73
     * @param RecordQueryBuilder $recordQueryBuilder
74
     * @param PlayerDb $playerDb
75
     * @param int $nbRecords
76
     * @param string $ordering
77
     */
78 14 View Code Duplication
    public function __construct(
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...
79
        RecordQueryBuilder $recordQueryBuilder,
80
        PlayerDb $playerDb,
81
        $nbRecords,
82
        $ordering = self::ORDER_ASC
83
    ) {
84 14
        $this->recordQueryBuilder = $recordQueryBuilder;
85 14
        $this->nbRecords = $nbRecords;
86 14
        $this->ordering = $ordering;
87 14
        $this->playerDb = $playerDb;
88 14
    }
89
90
    /**
91
     * @return Record[]
92
     */
93 7
    public function getRecords()
94
    {
95 7
        return $this->records;
96
    }
97
98
    /**
99
     * Get the position of a player
100
     *
101
     * @param string $login
102
     *
103
     * @return integer|null
104
     */
105 5
    public function getPlayerPosition($login)
106
    {
107 5
        return isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : null;
108
    }
109
110
    /**
111
     * Get a players record information.
112
     *
113
     * @param $login
114
     *
115
     * @return Record|null
116
     */
117 2
    public function getPlayerRecord($login)
118
    {
119 2
        return isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : null;
120
    }
121
122
    /**
123
     * Load records for a certain map.
124
     *
125
     * @param $mapUid
126
     * @param integer $nbLaps
127
     */
128 12
    public function loadForMap($mapUid, $nbLaps)
129
    {
130 12
        $this->recordsPerPlayer = [];
131 12
        $this->positionPerPlayer = [];
132
133 12
        $this->currentMapUid = $mapUid;
134 12
        $this->currentNbLaps = $nbLaps;
135
136 12
        $this->records = $this->recordQueryBuilder
137 12
            ->getMapRecords($mapUid, $nbLaps, $this->getScoreOrdering(), $this->nbRecords);
138
139 12
        $position = 1;
140 12
        foreach ($this->records as $record) {
141 11
            $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
142 11
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $position++;
143
        }
144 12
    }
145
146
    /**
147
     * Load records for certain players only.
148
     *
149
     * @param $mapUid
150
     * @param $nbLaps
151
     * @param $logins
152
     */
153 1
    public function loadForPlayers($mapUid, $nbLaps, $logins)
154
    {
155 1
        $logins = array_diff($logins, array_keys($this->recordsPerPlayer));
156
157 1
        if (!empty($logins)) {
158 1
            $records = $this->recordQueryBuilder->getPlayerMapRecords($mapUid, $nbLaps, $logins);
159
160 1
            foreach ($records as $record) {
161 1
                $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
162
            }
163
        }
164 1
    }
165
166
    /**
167
     * Save all new records.
168
     */
169
    public function save()
170
    {
171
        $con = Propel::getWriteConnection(RecordTableMap::DATABASE_NAME);
172
        $con->beginTransaction();
173
174
        foreach ($this->recordsPerPlayer as $record) {
175
            $record->save();
176
        }
177
178
        $con->commit();
179
180
        RecordTableMap::clearRelatedInstancePool();
181
        RecordTableMap::clearInstancePool();
182
    }
183
184
    /**
185
     * Add a new record
186
     *
187
     * @param string $login
188
     * @param int $score
189
     * @param int[] $checkpoints
190 12
     *
191
     * @return array|null Data for the new records.
192 12
     */
193 12
    public function addRecord($login, $score, $checkpoints)
194 12
    {
195
        $oldPosition = isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : count($this->records) + 1;
196 12
        $record = isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : $this->getNewRecord($login);
197 12
        $this->recordsPerPlayer[$login] = $record;
198
199 12
        $oldRecord = clone $record;
200 2
        $this->updateRecordStats($record, $score);
201 2
202 2
        if (empty($this->records)) {
203
            $record->setScore($score);
204 2
            $record->setCreatedAt(new \DateTime());
205 2
            $record->setCheckpoints($checkpoints);
206 2
207
            $this->records[0] = $record;
208
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = 1;
209 2
            $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
210 2
211 2
            return [
212 2
                self::COL_EVENT => self::EVENT_TYPE_FIRST_TIME,
213
                self::COL_RECORD => $record,
214
                self::COL_RECORDS => $this->records,
215
                self::COL_POS => 1,
216
            ];
217 12
        }
218
219 12
        // Check if first time of this player.
220
        $firstTime = is_null($record->getScore());
221 1
222 1
        if ($score == $record->getScore()) {
223 1
            return [
224 1
                self::COL_EVENT => self::EVENT_TYPE_SAME_SCORE,
225
                self::COL_RECORD => $record,
226
                self::COL_OLD_RECORD => $oldRecord,
227
                self::COL_RECORDS => $this->records,
228 11
            ];
229 9
        }
230 9
231
        if ($firstTime || $this->compareNewScore($record, $score)) {
232 9
            $recordIndex = $oldPosition - 1;
233 9
            $newPosition = $oldPosition;
234
235 9
            $this->records[$recordIndex] = $record;
236 5
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $oldPosition;
237
238 5
            while ($recordIndex > 0 && $this->compareNewScore($this->records[$recordIndex - 1], $score)) {
239 5
                $previousRecord = $this->records[$recordIndex - 1];
240
241 5
                $this->records[$recordIndex - 1] = $record;
242 5
                $this->records[$recordIndex] = $previousRecord;
243 5
244
                $newPosition = $recordIndex;
245 5
                $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $recordIndex;
246
                $this->positionPerPlayer[$previousRecord->getPlayer()->getLogin()] = $recordIndex + 1;
247
248 9
                $recordIndex--;
249 9
            }
250 9
251
            $record->setScore($score);
252
            $record->setUpdatedAt(new \DateTime());
253 9
            $record->setCheckpoints($checkpoints);
254
255 9
            // Remove entries whose position is superior to the limit.
256 8
            $this->records = array_slice($this->records, 0, $this->nbRecords);
257
258 6
            if ($newPosition <= $this->nbRecords) {
259 6
                if ($newPosition != $oldPosition || $firstTime) {
260 6
                    return [
261 6
                        self::COL_EVENT => self::EVENT_TYPE_BETTER_POS,
262 6
                        self::COL_RECORD => $record,
263 6
                        self::COL_OLD_RECORD => $oldRecord,
264
                        self::COL_RECORDS => $this->records,
265
                        self::COL_POS => $newPosition,
266
                        self::COL_OLD_POS => $firstTime ? null : $oldPosition,
267
                    ];
268 2
                }
269 2
270 2
                return [
271 2
                    self::COL_EVENT => self::EVENT_TYPE_SAME_POS,
272 2
                    self::COL_RECORD => $record,
273
                    self::COL_OLD_RECORD => $oldRecord,
274
                    self::COL_RECORDS => $this->records,
275
                    self::COL_POS => $newPosition,
276
                ];
277 5
            }
278
        }
279
280
        return null;
281
    }
282
283
    /**
284
     * Get a new record instance.
285
     *
286
     * @param string $login
287 6
     *
288
     * @return Record
289 6
     */
290 6
    protected function getNewRecord($login)
291 6
    {
292 6
        $record = new Record();
293 6
        $record->setPlayer($this->playerDb->get($login));
294
        $record->setNbLaps($this->currentNbLaps);
295 6
        $record->setNbFinish(0);
296
        $record->setMapUid($this->currentMapUid);
297
298
        return $record;
299
    }
300
301
    /**
302
     * Update Records statistics.
303
     *
304 12
     * @param Record $record
305
     * @param        integer $score
306 12
     */
307 12
    protected function updateRecordStats(Record $record, $score)
308
    {
309 12
        $record->setAvgScore(
310 12
            (($record->getAvgScore() * $record->getNbFinish()) + $score) / ($record->getNbFinish() + 1)
311
        );
312
        $record->setNbFinish($record->getNbFinish() + 1);
313
    }
314
315
316
    /**
317
     * Get ordering use for sorting.
318 13
     *
319
     * @return string
320 13
     */
321
    protected function getScoreOrdering()
322
    {
323
        return $this->ordering;
324
    }
325
326
    /**
327
     * @param int $newScore
328
     * @param Record $record
329 11
     *
330
     * @return bool
331 11
     */
332 10
    protected function compareNewScore($record, $newScore)
333
    {
334 1
        if ($this->getScoreOrdering() == self::ORDER_ASC) {
335
            return $newScore <= $record->getScore();
336
        } else {
337
            return $newScore >= $record->getScore();
338
        }
339
    }
340
}
341