GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

AbstractSql   D
last analyzed

Complexity

Total Complexity 115

Size/Duplication

Total Lines 1095
Duplicated Lines 16.44 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 115
lcom 1
cbo 10
dl 180
loc 1095
rs 4.4118
c 0
b 0
f 0

58 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getDbConn() 0 4 1
A getUpcomingCount() 0 4 1
A setUpcomingCount() 0 4 1
A getLogger() 0 8 2
A setLogger() 0 4 1
A fetchSongRowById() 0 9 2
A expandTicketData() 0 15 3
A expandTicketsData() 0 8 2
B expandSongData() 0 30 2
A isPotentialCodeNumber() 0 7 1
A generatePerformerStats() 0 14 1
B storeBandToTicket() 0 31 6
A fetchPerformerIdByName() 0 15 3
A fetchPerformersByTicketId() 0 19 4
B fetchPerformersWithInstrumentByTicketId() 0 30 5
A findSongsBySearchString() 0 49 3
A markTicketUsedById() 0 5 1
A deleteTicketById() 0 5 1
A updateTicketOffsetById() 0 20 3
A fetchSongByKey() 0 11 2
A fetchUpcomingTickets() 0 15 2
A fetchUndeletedTickets() 9 10 1
A fetchPerformedTickets() 9 10 1
A getQueueEntriesForSongId() 12 12 1
A storeNewTicket() 0 21 3
B normaliseSongRecord() 3 20 5
A updateTicketById() 0 8 2
A fetchTicketById() 7 7 1
A fetchSetting() 0 7 2
A settingExists() 0 7 1
A updateSetting() 0 10 2
concatenateEscapedFields() 0 1 ?
A normaliseTicketRecord() 3 15 4
A resetAllSessionData() 0 16 2
A resetCatalogue() 0 21 3
A storeInstrument() 0 7 2
A storePlatform() 0 7 2
A storeSource() 0 7 2
A storeSong() 0 9 2
A fetchSourceByName() 17 17 2
A fetchSourceById() 17 17 2
A fetchPlatformByName() 17 17 2
A fetchAllPlatforms() 0 16 2
A fetchAllInstruments() 0 16 2
A storeSongPlatformLinks() 8 8 2
A fetchInstrumentByName() 17 17 2
A fetchInstrumentByAbbreviation() 17 17 2
A storeSongInstrumentLinks() 8 8 2
A fetchInstrumentsForSongId() 18 18 1
A fetchPlatformsForSongId() 18 18 1
A buildInstrumentFromDbRow() 0 10 1
A buildPlatformFromDbRow() 0 8 1
A buildSourceFromDbRow() 0 8 1
A songToDbRow() 0 14 2
A sourceToDbRow() 0 10 2
A instrumentToDbRow() 0 12 2
A platformToDbRow() 0 10 2

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractSql often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractSql, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: wechsler
5
 * Date: 03/09/15
6
 * Time: 20:45
7
 */
8
9
namespace Phase\TakeATicket\DataSource;
10
11
use Doctrine\DBAL\Connection;
12
use PDO;
13
use Phase\TakeATicket\Model\Instrument;
14
use Phase\TakeATicket\Model\Platform;
15
use Phase\TakeATicket\Model\Song;
16
use Phase\TakeATicket\Model\Source;
17
use Psr\Log\LoggerInterface;
18
use Psr\Log\NullLogger;
19
20
/**
21
 * Base Datasource
22
 *
23
 * Onward naming convention for methods (note that some are currently misnamed)
24
 *
25
 * get*() property getter
26
 * set*() property setter
27
 *
28
 * fetch*Row() Returns raw DB content as array
29
 * fetch*Data() Returns non-object data that's not just homogenous rows
30
 * fetch[ModelName]() Returns model objects
31
 *
32
 * protected build[ModelName]FromDbRow() Return single model object from array
33
 * protected [modelName]ToDbRow() Return single array suitable for DB insertion from model object
34
 */
