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.
Passed
Push — master ( 6812c5...04c9e1 )
by Richard
02:58
created

AbstractSql::fetchAllPlatforms()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 0
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
            $ticket['song'] = $this->expandSongData($this->fetchSongRowById($ticket['songId']));
138
        }
139
        //FIXED inefficient, but different pages expect different structure while we refactor
140
        $ticket['band'] = $this->fetchPerformersWithInstrumentByTicketId($ticket['id']);
141
142
        return $ticket;
143
    }
144
145
    /**
146
     * expandTicketData for multiple tickets
147
     *
148
     * @param $tickets
149
     *
150
     * @return mixed
151
     */
152
    public function expandTicketsData($tickets)
153
    {
154
        foreach ($tickets as &$ticket) {
155
            $ticket = $this->expandTicketData($ticket);
156
        }
157
158
        return $tickets;
159
    }
160
161
162
    /**
163
     * Fill out names of linked object ids for given song
164
     *
165
     * @param $song
166
     * @return mixed
167
     */
168
    public function expandSongData($song)
169
    {
170
        $dataStore = $this;
171
        $instruments = $dataStore->fetchInstrumentsForSongId($song['id']);
172
        $instruments = array_map(
173
            function (Instrument $instrument) {
174
                return $instrument->getName();
175
            },
176
            $instruments
177
        );
178
        $song['instruments'] = $instruments;
179
180
        $source = $dataStore->fetchSourceById($song['sourceId']);
181
        if ($source) {
182
            $song['source'] = $source->getName();
183
        }
184
185
        $platforms = $dataStore->fetchPlatformsForSongId($song['id']);
186
        $platforms = array_map(
187
            function (Platform $platform) {
188
                return $platform->getName();
189
            },
190
            $platforms
191
        );
192
        $song['platforms'] = $platforms;
193
        //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...
194
        $song['inRb3'] = in_array('RB3', $song['platforms']);
195
        $song['inRb4'] = in_array('RB4', $song['platforms']);
196
        return $song;
197
    }
198
199
    public function isPotentialCodeNumber($searchString)
200
    {
201
        $codeLength = (int)self::CODE_LENGTH;
202
        $regexp = '/^[a-f0-9]{' . $codeLength . '}$/i';
203
204
        return preg_match($regexp, $searchString);
205
    }
206
207
    /**
208
     * Get performed, pending data for all performers
209
     *
210
     * @return array
211
     */
212
    public function generatePerformerStats()
213
    {
214
        $conn = $this->getDbConn();
215
        $sql = 'SELECT p.id AS performerId, p.name AS performerName,
216
                  sum(CASE WHEN t.id IS NOT NULL AND t.used=0 AND t.deleted=0 THEN 1 ELSE 0 END) AS songsPending,
217
                  sum(CASE WHEN t.id IS NOT NULL AND t.used=1 AND t.deleted=0 THEN 1 ELSE 0 END) AS songsDone,
218
                  sum(CASE WHEN t.id IS NOT NULL AND t.deleted=0 THEN 1 ELSE 0 END) AS songsTotal
219
               FROM performers p
220
                    LEFT OUTER JOIN tickets_x_performers txp ON p.id=txp.performerId
221
                    LEFT OUTER JOIN tickets t ON txp.ticketId = t.id
222
                GROUP BY p.id ORDER BY p.name';
223
224
        return $conn->fetchAll($sql);
225
    }
226
227
    /**
228
     * Save band to ticket
229
     *
230
     * @param $ticketId
231
     * @param array $band ['instrumentCode' => 'name'] FIXME update
232
     */
233
    public function storeBandToTicket($ticketId, $band)
234
    {
235
        if (!is_array($band)) {
236
            throw new \InvalidArgumentException('Band must be array');
237
        }
238
239
        // remove existing performers
240
        $this->getDbConn()->delete(self::TICKETS_X_PERFORMERS_TABLE, ['ticketId' => $ticketId]);
241
242
        foreach ($band as $instrumentCode => $performers) {
243
            $instrument = $this->fetchInstrumentByAbbreviation($instrumentCode);
244
            if ($instrument) {
245
                $instrumentId = $instrument->getId();
246
247
                foreach ($performers as $performerName) {
248
                    $performerName = trim($performerName);
249
                    $performerId = $this->fetchPerformerIdByName($performerName, true);
250
                    if ($performerId) {
251
                        $link = [
252
                            'ticketId' => $ticketId,
253
                            'performerId' => $performerId,
254
                            'instrumentId' => $instrumentId
255
                        ];
256
                        $this->getDbConn()->insert(self::TICKETS_X_PERFORMERS_TABLE, $link);
257
                    }
258
                }
259
            } else {
260
                throw new \UnexpectedValueException("Unknown instrument abbreviation '$instrumentCode'");
261
            }
262
        }
263
    }
