Completed
Push — master ( f30e4c...e7851f )
by
unknown
16s
created

RecordHandler::getNewRecord()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
crap 1
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
    public function __construct(
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 12
        foreach ($this->records as $record) {
133
            unset($record);
134
        }
135
136 12
        foreach ($this->recordsPerPlayer as $record) {
137
            unset($record);
138
        }
139
140 12
        RecordTableMap::clearInstancePool();
141
142 12
        $this->recordsPerPlayer = [];
143 12
        $this->positionPerPlayer = [];
144
145 12
        $this->currentMapUid = $mapUid;
146 12
        $this->currentNbLaps = $nbLaps;
147
148 12
        $this->records = $this->recordQueryBuilder
149 12
            ->getMapRecords($mapUid, $nbLaps, $this->getScoreOrdering(), $this->nbRecords);
150
151 12
        $position = 1;
152 12
        foreach ($this->records as $record) {
153 11
            $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
154 11
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $position++;
155
        }
156 12
    }
157
158
    /**
159
     * Load records for certain players only.
160
     *
161
     * @param $mapUid
162
     * @param $nbLaps
163
     * @param $logins
164
     * @throws \Propel\Runtime\Exception\PropelException
165
     */
166 1
    public function loadForPlayers($mapUid, $nbLaps, $logins)
167
    {
168 1
        $logins = array_diff($logins, array_keys($this->recordsPerPlayer));
169
170 1
        if (!empty($logins)) {
171 1
            $records = $this->recordQueryBuilder->getPlayerMapRecords($mapUid, $nbLaps, $logins);
172
173 1
            foreach ($records as $record) {
174 1
                $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
175
            }
176
        }
177 1
    }
178
179
    /**
180
     * Save all new records.
181
     * @param bool $releaseRecords
182
     * @throws \Propel\Runtime\Exception\PropelException
183
     */
184
    public function save($releaseRecords = true)
185
    {
186
        PlayerTableMap::clearInstancePool();
187
        RecordTableMap::clearInstancePool();
188
189
        $con = Propel::getWriteConnection(RecordTableMap::DATABASE_NAME);
190
        $con->beginTransaction();
191
192
        foreach ($this->recordsPerPlayer as $record) {
193
            $record->save();
194
            if ($releaseRecords) {
195
                $record->setPlayer(null);
196
                unset($record);
197
            }
198
199
        }
200
201
        foreach ($this->records as $record) {
202
            if ($releaseRecords) {
203
                $record->setPlayer(null);
204
                unset($record);
205
            }
206
        }
207
208
        $con->commit();
209
210
        RecordTableMap::clearInstancePool();
211
212
    }
213
214
    /**
215
     * Add a new record
216
     *
217
     * @param string $login
218
     * @param int    $score
219
     * @param int[]  $checkpoints
220
     *
221
     * @return array|null Data for the new records.
222
     * @throws \Propel\Runtime\Exception\PropelException
223
     */
224 12
    public function addRecord($login, $score, $checkpoints)