35
abstract class AbstractSql
36
{
37
    const TICKETS_TABLE = 'tickets';
38
    const PERFORMERS_TABLE = 'performers';
39
    const TICKETS_X_PERFORMERS_TABLE = 'tickets_x_performers';
40
    const CODE_LENGTH = 6;
41
42
    /**
43
     * @var Connection
44
     */
45
    protected $dbConn;
46
    /**
47
     * @var int
48
     */
49
    protected $upcomingCount = 3;
50
51
    /**
52
     * @var LoggerInterface
53
     */
54
    protected $logger;
55
56
    /**
57
     * DataSource constructor.
58
     *
59
     * @param $dbConn
60
     */
61
    public function __construct(Connection $dbConn)
62
    {
63
        $this->dbConn = $dbConn;
64
    }
65
66
    /**
67
     * @return Connection
68
     */
69
    public function getDbConn()
70
    {
71
        return $this->dbConn;
72
    }
73
74
    /**
75
     * @return int
76
     */
77
    public function getUpcomingCount()
78
    {
79
        return $this->upcomingCount;
80
    }
81
82
    /**
83
     * @param int $upcomingCount
84
     */
85
    public function setUpcomingCount($upcomingCount)
86
    {
87
        $this->upcomingCount = $upcomingCount;
88
    }
89
90
    /**
91
     * @return LoggerInterface
92
     */
93
    public function getLogger()
94
    {
95
        if (!$this->logger) {
96
            $this->logger = new NullLogger();
97
        }
98
99
        return $this->logger;
100
    }
101
102
    /**
103
     * @param LoggerInterface $logger
104
     */
105
    public function setLogger(LoggerInterface $logger)
106
    {
107
        $this->logger = $logger;
108
    }
109
110
    /**
111
     * @param $songId
112
     *
113
     * @return array
114
     */
115
    public function fetchSongRowById($songId)
116
    {
117
        $song = $this->getDbConn()->fetchAssoc('SELECT * FROM songs WHERE id = :code', ['code' => $songId]);
118
        if ($song) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $song of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
119
            $song = $this->normaliseSongRecord($song);
120
        }
121
122
        return $song;
123
    }
124
125
    /**
126
     * Fill out names of linked object ids for given ticket
127
     *
128
     * @param $ticket
129
     *
130
     * @return mixed
131
     */
132
    public function expandTicketData($ticket)
133
    {
134
        $ticket = $this->normaliseTicketRecord($ticket);
135
136
        if ($ticket['songId']) {
137
            $song = $this->fetchSongRowById($ticket['songId']);
138
            if ($song) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $song of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
139
                $ticket['song'] = $this->expandSongData($song);
140
            }
141
        }
142
        //FIXED inefficient, but different pages expect different structure while we refactor
143
        $ticket['band'] = $this->fetchPerformersWithInstrumentByTicketId($ticket['id']);
144
145
        return $ticket;
146
    }
147
148
    /**
149
     * expandTicketData for multiple tickets
150
     *
151
     * @param $tickets
152
     *
153
     * @return mixed
154
     */
155
    public function expandTicketsData($tickets)
156
    {
157
        foreach ($tickets as &$ticket) {
158
            $ticket = $this->expandTicketData($ticket);
159
        }
160
161
        return $tickets;
162
    }
163
164
165
    /**
166
     * Fill out names of linked object ids for given song
167
     *
168
     * @param $song
169
     * @return mixed
170
     */
171
    public function expandSongData($song)
172
    {
173
        $dataStore = $this;
174
        $instruments = $dataStore->fetchInstrumentsForSongId($song['id']);
175
        $instruments = array_map(
176
            function (Instrument $instrument) {
177
                return $instrument->jsonSerialize();
178
            },
179
            $instruments
180
        );
181
        $song['instruments'] = $instruments;
182
183
        $source = $dataStore->fetchSourceById($song['sourceId']);
184
        if ($source) {
185
            $song['source'] = $source->getName();
186
        }
187
188
        $platforms = $dataStore->fetchPlatformsForSongId($song['id']);
189
        $platforms = array_map(
190
            function (Platform $platform) {
191
                return $platform->getName();
192
            },
193
            $platforms
194
        );
195
        $song['platforms'] = $platforms;
196
        //Legacy format - TODO remove this, use ['song']['platforms']
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
197
        $song['inRb3'] = in_array('RB3', $song['platforms']);
198
        $song['inRb4'] = in_array('RB4', $song['platforms']);
199
        return $song;
200
    }
201
202
    public function isPotentialCodeNumber($searchString)
203
    {
204
        $codeLength = (int)self::CODE_LENGTH;
205
        $regexp = '/^[a-f0-9]{' . $codeLength . '}$/i';
206
207
        return preg_match($regexp, $searchString);
208
    }
209
210
    /**
211
     * Get performed, pending data for all performers
212
     *
213
     * @return array
214
     */
215
    public function generatePerformerStats()
216
    {
217
        $conn = $this->getDbConn();
218
        $sql = 'SELECT p.id AS performerId, p.name AS performerName,
219
                  sum(CASE WHEN t.id IS NOT NULL AND t.used=0 AND t.deleted=0 THEN 1 ELSE 0 END) AS songsPending,
220
                  sum(CASE WHEN t.id IS NOT NULL AND t.used=1 AND t.deleted=0 THEN 1 ELSE 0 END) AS songsDone,
221
                  sum(CASE WHEN t.id IS NOT NULL AND t.deleted=0 THEN 1 ELSE 0 END) AS songsTotal
222
               FROM performers p
223
                    LEFT OUTER JOIN tickets_x_performers txp ON p.id=txp.performerId
224
                    LEFT OUTER JOIN tickets t ON txp.ticketId = t.id
225
                GROUP BY p.id ORDER BY p.name';
226
227
        return $conn->fetchAll($sql);
228
    }