264
265
    public function fetchPerformerIdByName($performerName, $createMissing = false)
266
    {
267
        $conn = $this->getDbConn();
268
        $sql = 'SELECT id FROM performers p WHERE p.name LIKE :name LIMIT 1';
269
        $performerId = $conn->fetchColumn($sql, ['name' => $performerName]);
270
271
        if ($createMissing && !$performerId) {
272
            $max = $conn->fetchColumn('SELECT max(id) FROM performers');
273
            $performerId = $max + 1;
274
            $conn->insert(self::PERFORMERS_TABLE, ['id' => $performerId, 'name' => ucwords($performerName)]);
275
            //add new performer row
276
        }
277
278
        return $performerId;
279
    }
280
281
    /**
282
     * Fetch all performers on a song with their stats
283
     *
284
     * @deprecated Use fetchPerformersWithInstrumentByTicketId()
285
     *
286
     * @param $ticketId
287
     *
288
     * @return array[]
289
     */
290
    public function fetchPerformersByTicketId($ticketId)
291
    {
292
        $ticketPerformerSql = 'SELECT performerId FROM tickets_x_performers WHERE ticketId = :ticketId';
293
        $performerRows = $this->getDbConn()->fetchAll($ticketPerformerSql, ['ticketId' => $ticketId]);
294
        $performerIds = [];
295
        foreach ($performerRows as $row) {
296
            $performerIds[] = $row['performerId'];
297
        }
298
299
        $allPerformers = $this->generatePerformerStats();
300
        $trackPerformers = [];
301
        foreach ($allPerformers as $performer) {
302
            if (in_array($performer['performerId'], $performerIds)) {
303
                $trackPerformers[] = $performer;
304
            }
305
        }
306
307
        return $trackPerformers;
308
    }
309
310
    public function fetchPerformersWithInstrumentByTicketId($ticketId)
311
    {
312
        $ticketPerformerSql = 'SELECT x.performerId, i.abbreviation AS instrument
313
          FROM tickets_x_performers x INNER JOIN instruments i ON x.instrumentId = i.id
314
          WHERE x.ticketId = :ticketId';
315
        $performerRows = $this->getDbConn()->fetchAll($ticketPerformerSql, ['ticketId' => $ticketId]);
316
        $performerIds = [];
317
        $instrumentsByPerformer = [];
318
        foreach ($performerRows as $row) {
319
            $performerId = $row['performerId'];
320
            $performerIds[] = $performerId;
321
            $instrumentsByPerformer[$performerId] = $row['instrument'];
322
        }
323
324
        //todo Can probably clean up this algorithm
325
        $allPerformers = $this->generatePerformerStats();
326
        $trackPerformersByInstrument = [];
327
        foreach ($allPerformers as $performer) {
328
            $performerId = $performer['performerId'];
329
            if (in_array($performerId, $performerIds)) {
330
                $instrument = $instrumentsByPerformer[$performerId];
331
                if (!isset($trackPerformersByInstrument[$instrument])) {
332
                    $trackPerformersByInstrument[$instrument] = [];
333
                }
334
                $trackPerformersByInstrument[$instrument][] = $performer;
335
            }
336
        }
337
338
        return $trackPerformersByInstrument;
339
    }
340
341
    /**
342
     * @param $searchString
343
     * @param $howMany
344
     *
345
     * @return array
346
     */
347
    public function findSongsBySearchString($searchString, $howMany = 10)
