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.
Test Failed
Push — master ( 2a3ed5...ec7e77 )
by Richard
02:59
created

AbstractSql::fetchAllInstruments()   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->jsonSerialize();
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
     * Fetch all available instruments
859
     *
860
     * @return Instrument[]
861
     */
862
    public function fetchAllInstruments()
863
    {
864
        $query = $this->dbConn->createQueryBuilder()
865
            ->select('*')
866
            ->from('instruments')
867
            ->orderBy('id');
868
869
        $rows = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
870
871
        $instruments = [];
872
        foreach ($rows as $row) {
873
            $instruments[] = $this->buildInstrumentFromDbRow($row);
874
        }
875
876
        return $instruments;
877
    }
878
879
    /**
880
     * @param $songId
881
     * @param array $platformIds
882
     */
883 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...
884
    {
885
        $this->dbConn->delete('songs_x_platforms', ['songId' => $songId]);
886
887
        foreach ($platformIds as $platformId) {
888
            $this->dbConn->insert('songs_x_platforms', ['songId' => $songId, 'platformId' => $platformId]);
889
        }
890
    }
891
892
    /**
893
     * @param $name
894
     * @return null|Instrument
895
     */
896 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...
897
    {
898
        $instrument = null;
899
        $query = $this->dbConn->createQueryBuilder()
900
            ->select('*')
901
            ->from('instruments')
902
            ->where('name = :name')
903
            ->setParameter(':name', $name);
904
905
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
906
907
        if ($row) {
908
            $instrument = $this->buildInstrumentFromDbRow($row);
909
        }
910
911
        return $instrument;
912
    }
913
914 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...
915
    {
916
        $instrument = null;
917
        $query = $this->dbConn->createQueryBuilder()
918
            ->select('*')
919
            ->from('instruments')
920
            ->where('abbreviation = :abbreviation')
921
            ->setParameter(':abbreviation', $abbreviation);
922
923
        $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
924
925
        if ($row) {
926
            $instrument = $this->buildInstrumentFromDbRow($row);
927
        }
928
929
        return $instrument;
930
    }
931
932
933
    /**
934
     * @param $songId
935
     * @param array $instrumentIds
936
     * @throws \Doctrine\DBAL\Exception\InvalidArgumentException
937
     */
938 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...
939
    {
940
        $this->dbConn->delete('songs_x_instruments', ['songId' => $songId]);
941
942
        foreach ($instrumentIds as $instrumentId) {
943
            $this->dbConn->insert('songs_x_instruments', ['songId' => $songId, 'instrumentId' => $instrumentId]);
944
        }
945
    }
946
947
    /**
948
     * @param $songId
949
     * @return Instrument[]
950
     */
951 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...
952
    {
953
        $instrumentRows = $this->dbConn->fetchAll(
954
            'SELECT i.* FROM songs_x_instruments si 
955
              INNER JOIN instruments i ON si.instrumentId = i.id WHERE si.songId = :songId',
956
            ['songId' => $songId]
957
        );
958
959
        $dbConn = $this;
960
        $instruments = array_map(
961
            function ($row) use ($dbConn) {
962
                return $dbConn->buildInstrumentFromDbRow($row);
963
            },
964
            $instrumentRows
965
        );
966
967
        return $instruments;
968
    }
969
970
    /**
971
     * @param $songId
972
     * @return Platform[]
973
     */
974 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...
975
    {
976
        $platformRows = $this->dbConn->fetchAll(
977
            'SELECT p.* FROM songs_x_platforms sp 
978
              INNER JOIN platforms p ON sp.platformId = p.id WHERE sp.songId = :songId',
979
            ['songId' => $songId]
980
        );
981
982
        $dbConn = $this;
983
        $platforms = array_map(
984
            function ($row) use ($dbConn) {
985
                return $dbConn->buildPlatformFromDbRow($row);
986
            },
987
            $platformRows
988
        );
989
990
        return $platforms;
991
    }
992
993
    /**
994
     * @param $row
995
     * @return Instrument
996
     */
997
    protected function buildInstrumentFromDbRow($row)
998
    {
999
        $instrument = new Instrument();
1000
        $instrument
1001
            ->setId($row['id'])
1002
            ->setName($row['name'])
1003
            ->setAbbreviation($row['abbreviation'])
1004
            ->setIconHtml($row['iconHtml']);
1005
        return $instrument;
1006
    }
1007
1008
    /**
1009
     * @param $row
1010
     * @return Platform
1011
     */
1012
    protected function buildPlatformFromDbRow($row)
1013
    {
1014
        $platform = new Platform();
1015
        $platform
1016
            ->setId($row['id'])
1017
            ->setName($row['name']);
1018
        return $platform;
1019
    }
1020
1021
    /**
1022
     * @param $row
1023
     * @return Source
1024
     */
1025
    protected function buildSourceFromDbRow($row)
1026
    {
1027
        $source = new Source();
1028
        $source
1029
            ->setId($row['id'])
1030
            ->setName($row['name']);
1031
        return $source;
1032
    }
1033
1034
    /**
1035
     * @param Song $song
1036
     * @return array
1037
     */
1038
    protected function songToDbRow(Song $song)
1039
    {
1040
        $asArray = [];
1041
        if ($song->getId()) {
1042
            $asArray['id'] = $song->getId();
1043
        }
1044
1045
        $asArray['artist'] = $song->getArtist();
1046
        $asArray['title'] = $song->getTitle();
1047
        $asArray['duration'] = $song->getDuration();
1048
        $asArray['sourceId'] = $song->getSourceId();
1049
        $asArray['codeNumber'] = $song->getCodeNumber();
1050
        return $asArray;
1051
    }
1052
1053
    /**
1054
     * @param Source $source
1055
     * @return array
1056
     */
1057
    protected function sourceToDbRow(Source $source)
1058
    {
1059
        $asArray = [];
1060
        if ($source->getId()) {
1061
            $asArray['id'] = $source->getId();
1062
        }
1063
1064
        $asArray['name'] = $source->getName();
1065
        return $asArray;
1066
    }
1067
1068
    /**
1069
     * @param Instrument $instrument
1070
     * @return array
1071
     */
1072
    protected function instrumentToDbRow(Instrument $instrument)
1073
    {
1074
        $asArray = [];
1075
        if ($instrument->getId()) {
1076
            $asArray['id'] = $instrument->getId();
1077
        }
1078
1079
        $asArray['name'] = $instrument->getName();
1080
        $asArray['abbreviation'] = $instrument->getAbbreviation();
1081
        $asArray['iconHtml'] = $instrument->getIconHtml();
1082
        return $asArray;
1083
    }
1084
1085
    /**
1086
     * @param Platform $platform
1087
     * @return array
1088
     */
1089
    protected function platformToDbRow(Platform $platform)
1090
    {
1091
        $asArray = [];
1092
        if ($platform->getId()) {
1093
            $asArray['id'] = $platform->getId();
1094
        }
1095
1096
        $asArray['name'] = $platform->getName();
1097
        return $asArray;
1098
    }
1099
}
1100