229
230
    /**
231
     * Save band to ticket
232
     *
233
     * @param $ticketId
234
     * @param array $band ['instrumentCode' => 'name'] FIXME update
235
     */
236
    public function storeBandToTicket($ticketId, $band)
237
    {
238
        if (!is_array($band)) {
239
            throw new \InvalidArgumentException('Band must be array');
240
        }
241
242
        // remove existing performers
243
        $this->getDbConn()->delete(self::TICKETS_X_PERFORMERS_TABLE, ['ticketId' => $ticketId]);
244
245
        foreach ($band as $instrumentCode => $performers) {
246
            $instrument = $this->fetchInstrumentByAbbreviation($instrumentCode);
247
            if ($instrument) {
248
                $instrumentId = $instrument->getId();
249
250
                foreach ($performers as $performerName) {
251
                    $performerName = trim($performerName);
252
                    $performerId = $this->fetchPerformerIdByName($performerName, true);
253
                    if ($performerId) {
254
                        $link = [
255
                            'ticketId' => $ticketId,
256
                            'performerId' => $performerId,
257
                            'instrumentId' => $instrumentId
258
                        ];
259
                        $this->getDbConn()->insert(self::TICKETS_X_PERFORMERS_TABLE, $link);
260
                    }
261
                }
262
            } else {
263
                throw new \UnexpectedValueException("Unknown instrument abbreviation '$instrumentCode'");
264
            }
265
        }
266
    }
267
268
    public function fetchPerformerIdByName($performerName, $createMissing = false)
269
    {
270
        $conn = $this->getDbConn();
271
        $sql = 'SELECT id FROM performers p WHERE p.name LIKE :name LIMIT 1';
272
        $performerId = $conn->fetchColumn($sql, ['name' => $performerName]);
273
274
        if ($createMissing && !$performerId) {
275
            $max = $conn->fetchColumn('SELECT max(id) FROM performers');
276
            $performerId = $max + 1;
277
            $conn->insert(self::PERFORMERS_TABLE, ['id' => $performerId, 'name' => ucwords($performerName)]);
278
            //add new performer row
279
        }
280
281
        return $performerId;
282
    }
283
284
    /**
285
     * Fetch all performers on a song with their stats
286
     *
287
     * @deprecated Use fetchPerformersWithInstrumentByTicketId()
288
     *
289
     * @param $ticketId
290
     *
291
     * @return array[]
292
     */
293
    public function fetchPerformersByTicketId($ticketId)
294
    {
295
        $ticketPerformerSql = 'SELECT performerId FROM tickets_x_performers WHERE ticketId = :ticketId';
296
        $performerRows = $this->getDbConn()->fetchAll($ticketPerformerSql, ['ticketId' => $ticketId]);
297
        $performerIds = [];
298
        foreach ($performerRows as $row) {
299
            $performerIds[] = $row['performerId'];
300
        }
301
302
        $allPerformers = $this->generatePerformerStats();
303
        $trackPerformers = [];
304
        foreach ($allPerformers as $performer) {
305
            if (in_array($performer['performerId'], $performerIds)) {
306
                $trackPerformers[] = $performer;
307
            }
308
        }
309
310
        return $trackPerformers;
311
    }
312
313
    public function fetchPerformersWithInstrumentByTicketId($ticketId)
314
    {
315
        $ticketPerformerSql = 'SELECT x.performerId, i.abbreviation AS instrument
316
          FROM tickets_x_performers x INNER JOIN instruments i ON x.instrumentId = i.id
317
          WHERE x.ticketId = :ticketId';
318
        $performerRows = $this->getDbConn()->fetchAll($ticketPerformerSql, ['ticketId' => $ticketId]);
319
        $performerIds = [];
320
        $instrumentsByPerformer = [];
321
        foreach ($performerRows as $row) {
322
            $performerId = $row['performerId'];
323
            $performerIds[] = $performerId;
324
            $instrumentsByPerformer[$performerId] = $row['instrument'];
325
        }
326
327
        //todo Can probably clean up this algorithm
328
        $allPerformers = $this->generatePerformerStats();
329
        $trackPerformersByInstrument = [];
330
        foreach ($allPerformers as $performer) {
331
            $performerId = $performer['performerId'];
332
            if (in_array($performerId, $performerIds)) {
333
                $instrument = $instrumentsByPerformer[$performerId];
334
                if (!isset($trackPerformersByInstrument[$instrument])) {
335
                    $trackPerformersByInstrument[$instrument] = [];
336
                }
337
                $trackPerformersByInstrument[$instrument][] = $performer;
338
            }
339
        }
340
341
        return $trackPerformersByInstrument;
342
    }
343
344
    /**
345
     * @param $searchString
346
     * @param $howMany
347
     *
348
     * @return array
349
     */
