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.
Completed
Push — master ( 1d0edd...441033 )
by Richard
24:14
created

src/Phase/TakeATicket/DataSource/AbstractSql.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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