225
    {
226 12
        $oldPosition = isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : count($this->records) + 1;
227 12
        $record = isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : $this->getNewRecord($login);
228 12
        $this->recordsPerPlayer[$login] = $record;
229
230 12
        $oldRecord = clone $record;
231 12
        $this->updateRecordStats($record, $score);
232
233 12
        if (empty($this->records)) {
234 2
            $record->setScore($score);
235 2
            $record->setCreatedAt(new \DateTime());
236 2
            $record->setCheckpoints($checkpoints);
237
238 2
            $this->records[0] = $record;
239 2
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = 1;
240 2
            $this->recordsPerPlayer[$record->getPlayer()->getLogin()] = $record;
241
242
            return [
243 2
                self::COL_EVENT => self::EVENT_TYPE_FIRST_TIME,
244 2
                self::COL_RECORD => $record,
245 2
                self::COL_RECORDS => $this->records,
246 2
                self::COL_POS => 1,
247
            ];
248
        }
249
250
        // Check if first time of this player.
251 12
        $firstTime = is_null($record->getScore());
252
253 12
        if ($score == $record->getScore()) {
254
            return [
255 1
                self::COL_EVENT => self::EVENT_TYPE_SAME_SCORE,
256 1
                self::COL_RECORD => $record,
257 1
                self::COL_OLD_RECORD => $oldRecord,
258 1
                self::COL_RECORDS => $this->records,
259
            ];
260
        }
261
262 11
        if ($firstTime || $this->compareNewScore($record, $score)) {
263 9
            $recordIndex = $oldPosition - 1;
264 9
            $newPosition = $oldPosition;
265
266 9
            $this->records[$recordIndex] = $record;
267 9
            $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $oldPosition;
268
269 9
            while ($recordIndex > 0 && $this->compareNewScore($this->records[$recordIndex - 1], $score)) {
270 5
                $previousRecord = $this->records[$recordIndex - 1];
271
272 5
                $this->records[$recordIndex - 1] = $record;
273 5
                $this->records[$recordIndex] = $previousRecord;
274
275 5
                $newPosition = $recordIndex;
276 5
                $this->positionPerPlayer[$record->getPlayer()->getLogin()] = $recordIndex;
277 5
                $this->positionPerPlayer[$previousRecord->getPlayer()->getLogin()] = $recordIndex + 1;
278
279 5
                $recordIndex--;
280
            }
281
282 9
            $record->setScore($score);
283 9
            $record->setUpdatedAt(new \DateTime());
284 9
            $record->setCheckpoints($checkpoints);
285
286
            // Remove entries whose position is superior to the limit.
287 9
            $this->records = array_slice($this->records, 0, $this->nbRecords);
288
289 9
            if ($newPosition <= $this->nbRecords) {
290 8
                if ($newPosition != $oldPosition || $firstTime) {
291
                    return [
292 6
                        self::COL_EVENT => self::EVENT_TYPE_BETTER_POS,
293 6
                        self::COL_RECORD => $record,
294 6
                        self::COL_OLD_RECORD => $oldRecord,
295 6
                        self::COL_RECORDS => $this->records,
296 6
                        self::COL_POS => $newPosition,
297 6
                        self::COL_OLD_POS => $firstTime ? null : $oldPosition,
298
                    ];
299
                }
300
301
                return [
302 2
                    self::COL_EVENT => self::EVENT_TYPE_SAME_POS,
303 2
                    self::COL_RECORD => $record,
304 2
                    self::COL_OLD_RECORD => $oldRecord,
305 2
                    self::COL_RECORDS => $this->records,
306 2
                    self::COL_POS => $newPosition,
307
                ];
308
            }
309
        }
310
311 5
        return null;
312
    }
313
314
    /**
315
     * Get a new record instance.
316
     *
317
     * @param string $login
318
     *
319
     * @return Record
320
     */
321 6
    protected function getNewRecord($login)
322
    {
323 6
        $record = new Record();
324 6
        $record->setPlayer($this->playerDb->get($login));
325 6
        $record->setNbLaps($this->currentNbLaps);
326 6
        $record->setNbFinish(0);
327 6
        $record->setMapUid($this->currentMapUid);
328
329 6
        return $record;
330
    }
331
332
    /**
333
     * Update Records statistics.
334
     *
335
     * @param Record         $record
336
     * @param        integer $score
337
     */
338 12
    protected function updateRecordStats(Record $record, $score)
339
    {
340 12
        $record->setAvgScore(
341 12
            (($record->getAvgScore() * $record->getNbFinish()) + $score) / ($record->getNbFinish() + 1)
342
        );
343 12
        $record->setNbFinish($record->getNbFinish() + 1);
344 12
    }
345
346
347
    /**
348
     * Get ordering use for sorting.
349
     *
350
     * @return string
351
     */
352 13
    protected function getScoreOrdering()
353
    {
354 13
        return $this->ordering;
355
    }
356
357
    /**
358
     * @param int    $newScore
359
     * @param Record $record
360
     *
361
     * @return bool
362
     */
363 11
    protected function compareNewScore($record, $newScore)
364
    {
365 11
        if ($this->getScoreOrdering() == self::ORDER_ASC) {
366 10
            return $newScore <= $record->getScore();
367
        } else {
368 1
            return $newScore >= $record->getScore();
369
        }
370
    }
371
}
372