350
    public function findSongsBySearchString($searchString, $howMany = 10)
351
    {
352
        $howMany += 0; // force int
353
354
        $conn = $this->getDbConn();
355
        $leadingPattern = implode('%', preg_split('/\s+/', $searchString)) . '%';
356
        $internalPattern = '%' . $leadingPattern;
357
        $params = [
358
            'internalPattern' => $internalPattern,
359
            'leadingPattern' => $leadingPattern,
360
            'searchString' => $searchString,
361
        ];
362
363
        // this may be unnecessary - chances of a code number hitting anything else is minimal
364
        if ($this->isPotentialCodeNumber($searchString)) {
365
            $sql = "SELECT s.*, max(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) as queued
366
            FROM songs s
367
            LEFT OUTER JOIN tickets t ON s.id = t.songId AND t.deleted=0
368
            WHERE (s.title = :searchString)
369
            OR (codeNumber = :searchString)
370
            GROUP BY s.id
371
            ORDER BY artist, title
372
            LIMIT $howMany";
373
            //allow title just in case
374
        } else {
375
            $matchingTokens = ['s.title', '" "', 's.artist'];
376
            $concatSearchFields = $this->concatenateEscapedFields($matchingTokens);
377
            $concatSearchFieldsReverse = $this->concatenateEscapedFields(array_reverse($matchingTokens));
378
            $sql = "SELECT s.*, max(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) as queued
379
            FROM songs s
380
            LEFT OUTER JOIN tickets t ON s.id = t.songId AND t.deleted=0
381
            WHERE ( $concatSearchFields LIKE :internalPattern)
382
            OR ($concatSearchFieldsReverse LIKE :internalPattern)
383
            OR (codeNumber LIKE :leadingPattern)
384
            OR (s.id = :searchString)
385
            GROUP BY s.id
386
            ORDER BY artist, title
387
            LIMIT $howMany";
388
        }
389
390
        $songs = $conn->fetchAll($sql, $params);
391
392
        // normalise data types
393
        foreach ($songs as &$song) {
394
            $song = $this->normaliseSongRecord($song);
395
        }
396
397
        return $songs;
398
    }
399
400
    /**
401
     * @param $id
402
     *
403
     * @return int
404
     */
405
    public function markTicketUsedById($id)
406
    {
407
        $conn = $this->getDbConn();
408
        return $conn->update(self::TICKETS_TABLE, ['used' => 1, 'startTime' => time()], ['id' => $id]);
409
    }
410
411
    /**
412
     * @param $id
413
     *
414
     * @return int
415
     */
416
    public function deleteTicketById($id)
417
    {
418
        $conn = $this->getDbConn();
419
        return $conn->update(self::TICKETS_TABLE, ['deleted' => 1], ['id' => $id]);
420
    }
421
422
    /**
423
     * @param $id
424
     * @param $offset
425
     *
426
     * @return mixed
427
     */
428
    public function updateTicketOffsetById($id, $offset)
429
    {
430
        $id = (int)$id;
431
        $offset = (int)$offset;
432
        $fields = ['offset' => $offset];
433
        $currentTrack = $this->fetchTicketById($id);
434
        $oldOffset = (int)$currentTrack['offset'];
435
        $ok = ($oldOffset === $offset);
436
437
        $this->getLogger()->debug(
438
            "Update track $id offset: $oldOffset => $offset: " .
439
            ($ok ? ' already set' : ' will update')
440
        );
441
442
        if (!$ok) {
443
            $ok = $this->updateTicketById($id, $fields);
444
        }
445
446
        return $ok;
447
    }
448
449
    /**
450
     * @param $songKey
451
     *
452
     * @return array
453
     */
454
    public function fetchSongByKey($songKey)
455
    {
456
        $conn = $this->getDbConn();
457
458
        $song = $conn->fetchAssoc('SELECT * FROM songs WHERE codeNumber = :code', ['code' => $songKey]);
459
        if ($song) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $song of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
460
            $song = $this->normaliseSongRecord($song);
461
        }
462
463
        return $song;
464
    }
465
466
    /**
467
     * @param bool $includePrivate
468
     *
469
     * @return array|mixed
470
     *
471
     * @throws \Doctrine\DBAL\DBALException
472
     */
473
    public function fetchUpcomingTickets($includePrivate = false)
474
    {
475
        $conn = $this->getDbConn();
476
        $privateClause = $includePrivate ? '' : ' AND private = 0 ';
477
        $statement = $conn->prepare(
478
            'SELECT * FROM tickets WHERE deleted=0 AND used=0 ' .
479
            $privateClause .
480
            'ORDER BY OFFSET ASC LIMIT ' . (int)$this->upcomingCount
481
        );
482
        $statement->execute();
483
        $next = $statement->fetchAll();
484
        $next = $this->expandTicketsData($next);
485
486
        return $next;
487
    }