348
    {
349
        $howMany += 0; // force int
350
351
        $conn = $this->getDbConn();
352
        $leadingPattern = implode('%', preg_split('/\s+/', $searchString)) . '%';
353
        $internalPattern = '%' . $leadingPattern;
354
        $params = [
355
            'internalPattern' => $internalPattern,
356
            'leadingPattern' => $leadingPattern,
357
            'searchString' => $searchString,
358
        ];
359
360
        // this may be unnecessary - chances of a code number hitting anything else is minimal
361
        if ($this->isPotentialCodeNumber($searchString)) {
362
            $sql = "SELECT s.*, max(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) as queued
363
            FROM songs s
364
            LEFT OUTER JOIN tickets t ON s.id = t.songId AND t.deleted=0
365
            WHERE (s.title = :searchString)
366
            OR (codeNumber = :searchString)
367
            GROUP BY s.id
368
            ORDER BY artist, title
369
            LIMIT $howMany";
370
            //allow title just in case
371
        } else {
372
            $matchingTokens = ['s.title', '" "', 's.artist'];
373
            $concatSearchFields = $this->concatenateEscapedFields($matchingTokens);
374
            $concatSearchFieldsReverse = $this->concatenateEscapedFields(array_reverse($matchingTokens));
375
            $sql = "SELECT s.*, max(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) as queued
376
            FROM songs s
377
            LEFT OUTER JOIN tickets t ON s.id = t.songId AND t.deleted=0
378
            WHERE ( $concatSearchFields LIKE :internalPattern)
379
            OR ($concatSearchFieldsReverse LIKE :internalPattern)
380
            OR (codeNumber LIKE :leadingPattern)
381
            OR (s.id = :searchString)
382
            GROUP BY s.id
383
            ORDER BY artist, title
384
            LIMIT $howMany";
385
        }
386
387
        $songs = $conn->fetchAll($sql, $params);
388
389
        // normalise data types
390
        foreach ($songs as &$song) {
391
            $song = $this->normaliseSongRecord($song);
392
        }
393
394
        return $songs;
395
    }
396
397
    /**
398
     * @param $id
399
     *
400
     * @return int
401
     */
402
    public function markTicketUsedById($id)
403
    {
404
        $conn = $this->getDbConn();
405
        return $conn->update(self::TICKETS_TABLE, ['used' => 1, 'startTime' => time()], ['id' => $id]);
406
    }
407
408
    /**
409
     * @param $id
410
     *
411
     * @return int
412
     */
413
    public function deleteTicketById($id)
414
    {
415
        $conn = $this->getDbConn();
416
        return $conn->update(self::TICKETS_TABLE, ['deleted' => 1], ['id' => $id]);
417
    }
418
419
    /**
420
     * @param $id
421
     * @param $offset
422
     *
423
     * @return mixed
424
     */
425
    public function updateTicketOffsetById($id, $offset)
426
    {
427
        $id = (int)$id;
428
        $offset = (int)$offset;
429
        $fields = ['offset' => $offset];
430
        $currentTrack = $this->fetchTicketById($id);
431
        $oldOffset = (int)$currentTrack['offset'];
432
        $ok = ($oldOffset === $offset);
433
434
        $this->getLogger()->debug(
435
            "Update track $id offset: $oldOffset => $offset: " .
436
            ($ok ? ' already set' : ' will update')
437
        );
438
439
        if (!$ok) {
440
            $ok = $this->updateTicketById($id, $fields);
441
        }
442
443
        return $ok;
444
    }
445
446
    /**
447
     * @param $songKey
448
     *
449
     * @return array
450
     */
451
    public function fetchSongByKey($songKey)
452
    {
453
        $conn = $this->getDbConn();
454
455
        $song = $conn->fetchAssoc('SELECT * FROM songs WHERE codeNumber = :code', ['code' => $songKey]);
456
        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...
457
            $song = $this->normaliseSongRecord($song);
458
        }
459
460
        return $song;
461
    }
462
463
    /**
464
     * @param bool $includePrivate
465
     *
466
     * @return array|mixed
467
     *
468
     * @throws \Doctrine\DBAL\DBALException
469
     */
470
    public function fetchUpcomingTickets($includePrivate = false)
471
    {
472
        $conn = $this->getDbConn();
473
        $privateClause = $includePrivate ? '' : ' AND private = 0 ';
474
        $statement = $conn->prepare(
475
            'SELECT * FROM tickets WHERE deleted=0 AND used=0 ' .
476
            $privateClause .
477
            'ORDER BY OFFSET ASC LIMIT ' . (int)$this->upcomingCount
478
        );
479
        $statement->execute();
480
        $next = $statement->fetchAll();
481
        $next = $this->expandTicketsData($next);
482
483
        return $next;
484
    }
