RecordHandler   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 350
Duplicated Lines 3.14 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 88.33%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 3
dl 11
loc 350
ccs 106
cts 120
cp 0.8833
rs 9.68
c 0
b 0
f 0

13 Methods

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