488
489
    /**
490
     * Fetch all non-deleted tickets in offset order
491
     *
492
     * @return array|mixed
493
     *
494
     * @throws \Doctrine\DBAL\DBALException
495
     */
496 View Code Duplication
    public function fetchUndeletedTickets()
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...
497
    {
498
        $conn = $this->getDbConn();
499
        $statement = $conn->prepare('SELECT * FROM tickets WHERE deleted=0 ORDER BY offset ASC');
500
        $statement->execute();
501
        $tickets = $statement->fetchAll();
502
        $tickets = $this->expandTicketsData($tickets);
503
504
        return $tickets;
505
    }
506
507
    /**
508
     * Fetch all performed tickets in offset order
509
     *
510
     * @return array
511
     *
512
     * @throws \Doctrine\DBAL\DBALException
513
     */
514 View Code Duplication
    public function fetchPerformedTickets()
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...
515
    {
516
        $conn = $this->getDbConn();
517
        $statement = $conn->prepare('SELECT * FROM tickets WHERE deleted=0 AND used=1 ORDER BY offset ASC');
518
        $statement->execute();
519
        $tickets = $statement->fetchAll();
520
        $tickets = $this->expandTicketsData($tickets);
521
522
        return $tickets;
523
    }
524
525
    /**
526
     * Check whether song is already in the queue
527
     *
528
     * @param $songId
529
     * @return array|mixed
530
     * @throws \Doctrine\DBAL\DBALException
531
     */
532 View Code Duplication
    public function getQueueEntriesForSongId($songId)
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...
533
    {
534
        $conn = $this->getDbConn();
535
        $statement = $conn->prepare(
536
            'SELECT * FROM tickets WHERE deleted=0 AND songId=:songId ORDER BY offset ASC'
537
        );
538
        $statement->execute(['songId' => $songId]);
539
        $tickets = $statement->fetchAll();
540
        $tickets = $this->expandTicketsData($tickets);
541
542
        return $tickets;
543
    }
544
545
    /**
546
     * @param $title
547
     * @param $songId
548
     *
549
     * @param null $userId
550
     * @return false|int Row ID
551
     */
552
    public function storeNewTicket($title, $songId, $userId = null)
553
    {
554
        $conn = $this->getDbConn();
555
        $max = $conn->fetchAssoc('SELECT max(offset) AS o, max(id) AS i FROM tickets');
556
557
        $maxOffset = $max['o'];
558
        $maxId = $max['i'];
559
        $ticket = [
560
            'title' => $title,
561
            'id' => $maxId + 1,
562
            'offset' => $maxOffset + 1,
563
            'songId' => $songId,
564
        ];
565
566
        if ($userId) {
567
            $ticket['createdBy'] = $userId;
568
        }
569
        $res = $conn->insert(self::TICKETS_TABLE, $ticket);
570
571
        return $res ? $ticket['id'] : false;
572
    }
573
574
    /**
575
     * Normalise datatypes returned in song query
576
     *
577
     * @param $song
578
     *
579
     * @return mixed
580
     */
581
    public function normaliseSongRecord($song)
582
    {
583
        $boolFields = [];
584
        $intFields = ['id', 'duration', 'sourceId'];
585
586 View Code Duplication
        foreach ($intFields as $k) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
587
            $song[$k] = is_null($song[$k]) ? null : (int)$song[$k];
588
        }
589
590
        foreach ($boolFields as $k) {
591
            $song[$k] = (bool)$song[$k];
592
        }
593
594
        // Search API adds a 'queued' parameter to show if song is taken
595
        if (isset($song['queued'])) { //TODO see if this is safe to move to $boolFields
596
            $song['queued'] = (bool)$song['queued'];
597
        }
598
599
        return $song;
600
    }
601
602
    /**
603
     * @param $id
604
     * @param $fields
605
     *
606
     * @return int Number of updated rows
607
     */
608
    public function updateTicketById($id, $fields)
609
    {
610
        if (isset($fields['id'])) {
611
            throw new \InvalidArgumentException('Fields must not include id');
612
        }
613
614
        return $this->getDbConn()->update(self::TICKETS_TABLE, $fields, ['id' => $id]);
615
    }
616
617
    /**
618
     * Fetch core ticket data (not band, etc) by ticket id
619
     *
620
     * @param $id
621
     *
622
     * @return array
623
     */
624 View Code Duplication
    public function fetchTicketById($id)
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...
625
    {
626
        $conn = $this->getDbConn();
627
        $song = $conn->fetchAssoc('SELECT * FROM ' . self::TICKETS_TABLE . ' WHERE id = :id', ['id' => $id]);
628
629
        return $song;
630
    }
631
632
    /**
633
     * Get current value of a named setting, NULL if missing
634
     *
635
     * @param  $key
636
     * @return mixed|null
637
     */
638
    public function fetchSetting($key)