485
486
    /**
487
     * Fetch all non-deleted tickets in offset order
488
     *
489
     * @return array|mixed
490
     *
491
     * @throws \Doctrine\DBAL\DBALException
492
     */
493 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...
494
    {
495
        $conn = $this->getDbConn();
496
        $statement = $conn->prepare('SELECT * FROM tickets WHERE deleted=0 ORDER BY offset ASC');
497
        $statement->execute();
498
        $tickets = $statement->fetchAll();
499
        $tickets = $this->expandTicketsData($tickets);
500
501
        return $tickets;
502
    }
503
504
    /**
505
     * Fetch all performed tickets in offset order
506
     *
507
     * @return array
508
     *
509
     * @throws \Doctrine\DBAL\DBALException
510
     */
511 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...
512
    {
513
        $conn = $this->getDbConn();
514
        $statement = $conn->prepare('SELECT * FROM tickets WHERE deleted=0 AND used=1 ORDER BY offset ASC');
515
        $statement->execute();
516
        $tickets = $statement->fetchAll();
517
        $tickets = $this->expandTicketsData($tickets);
518
519
        return $tickets;
520
    }
521
522
    /**
523
     * @param $title
524
     * @param $songId
525
     *
526
     * @return int|false Row ID
527
     */
528
    public function storeNewTicket($title, $songId)
529
    {
530
        $conn = $this->getDbConn();
531
        $max = $conn->fetchAssoc('SELECT max(offset) AS o, max(id) AS i FROM tickets');
532
533
        $maxOffset = $max['o'];
534
        $maxId = $max['i'];
535
        $ticket = [
536
            'title' => $title,
537
            'id' => $maxId + 1,
538
            'offset' => $maxOffset + 1,
539
            'songId' => $songId,
540
        ];
541
        $res = $conn->insert(self::TICKETS_TABLE, $ticket);
542
543
        return $res ? $ticket['id'] : false;
544
    }
545
546
    /**
547
     * Normalise datatypes returned in song query
548
     *
549
     * @param $song
550
     *
551
     * @return mixed
552
     */
553
    public function normaliseSongRecord($song)
554
    {
555
        $boolFields = [];
556
        $intFields = ['id', 'duration', 'sourceId'];
557
558 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...
559
            $song[$k] = is_null($song[$k]) ? null : (int)$song[$k];
560
        }
561
562
        foreach ($boolFields as $k) {
563
            $song[$k] = (bool)$song[$k];
564
        }
565
566
        // Search API adds a 'queued' parameter to show if song is taken
567
        if (isset($song['queued'])) { //TODO see if this is safe to move to $boolFields
568
            $song['queued'] = (bool)$song['queued'];
569
        }
570
571
        return $song;
572
    }
573
574
    /**
575
     * @param $id
576
     * @param $fields
577
     *
578
     * @return int Number of updated rows
579
     */
580
    public function updateTicketById($id, $fields)
581
    {
582
        if (isset($fields['id'])) {
583
            throw new \InvalidArgumentException('Fields must not include id');
584
        }
585
586
        return $this->getDbConn()->update(self::TICKETS_TABLE, $fields, ['id' => $id]);
587
    }
588
589
    /**
590
     * Fetch core ticket data (not band, etc) by ticket id
591
     *
592
     * @param $id
593
     *
594
     * @return array
595
     */
596
    public function fetchTicketById($id)
597
    {
598
        $conn = $this->getDbConn();
599
        $song = $conn->fetchAssoc('SELECT * FROM ' . self::TICKETS_TABLE . ' WHERE id = :id', ['id' => $id]);
600
601
        return $song;
602
    }
603
604
    /**
605
     * Get current value of a named setting, NULL if missing
606
     *
607
     * @param  $key
608
     * @return mixed|null
609
     */
610
    public function fetchSetting($key)
611
    {
612
        $conn = $this->getDbConn();
613
        $query = $conn->executeQuery('SELECT settingValue FROM settings WHERE settingKey=:key', ['key' => $key]);
614
615
        return $query->rowCount() ? $query->fetchColumn() : null;
616
    }
617
618
619
    public function settingExists($key)
