Completed
Push — master ( 66eb65...e1591a )
by
unknown
9s
created

RecordHandler::loadForMap()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4.1492

Importance

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