639
    {
640
        $conn = $this->getDbConn();
641
        $query = $conn->executeQuery('SELECT settingValue FROM settings WHERE settingKey=:key', ['key' => $key]);
642
643
        return $query->rowCount() ? $query->fetchColumn() : null;
644
    }
645
646
647
    public function settingExists($key)
648
    {
649
        $conn = $this->getDbConn();
650
        $value = $conn->fetchColumn('SELECT 1 FROM settings WHERE settingKey=:key', ['key' => $key]);
651
652
        return (bool)$value;
653
    }
654
655
    public function updateSetting($k, $v)
656
    {
657
        $conn = $this->getDbConn();
658
        if ($this->settingExists($k)) {
659
            $conn->update('settings', ['settingValue' => $v], ['settingKey' => $k]);
660
        } else {
661
            $conn->insert('settings', ['settingValue' => $v, 'settingKey' => $k]);
662
        }
663
        return $this->fetchSetting($k);
664
    }
665
666
    /**
667
     * Return SQL in appropriate dialect to concatenate the listed values
668
     *
669
     * @param array $fields
670
     *
671
     * @return string
672
     */
673
    abstract protected function concatenateEscapedFields($fields);
674
675
    /**
676
     * Return ticket array with fields converted to correct datatype
677
     *
678
     * @param $ticket
679
     *
680
     * @return mixed
681
     */
682
    protected function normaliseTicketRecord($ticket)
683
    {
684
        $boolFields = ['used', 'deleted', 'private', 'blocking'];
685
        $intFields = ['id', 'songId'];
686
687 View Code Duplication
        foreach ($intFields as $k) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
688
            $ticket[$k] = is_null($ticket[$k]) ? null : (int)$ticket[$k];
689
        }
690
691
        foreach ($boolFields as $k) {
692
            $ticket[$k] = (bool)$ticket[$k];
693
        }
694
695
        return $ticket;
696
    }
697
698
    /**
699
     * Delete all song & performer data
700
     *
701
     * @throws \Doctrine\DBAL\DBALException
702
     */
703
    public function resetAllSessionData()
704
    {
705
        $truncateTables = [
706
            'tickets_x_performers',
707
            'performers',
708
            'tickets'
709
        ];
710
711
        $connection = $this->getDbConn();
712
713
        foreach ($truncateTables as $table) {
714
            $connection->query(
715
                'TRUNCATE TABLE ' . $connection->quoteIdentifier($table)
716
            );
717
        }
718
    }
719
720
    /**
721
     * Delete all catalogue data
722
     *
723
     * @throws \Doctrine\DBAL\DBALException
724
     */
725
    public function resetCatalogue()
726
    {
727
        $dbConn = $this->getDbConn();
728
        $driverType = $dbConn->getDriver()->getName();
729
        $sqlite = (stripos($driverType, 'sqlite') !== false);
730
        // FIXME refactor to subclasses
731
732
        $truncateTables = [
733
            'songs_x_instruments',
734
            'songs_x_platforms',
735
            'songs',
736
            'instruments',
737
            'platforms'
738
        ];
739
        foreach ($truncateTables as $table) {
740
            $dbConn->exec(
741
                ($sqlite ? 'DELETE FROM ' : 'TRUNCATE TABLE ') .
742
                $this->dbConn->quoteIdentifier($table)
743
            );
744
        }
745
    }
746
747
    /**
748
     * Store an instrument to DB
749
     *
750
     * @param Instrument $instrument
751
     */
752
    public function storeInstrument(Instrument $instrument)
753
    {
754
        $asArray = $this->instrumentToDbRow($instrument);
755
        if ($this->getDbConn()->insert('instruments', $asArray)) {
756
            $instrument->setId($this->dbConn->lastInsertId());
757
        }
758
    }
759
760
    /**
761
     * Store a platform to DB
762
     *
763
     * @param Platform $platform
764
     */
765
    public function storePlatform(Platform $platform)
766
    {
767
        $asArray = $this->platformToDbRow($platform);
768
        if ($this->getDbConn()->insert('platforms', $asArray)) {
769
            $platform->setId($this->dbConn->lastInsertId());
770
        }
771
    }
772
773
    /**
774
     * Store a source to DB
775
     *
776
     * @param Source $source
777
     */
778
    public function storeSource(Source $source)
779
    {
780
        $asArray = $this->sourceToDbRow($source);
781
        if ($this->getDbConn()->insert('sources', $asArray)) {
782
            $source->setId($this->dbConn->lastInsertId());
783
        }
784
    }
785
786
    /**
787
     * Store a song to DB
788
     *
789
     * @param Song $song
790
     */
791
    public function storeSong(Song $song)
792
    {
793
        $asArray = $this->songToDbRow($song);
794
        if ($this->getDbConn()->insert('songs', $asArray)) {
795
            $song->setId($this->dbConn->lastInsertId());
796
        } else {
797
            throw new \Exception('insert failed');
798
        }
799
    }
