Completed
Pull Request — master (#331)
by
unknown
03:33
created

RecordHandler   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 350
Duplicated Lines 3.14 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 89.83%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 3
dl 11
loc 350
ccs 106
cts 118
cp 0.8983
rs 9.2
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 11 11 1
A getCurrentNbLaps() 0 4 1
A getRecords() 0 4 1
A getPlayerPosition() 0 4 2
A getPlayerRecord() 0 4 2
B loadForMap() 0 29 4
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\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 14
     */
81 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 14
    ) {
87 14
        $this->recordQueryBuilder = $recordQueryBuilder;
88 14
        $this->nbRecords = $nbRecords;
89 14
        $this->ordering = $ordering;
90 14
        $this->playerDb = $playerDb;
91
    }
92
93
    /**
94
     * @return int
95 7
     */
96
    public function getCurrentNbLaps(): int
97 7
    {
98
        return $this->currentNbLaps;
99
    }
100
101
    /**
102
     * @return Record[]
103
     */
104
    public function getRecords()
105
    {
106
        return $this->records;
107 5
    }
108
109 5
    /**
110
     * Get the position of a player
111
     *
112
     * @param string $login
113
     *
114
     * @return integer|null
115
     */
116
    public function getPlayerPosition($login)
117
    {
118
        return isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : null;
119 2
    }
120
121 2
    /**
122
     * Get a players record information.
123
     *
124
     * @param $login
125
     *
126
     * @return Record|null
127
     */
128
    public function getPlayerRecord($login)
129
    {
130
        return isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : null;
131 12
    }
132
133
    /**
134 12
     * Load records for a certain map.
135
     *
136
     * @param string  $mapUid
137
     * @param integer $nbLaps
138 12
     * @throws \Propel\Runtime\Exception\PropelException
139
     */
140
    public function loadForMap($mapUid, $nbLaps)
141
    {
142 12
        // Free old records from memory first.
143
        foreach ($this->records as $record) {
144
            $record->clearAllReferences(false);
145 12
            unset($record);
146 12
        }
147
        foreach ($this->recordsPerPlayer as $record) {
148 12
            $record->clearAllReferences(false);
149 12
            unset($record);
150
        }
151 12
        RecordTableMap::clearInstancePool();
152 12
153
        // Load them amm new.
154 12
        $this->recordsPerPlayer = [];
155 12
        $this->positionPerPlayer = [];
156 11
157 11
        $this->currentMapUid = $mapUid;
158
        $this->currentNbLaps = $nbLaps;
159 12
160
        $this->records = $this->recordQueryBuilder
161
            ->getMapRecords($mapUid, $nbLaps, $this->getScoreOrdering(), $this->nbRecords->get());
162
163
        $position = 1;
164
        foreach ($this->records as $record) {
165
            $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
166
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $position++;
167
        }
168
    }
169 1
170
    /**
171 1
     * Load records for certain players only.
172
     *
173 1
     * @param $mapUid
174 1
     * @param $nbLaps
175
     * @param $logins
176 1
     * @throws \Propel\Runtime\Exception\PropelException
177 1
     */
178
    public function loadForPlayers($mapUid, $nbLaps, $logins)
179
    {
180 1
        $logins = array_diff($logins, array_keys($this->recordsPerPlayer));
181
182
        if (!empty($logins)) {
183
            $records = $this->recordQueryBuilder->getPlayerMapRecords($mapUid, $nbLaps, $logins);
184
185
            foreach ($records as $record) {
186
                $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
187
            }
188
        }
189
    }
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 12
     * Add a new record
213
     *
214 12
     * @param string $login
215 12
     * @param int    $score
216 12
     * @param int[]  $checkpoints
217
     *
218 12
     * @return array|null Data for the new records.
219 12
     * @throws \Propel\Runtime\Exception\PropelException
220
     */
221 12
    public function addRecord($login, $score, $checkpoints)