620
    {
621
        $conn = $this->getDbConn();
622
        $value = $conn->fetchColumn('SELECT 1 FROM settings WHERE settingKey=:key', ['key' => $key]);
623
624
        return (bool)$value;
625
    }
626
627
    public function updateSetting($k, $v)
628
    {
629
        $conn = $this->getDbConn();
630
        if ($this->settingExists($k)) {
631
            $conn->update('settings', ['settingValue' => $v], ['settingKey' => $k]);
632
        } else {
633
            $conn->insert('settings', ['settingValue' => $v, 'settingKey' => $k]);
634
        }
635
        return $this->fetchSetting($k);
636
    }
637
638
    /**
639
     * Return SQL in appropriate dialect to concatenate the listed values
640
     *
641
     * @param array $fields
642
     *
643
     * @return string
644
     */
645
    abstract protected function concatenateEscapedFields($fields);
646
647
    /**
648
     * Return ticket array with fields converted to correct datatype
649
     *
650
     * @param $ticket
651
     *
652
     * @return mixed
653
     */
654
    protected function normaliseTicketRecord($ticket)
655
    {
656
        $boolFields = ['used', 'deleted', 'private', 'blocking'];
657
        $intFields = ['id', 'songId'];
658
659 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...
660
            $ticket[$k] = is_null($ticket[$k]) ? null : (int)$ticket[$k];
661
        }
662
663
        foreach ($boolFields as $k) {
664
            $ticket[$k] = (bool)$ticket[$k];
665
        }
666
667
        return $ticket;
668
    }
669
670
    /**
671
     * Delete all song & performer data
672
     *
673
     * @throws \Doctrine\DBAL\DBALException
674
     */
675
    public function resetAllSessionData()
676
    {
677
        $truncateTables = [
678
            'tickets_x_performers',
679
            'performers',
680
            'tickets'
681
        ];
682
683
        $connection = $this->getDbConn();
684
685
        foreach ($truncateTables as $table) {
686
            $connection->query(
687
                'TRUNCATE TABLE ' . $connection->quoteIdentifier($table)
688
            );
689
        }
690
    }
691
692
    /**
693
     * Delete all catalogue data
694
     *
695
     * @throws \Doctrine\DBAL\DBALException
696
     */
697
    public function resetCatalogue()
698
    {
699
        $dbConn = $this->getDbConn();
700
        $driverType = $dbConn->getDriver()->getName();
701
        $sqlite = (stripos($driverType, 'sqlite') !== false);
702
        // FIXME refactor to subclasses
703
704
        $truncateTables = [
705
            'songs_x_instruments',
706
            'songs_x_platforms',
707
            'songs',
708
            'instruments',
709
            'platforms'
710
        ];
711
        foreach ($truncateTables as $table) {
712
            $dbConn->exec(
713
                ($sqlite ? 'DELETE FROM ' : 'TRUNCATE TABLE ') .
714
                $this->dbConn->quoteIdentifier($table)
715
            );
716
        }
717
    }
718
719
    /**
720
     * Store an instrument to DB
721
     *
722
     * @param Instrument $instrument
723
     */
724
    public function storeInstrument(Instrument $instrument)
725
    {
726
        $asArray = $this->instrumentToDbRow($instrument);
727
        if ($this->getDbConn()->insert('instruments', $asArray)) {
728
            $instrument->setId($this->dbConn->lastInsertId());
729
        }
730
    }
731
732
    /**
733
     * Store a platform to DB
734
     *
735
     * @param Platform $platform
736
     */
737
    public function storePlatform(Platform $platform)
738
    {
739
        $asArray = $this->platformToDbRow($platform);
740
        if ($this->getDbConn()->insert('platforms', $asArray)) {
741
            $platform->setId($this->dbConn->lastInsertId());
742
        }
743
    }
744
745
    /**
746
     * Store a source to DB
747
     *
748
     * @param Source $source
749
     */
750
    public function storeSource(Source $source)
751
    {
752
        $asArray = $this->sourceToDbRow($source);
753
        if ($this->getDbConn()->insert('sources', $asArray)) {
754
            $source->setId($this->dbConn->lastInsertId());
755
        }
756
    }
757
758
    /**
759
     * Store a song to DB
760
     *
761
     * @param Song $song
762
     */