800
801
    /**
802
     * @param string $sourceName
803
     *
804
     * @return null|Source
805
     */
806 View Code Duplication
    public function fetchSourceByName($sourceName)
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...
807
    {
808
        $source = null;
809
        $query = $this->dbConn->createQueryBuilder()
810
            ->select('*')
811
            ->from('sources')
812
            ->where('name = :name')
813
            ->setParameter(':name', $sourceName);
814
815
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
816
817
        if ($row) {
818
            $source = $this->buildSourceFromDbRow($row);
819
        }
820
821
        return $source;
822
    }
823
824
825
    /**
826
     * @param $sourceId
827
     * @return null|Source
828
     */
829 View Code Duplication
    public function fetchSourceById($sourceId)
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...
830
    {
831
        $source = null;
832
        $query = $this->dbConn->createQueryBuilder()
833
            ->select('*')
834
            ->from('sources')
835
            ->where('id = :id')
836
            ->setParameter(':id', $sourceId);
837
838
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
839
840
        if ($row) {
841
            $source = $this->buildSourceFromDbRow($row);
842
        }
843
844
        return $source;
845
    }
846
847 View Code Duplication
    public function fetchPlatformByName($platformName)
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...
848
    {
849
        $platform = null;
850
        $query = $this->dbConn->createQueryBuilder()
851
            ->select('*')
852
            ->from('platforms')
853
            ->where('name = :name')
854
            ->setParameter(':name', $platformName);
855
856
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
857
858
        if ($row) {
859
            $platform = $this->buildPlatformFromDbRow($row);
860
        }
861
862
        return $platform;
863
    }
864
865
    /**
866
     * Fetch all available platforms
867
     *
868
     * @return Platform[]
869
     */
870
    public function fetchAllPlatforms()
871
    {
872
        $query = $this->dbConn->createQueryBuilder()
873
            ->select('*')
874
            ->from('platforms')
875
            ->orderBy('id');
876
877
        $rows = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
878
879
        $platforms = [];
880
        foreach ($rows as $row) {
881
            $platforms[] = $this->buildPlatformFromDbRow($row);
882
        }
883
884
        return $platforms;
885
    }
886
887
    /**
888
     * Fetch all available instruments
889
     *
890
     * @return Instrument[]
891
     */
892
    public function fetchAllInstruments()
893
    {
894
        $query = $this->dbConn->createQueryBuilder()
895
            ->select('*')
896
            ->from('instruments')
897
            ->orderBy('id');
898
899
        $rows = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
900
901
        $instruments = [];
902
        foreach ($rows as $row) {
903
            $instruments[] = $this->buildInstrumentFromDbRow($row);
904
        }
905
906
        return $instruments;
907
    }
908
909
    /**
910
     * @param $songId
911
     * @param array $platformIds
912
     */
913 View Code Duplication
    public function storeSongPlatformLinks($songId, array $platformIds)
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...
914
    {
915
        $this->dbConn->delete('songs_x_platforms', ['songId' => $songId]);
916
917
        foreach ($platformIds as $platformId) {
918
            $this->dbConn->insert('songs_x_platforms', ['songId' => $songId, 'platformId' => $platformId]);
919
        }
920
    }
921
922
    /**
923
     * @param $name
924
     * @return null|Instrument
925
     */
926 View Code Duplication
    public function fetchInstrumentByName($name)
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...
927
    {
928
        $instrument = null;
929
        $query = $this->dbConn->createQueryBuilder()
930
            ->select('*')
931
            ->from('instruments')
932
            ->where('name = :name')
933
            ->setParameter(':name', $name);
934
935
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
936
937
        if ($row) {
938
            $instrument = $this->buildInstrumentFromDbRow($row);
939
        }
940
941
        return $instrument;
942
    }
943
944 View Code Duplication
    protected function fetchInstrumentByAbbreviation($abbreviation)
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...
945
    {
946
        $instrument = null;
947
        $query = $this->dbConn->createQueryBuilder()
948
            ->select('*')
949
            ->from('instruments')
950
            ->where('abbreviation = :abbreviation')
951
            ->setParameter(':abbreviation', $abbreviation);
952
953
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
954
955
        if ($row) {
956
            $instrument = $this->buildInstrumentFromDbRow($row);
957
        }
958
959
        return $instrument;
960
    }
961
962
963
    /**
964
     * @param $songId
965
     * @param array $instrumentIds
966
     * @throws \Doctrine\DBAL\Exception\InvalidArgumentException
967
     */
968 View Code Duplication
    public function storeSongInstrumentLinks($songId, array $instrumentIds)
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...
969
    {
970
        $this->dbConn->delete('songs_x_instruments', ['songId' => $songId]);
971
972
        foreach ($instrumentIds as $instrumentId) {
973
            $this->dbConn->insert('songs_x_instruments', ['songId' => $songId, 'instrumentId' => $instrumentId]);
974
        }
975
    }