222 2
    {
223 2
        $oldPosition = isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : count($this->records) + 1;
224 2
        $record = isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : $this->getNewRecord($login);
225
        $this->recordsPerPlayer[$login] = $record;
226 2
227 2
        $oldRecord = clone $record;
228 2
        $this->updateRecordStats($record, $score);
229
230
        if (empty($this->records)) {
231 2
            $record->setScore($score);
232 2
            $record->setCreatedAt(new \DateTime());
233 2
            $record->setCheckpoints($checkpoints);
234 2
235
            $this->records[0] = $record;
236
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = 1;
237
            $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
238
239 12
            return [
240
                self::COL_EVENT => self::EVENT_TYPE_FIRST_TIME,
241 12
                self::COL_RECORD => $record,
242
                self::COL_RECORDS => $this->records,
243 1
                self::COL_POS => 1,
244 1
            ];
245 1
        }
246 1
247
        // Check if first time of this player.
248
        $firstTime = is_null($record->getScore());
249
250 11
        if ($score == $record->getScore()) {
251 9
            return [
252 9
                self::COL_EVENT => self::EVENT_TYPE_SAME_SCORE,
253
                self::COL_RECORD => $record,
254 9
                self::COL_OLD_RECORD => $oldRecord,
255 9
                self::COL_RECORDS => $this->records,
256
            ];
257 9
        }
258 5
259
        if ($firstTime || $this->compareNewScore($record, $score)) {
260 5
            $recordIndex = $oldPosition - 1;
261 5
            $newPosition = $oldPosition;
262
263 5
            $this->records[$recordIndex] = $record;
264 5
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $oldPosition;
265 5
266
            while ($recordIndex > 0 && $this->compareNewScore($this->records[$recordIndex - 1], $score)) {
267 5
                $previousRecord = $this->records[$recordIndex - 1];
268
269
                $this->records[$recordIndex - 1] = $record;
270 9
                $this->records[$recordIndex] = $previousRecord;
271 9
272 9
                $newPosition = $recordIndex;
273
                $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $recordIndex;
274
                $this->positionPerPlayer[$previousRecord->getPlayer()->getLogin()] = $recordIndex + 1;
275 9
276
                $recordIndex--;
277 9
            }
278 8
279
            $record->setScore($score);
280 6
            $record->setUpdatedAt(new \DateTime());
281 6
            $record->setCheckpoints($checkpoints);
282 6
283 6
            // Remove entries whose position is superior to the limit.
284 6
            $this->records = array_slice($this->records, 0, $this->nbRecords->get());
285 6
286
            if ($newPosition <= $this->nbRecords->get()) {
287
                if ($newPosition != $oldPosition || $firstTime) {
288
                    return [
289
                        self::COL_EVENT => self::EVENT_TYPE_BETTER_POS,
290 2
                        self::COL_RECORD => $record,
291 2
                        self::COL_OLD_RECORD => $oldRecord,
292 2
                        self::COL_RECORDS => $this->records,
293 2
                        self::COL_POS => $newPosition,
294 2
                        self::COL_OLD_POS => $firstTime ? null : $oldPosition,
295
                    ];
296
                }
297
298
                return [
299 5
                    self::COL_EVENT => self::EVENT_TYPE_SAME_POS,
300
                    self::COL_RECORD => $record,
301
                    self::COL_OLD_RECORD => $oldRecord,
302
                    self::COL_RECORDS => $this->records,
303
                    self::COL_POS => $newPosition,
304
                ];
305
            }
306
        }
307
308
        return null;
309 6
    }
310
311 6
    /**
312 6
     * Get a new record instance.
313 6
     *
314 6
     * @param string $login
315 6
     *
316
     * @return Record
317 6
     */
318
    protected function getNewRecord($login)
319
    {
320
        $record = new Record();
321
        $record->setPlayer($this->playerDb->get($login));
322
        $record->setNbLaps($this->currentNbLaps);
323
        $record->setNbFinish(0);
324
        $record->setMapUid($this->currentMapUid);
325
326 12
        return $record;
327
    }
328 12
329 12
    /**
330
     * Update Records statistics.
331 12
     *
332 12
     * @param Record         $record
333
     * @param        integer $score
334
     */
335
    protected function updateRecordStats(Record $record, $score)
336
    {
337
        $record->setAvgScore(
338
            (($record->getAvgScore() * $record->getNbFinish()) + $score) / ($record->getNbFinish() + 1)
339
        );
340 13
        $record->setNbFinish($record->getNbFinish() + 1);
341
    }
342 13
343
344
    /**
345
     * Get ordering use for sorting.
346
     *
347
     * @return string
348
     */
349
    protected function getScoreOrdering()
350
    {
351 11
        return $this->ordering;
352
    }
353 11
354 10
    /**
355
     * @param int    $newScore
356 1
     * @param Record $record
357
     *
358
     * @return bool
359
     */
360
    protected function compareNewScore($record, $newScore)
361
    {
362
        if ($this->getScoreOrdering() == self::ORDER_ASC) {
363
            return $newScore <= $record->getScore();
364
        } else {
365
            return $newScore >= $record->getScore();
366
        }
367
    }
368
}
369