763
    public function storeSong(Song $song)
764
    {
765
        $asArray = $this->songToDbRow($song);
766
        if ($this->getDbConn()->insert('songs', $asArray)) {
767
            $song->setId($this->dbConn->lastInsertId());
768
        }
769
    }
770
771
    /**
772
     * @param string $sourceName
773
     *
774
     * @return null|Source
775
     */
776 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...
777
    {
778
        $source = null;
779
        $query = $this->dbConn->createQueryBuilder()
780
            ->select('*')
781
            ->from('sources')
782
            ->where('name = :name')
783
            ->setParameter(':name', $sourceName);
784
785
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
786
787
        if ($row) {
788
            $source = $this->buildSourceFromDbRow($row);
789
        }
790
791
        return $source;
792
    }
793
794
795
    /**
796
     * @param $sourceId
797
     * @return null|Source
798
     */
799 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...
800
    {
801
        $source = null;
802
        $query = $this->dbConn->createQueryBuilder()
803
            ->select('*')
804
            ->from('sources')
805
            ->where('id = :id')
806
            ->setParameter(':id', $sourceId);
807
808
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
809
810
        if ($row) {
811
            $source = $this->buildSourceFromDbRow($row);
812
        }
813
814
        return $source;
815
    }
816
817 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...
818
    {
819
        $platform = null;
820
        $query = $this->dbConn->createQueryBuilder()
821
            ->select('*')
822
            ->from('platforms')
823
            ->where('name = :name')
824
            ->setParameter(':name', $platformName);
825
826
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
827
828
        if ($row) {
829
            $platform = $this->buildPlatformFromDbRow($row);
830
        }
831
832
        return $platform;
833
    }
834
835
    /**
836
     * Fetch all available platforms
837
     *
838
     * @return Platform[]
839
     */
840
    public function fetchAllPlatforms()
841
    {
842
        $query = $this->dbConn->createQueryBuilder()
843
            ->select('*')
844
            ->from('platforms')
845
            ->orderBy('id');
846
847
        $rows = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
848
849
        $platforms = [];
850
        foreach ($rows as $row) {
851
            $platforms[] = $this->buildPlatformFromDbRow($row);
852
        }
853
854
        return $platforms;
855
    }
856
857
    /**
858
     * @param $songId
859
     * @param array $platformIds
860
     */
861 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...
862
    {
863
        $this->dbConn->delete('songs_x_platforms', ['songId' => $songId]);
864
865
        foreach ($platformIds as $platformId) {
866
            $this->dbConn->insert('songs_x_platforms', ['songId' => $songId, 'platformId' => $platformId]);
867
        }
868
    }
869
870
    /**
871
     * @param $name
872
     * @return null|Instrument
873
     */
874 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...
875
    {
876
        $instrument = null;
877
        $query = $this->dbConn->createQueryBuilder()
878
            ->select('*')
879
            ->from('instruments')
880
            ->where('name = :name')
881
            ->setParameter(':name', $name);
882
883
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
884
885
        if ($row) {
886
            $instrument = $this->buildInstrumentFromDbRow($row);
887
        }
888
889
        return $instrument;
890
    }
891
892 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...
893
    {
894
        $instrument = null;
895
        $query = $this->dbConn->createQueryBuilder()
896
            ->select('*')
897
            ->from('instruments')
898
            ->where('abbreviation = :abbreviation')
899
            ->setParameter(':abbreviation', $abbreviation);
900
901
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
902
903
        if ($row) {
904
            $instrument = $this->buildInstrumentFromDbRow($row);
905
        }
906
907
        return $instrument;
908
    }
909
910
911
    /**
912
     * @param $songId
913
     * @param array $instrumentIds
914
     * @throws \Doctrine\DBAL\Exception\InvalidArgumentException
915
     */
916 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...
917
    {
918
        $this->dbConn->delete('songs_x_instruments', ['songId' => $songId]);
919
920
        foreach ($instrumentIds as $instrumentId) {
921
            $this->dbConn->insert('songs_x_instruments', ['songId' => $songId, 'instrumentId' => $instrumentId]);
922
        }
923
    }
924
925
    /**
926
     * @param $songId
927
     * @return Instrument[]
928
     */