976
977
    /**
978
     * @param $songId
979
     * @return Instrument[]
980
     */
981 View Code Duplication
    public function fetchInstrumentsForSongId($songId)
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...
982
    {
983
        $instrumentRows = $this->dbConn->fetchAll(
984
            'SELECT i.* FROM songs_x_instruments si 
985
              INNER JOIN instruments i ON si.instrumentId = i.id WHERE si.songId = :songId',
986
            ['songId' => $songId]
987
        );
988
989
        $dbConn = $this;
990
        $instruments = array_map(
991
            function ($row) use ($dbConn) {
992
                return $dbConn->buildInstrumentFromDbRow($row);
993
            },
994
            $instrumentRows
995
        );
996
997
        return $instruments;
998
    }
999
1000
    /**
1001
     * @param $songId
1002
     * @return Platform[]
1003
     */
1004 View Code Duplication
    public function fetchPlatformsForSongId($songId)
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...
1005
    {
1006
        $platformRows = $this->dbConn->fetchAll(
1007
            'SELECT p.* FROM songs_x_platforms sp 
1008
              INNER JOIN platforms p ON sp.platformId = p.id WHERE sp.songId = :songId',
1009
            ['songId' => $songId]
1010
        );
1011
1012
        $dbConn = $this;
1013
        $platforms = array_map(
1014
            function ($row) use ($dbConn) {
1015
                return $dbConn->buildPlatformFromDbRow($row);
1016
            },
1017
            $platformRows
1018
        );
1019
1020
        return $platforms;
1021
    }
1022
1023
    /**
1024
     * @param $row
1025
     * @return Instrument
1026
     */
1027
    protected function buildInstrumentFromDbRow($row)
1028
    {
1029
        $instrument = new Instrument();
1030
        $instrument
1031
            ->setId($row['id'])
1032
            ->setName($row['name'])
1033
            ->setAbbreviation($row['abbreviation'])
1034
            ->setIconHtml($row['iconHtml']);
1035
        return $instrument;
1036
    }
1037
1038
    /**
1039
     * @param $row
1040
     * @return Platform
1041
     */
1042
    protected function buildPlatformFromDbRow($row)
1043
    {
1044
        $platform = new Platform();
1045
        $platform
1046
            ->setId($row['id'])
1047
            ->setName($row['name']);
1048
        return $platform;
1049
    }
1050
1051
    /**
1052
     * @param $row
1053
     * @return Source
1054
     */
1055
    protected function buildSourceFromDbRow($row)
1056
    {
1057
        $source = new Source();
1058
        $source
1059
            ->setId($row['id'])
1060
            ->setName($row['name']);
1061
        return $source;
1062
    }
1063
1064
    /**
1065
     * @param Song $song
1066
     * @return array
1067
     */
1068
    protected function songToDbRow(Song $song)
1069
    {
1070
        $asArray = [];
1071
        if ($song->getId()) {
1072
            $asArray['id'] = $song->getId();
1073
        }
1074
1075
        $asArray['artist'] = $song->getArtist();
1076
        $asArray['title'] = $song->getTitle();
1077
        $asArray['duration'] = $song->getDuration();
1078
        $asArray['sourceId'] = $song->getSourceId();
1079
        $asArray['codeNumber'] = $song->getCodeNumber();
1080
        return $asArray;
1081
    }
1082
1083
    /**
1084
     * @param Source $source
1085
     * @return array
1086
     */
1087
    protected function sourceToDbRow(Source $source)
1088
    {
1089
        $asArray = [];
1090
        if ($source->getId()) {
1091
            $asArray['id'] = $source->getId();
1092
        }
1093
1094
        $asArray['name'] = $source->getName();
1095
        return $asArray;
1096
    }
1097
1098
    /**
1099
     * @param Instrument $instrument
1100
     * @return array
1101
     */
1102
    protected function instrumentToDbRow(Instrument $instrument)
1103
    {
1104
        $asArray = [];
1105
        if ($instrument->getId()) {
1106
            $asArray['id'] = $instrument->getId();
1107
        }
1108
1109
        $asArray['name'] = $instrument->getName();
1110
        $asArray['abbreviation'] = $instrument->getAbbreviation();
1111
        $asArray['iconHtml'] = $instrument->getIconHtml();
1112
        return $asArray;
1113
    }
1114
1115
    /**
1116
     * @param Platform $platform
1117
     * @return array
1118
     */
1119
    protected function platformToDbRow(Platform $platform)
1120
    {
1121
        $asArray = [];
1122
        if ($platform->getId()) {
1123
            $asArray['id'] = $platform->getId();
1124
        }
1125
1126
        $asArray['name'] = $platform->getName();
1127
        return $asArray;
1128
    }
1129
}
1130