929 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...
930
    {
931
        $instrumentRows = $this->dbConn->fetchAll(
932
            'SELECT i.* FROM songs_x_instruments si 
933
              INNER JOIN instruments i ON si.instrumentId = i.id WHERE si.songId = :songId',
934
            ['songId' => $songId]
935
        );
936
937
        $dbConn = $this;
938
        $instruments = array_map(
939
            function ($row) use ($dbConn) {
940
                return $dbConn->buildInstrumentFromDbRow($row);
941
            },
942
            $instrumentRows
943
        );
944
945
        return $instruments;
946
    }
947
948
    /**
949
     * @param $songId
950
     * @return Platform[]
951
     */
952 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...
953
    {
954
        $platformRows = $this->dbConn->fetchAll(
955
            'SELECT p.* FROM songs_x_platforms sp 
956
              INNER JOIN platforms p ON sp.platformId = p.id WHERE sp.songId = :songId',
957
            ['songId' => $songId]
958
        );
959
960
        $dbConn = $this;
961
        $platforms = array_map(
962
            function ($row) use ($dbConn) {
963
                return $dbConn->buildPlatformFromDbRow($row);
964
            },
965
            $platformRows
966
        );
967
968
        return $platforms;
969
    }
970
971
    /**
972
     * @param $row
973
     * @return Instrument
974
     */
975
    protected function buildInstrumentFromDbRow($row)
976
    {
977
        $instrument = new Instrument();
978
        $instrument
979
            ->setId($row['id'])
980
            ->setName($row['name'])
981
            ->setAbbreviation($row['abbreviation'])
982
            ->setIconHtml($row['iconHtml']);
983
        return $instrument;
984
    }
985
986
    /**
987
     * @param $row
988
     * @return Platform
989
     */
990
    protected function buildPlatformFromDbRow($row)
991
    {
992
        $platform = new Platform();
993
        $platform
994
            ->setId($row['id'])
995
            ->setName($row['name']);
996
        return $platform;
997
    }
998
999
    /**
1000
     * @param $row
1001
     * @return Source
1002
     */
1003
    protected function buildSourceFromDbRow($row)
1004
    {
1005
        $source = new Source();
1006
        $source
1007
            ->setId($row['id'])
1008
            ->setName($row['name']);
1009
        return $source;
1010
    }
1011
1012
    /**
1013
     * @param Song $song
1014
     * @return array
1015
     */
1016
    protected function songToDbRow(Song $song)
1017
    {
1018
        $asArray = [];
1019
        if ($song->getId()) {
1020
            $asArray['id'] = $song->getId();
1021
        }
1022
1023
        $asArray['artist'] = $song->getArtist();
1024
        $asArray['title'] = $song->getTitle();
1025
        $asArray['duration'] = $song->getDuration();
1026
        $asArray['sourceId'] = $song->getSourceId();
1027
        $asArray['codeNumber'] = $song->getCodeNumber();
1028
        return $asArray;
1029
    }
1030
1031
    /**
1032
     * @param Source $source
1033
     * @return array
1034
     */
1035
    protected function sourceToDbRow(Source $source)
1036
    {
1037
        $asArray = [];
1038
        if ($source->getId()) {
1039
            $asArray['id'] = $source->getId();
1040
        }
1041
1042
        $asArray['name'] = $source->getName();
1043
        return $asArray;
1044
    }
1045
1046
    /**
1047
     * @param Instrument $instrument
1048
     * @return array
1049
     */
1050
    protected function instrumentToDbRow(Instrument $instrument)
1051
    {
1052
        $asArray = [];
1053
        if ($instrument->getId()) {
1054
            $asArray['id'] = $instrument->getId();
1055
        }
1056
1057
        $asArray['name'] = $instrument->getName();
1058
        $asArray['abbreviation'] = $instrument->getAbbreviation();
1059
        $asArray['iconHtml'] = $instrument->getIconHtml();
1060
        return $asArray;
1061
    }
1062
1063
    /**
1064
     * @param Platform $platform
1065
     * @return array
1066
     */
1067
    protected function platformToDbRow(Platform $platform)
1068
    {
1069
        $asArray = [];
1070
        if ($platform->getId()) {
1071
            $asArray['id'] = $platform->getId();
1072
        }
1073
1074
        $asArray['name'] = $platform->getName();
1075
        return $asArray;
1076
    }
1077
}
1078