deleteBelongsToManyRelationshipIdentifiers()   A
last analyzed

Complexity

Conditions 2
Paths 6

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 11
cts 11
cp 1
rs 9.568
c 0
b 0
f 0
cc 2
nc 6
nop 3
crap 2
1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Passport\Repositories;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Closure;
22
use DateTimeInterface;
23
use Doctrine\DBAL\Connection;
24
use Doctrine\DBAL\ConnectionException as ConEx;
25
use Doctrine\DBAL\DBALException;
26
use Doctrine\DBAL\Query\QueryBuilder;
27
use Doctrine\DBAL\Types\Type;
28
use Exception;
29
use Limoncello\Passport\Contracts\Entities\DatabaseSchemaInterface;
30
use Limoncello\Passport\Exceptions\RepositoryException;
31
use PDO;
32
use function assert;
33
use function call_user_func;
34
use function is_bool;
35
use function is_int;
36
use function is_numeric;
37
use function is_string;
38
39
/**
40
 * @package Limoncello\Passport
41
 *
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 */
44
abstract class BaseRepository
45
{
46
    /**
47
     * @return string
48
     */
49
    abstract protected function getTableNameForReading(): string;
50
51
    /**
52
     * @return string
53
     */
54
    abstract protected function getTableNameForWriting(): string;
55
56
    /**
57
     * @return string
58
     */
59
    abstract protected function getClassName(): string;
60
61
    /**
62
     * @return string
63
     */
64
    abstract protected function getPrimaryKeyName(): string;
65
66
    /**
67
     * @var Connection
68
     */
69
    private $connection;
70
71
    /**
72
     * @var DatabaseSchemaInterface
73
     */
74
    private $databaseSchema;
75
76
    /**
77
     * @param Closure $closure
78
     *
79
     * @return void
80
     *
81
     * @throws RepositoryException
82
     */
83 21
    public function inTransaction(Closure $closure): void
84
    {
85 21
        $connection = $this->getConnection();
86 21
        $connection->beginTransaction();
87
        try {
88 21
            $isOk = ($closure() === false ? null : true);
89 19
        } finally {
90 21
            $isCommitting = (isset($isOk) === true);
91
            try {
92 21
                $isCommitting === true ? $connection->commit() : $connection->rollBack();
93 1
            } /** @noinspection PhpRedundantCatchClauseInspection */ catch (ConEx | DBALException $exception) {
94 1
                throw new RepositoryException(
95 1
                    $isCommitting === true ? 'Failed to commit a transaction.' : 'Failed to rollback a transaction.',
96 1
                    0,
97 1
                    $exception
98
                );
99
            }
100
        }
101
    }
102
103
    /**
104
     * @return Connection
105
     */
106 61
    protected function getConnection(): Connection
107
    {
108 61
        return $this->connection;
109
    }
110
111
    /**
112
     * @param Connection $connection
113
     *
114
     * @return self
115
     */
116 71
    protected function setConnection(Connection $connection): self
117
    {
118 71
        $this->connection = $connection;
119
120 71
        return $this;
121
    }
122
123
    /**
124
     * @param array $columns
125
     *
126
     * @return array
127
     *
128
     * @throws RepositoryException
129
     */
130 4
    protected function indexResources(array $columns = ['*']): array
131
    {
132
        try {
133 4
            $query = $this->getConnection()->createQueryBuilder();
134
135
            $statement = $query
136 4
                ->select($columns)
137 4
                ->from($this->getTableNameForReading())
138 4
                ->execute();
139
140 2
            $statement->setFetchMode(PDO::FETCH_CLASS, $this->getClassName());
141 2
            $result = $statement->fetchAll();
142
143 2
            return $result;
144 2
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
145 2
            $message = 'Resource reading failed.';
146 2
            throw new RepositoryException($message, 0, $exception);
147
        }
148
    }
149
150
    /**
151
     * @param iterable $values
152
     *
153
     * @return void
154
     *
155
     * @throws RepositoryException
156
     */
157 30
    protected function createResource(iterable $values): void
158
    {
159
        try {
160 30
            $query = $this->getConnection()->createQueryBuilder();
161
162 30
            $query->insert($this->getTableNameForWriting());
163 30
            foreach ($values as $key => $value) {
164 30
                $query->setValue($key, $this->createTypedParameter($query, $value));
165
            }
166
167 30
            $numberOfAdded = $query->execute();
168 25
            assert(is_int($numberOfAdded) === true);
169 5
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
170 5
            $message = 'Resource creation failed.';
171 5
            throw new RepositoryException($message, 0, $exception);
172
        }
173
    }
174
175
    /**
176
     * @return int
177
     */
178 22
    protected function getLastInsertId(): int
179
    {
180 22
        $lastInsertId = $this->getConnection()->lastInsertId();
181
182 22
        assert(is_numeric($lastInsertId));
183
184 22
        return (int)$lastInsertId;
185
    }
186
187
    /**
188
     * @param string|int $identifier
189
     * @param array      $columns
190
     *
191
     * @return mixed
192
     *
193
     * @throws RepositoryException
194
     */
195 26
    protected function readResource($identifier, array $columns = ['*'])
196
    {
197 26
        return $this->readResourceByColumn($identifier, $this->getPrimaryKeyName(), $columns);
198
    }
199
200
    /**
201
     * @param string|int $identifier
202
     * @param string     $column
203
     * @param array      $columns
204
     *
205
     * @return mixed
206
     *
207
     * @throws RepositoryException
208
     */
209 26
    protected function readResourceByColumn($identifier, string $column, array $columns = ['*'])
210
    {
211
        try {
212 26
            $query = $this->getConnection()->createQueryBuilder();
213
214
            $statement = $query
215 26
                ->select($columns)
216 26
                ->from($this->getTableNameForReading())
217 26
                ->where($column . '=' . $this->createTypedParameter($query, $identifier))
218 26
                ->execute();
219
220 22
            $statement->setFetchMode(PDO::FETCH_CLASS, $this->getClassName());
221 22
            $result = $statement->fetch();
222
223 22
            return $result === false ? null : $result;
224 4
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
225 4
            $message = 'Resource reading failed.';
226 4
            throw new RepositoryException($message, 0, $exception);
227
        }
228
    }
229
230
    /**
231
     * @param string|int $identifier
232
     * @param array      $values
233
     *
234
     * @return int
235
     *
236
     * @throws RepositoryException
237
     */
238 7
    protected function updateResource($identifier, array $values): int
239
    {
240
        try {
241 7
            $query = $this->getConnection()->createQueryBuilder();
242
243
            $query
244 7
                ->update($this->getTableNameForWriting())
245 7
                ->where($this->getPrimaryKeyName() . '=' . $this->createTypedParameter($query, $identifier));
246 7
            foreach ($values as $key => $value) {
247 7
                $query->set($key, $this->createTypedParameter($query, $value));
248
            }
249
250 7
            $numberOfUpdated = $query->execute();
251 4
            assert(is_int($numberOfUpdated) === true);
252
253 4
            return $numberOfUpdated;
254 3
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
255 3
            $message = 'Resource update failed.';
256 3
            throw new RepositoryException($message, 0, $exception);
257
        }
258
    }
259
260
    /**
261
     * @param string|int $identifier
262
     *
263
     * @return int
264
     *
265
     * @throws RepositoryException
266
     */
267 7
    protected function deleteResource($identifier): int
268
    {
269
        try {
270 7
            $query = $this->getConnection()->createQueryBuilder();
271
272
            $query
273 7
                ->delete($this->getTableNameForWriting())
274 7
                ->where($this->getPrimaryKeyName() . '=' . $this->createTypedParameter($query, $identifier));
275
276 7
            $numberOfDeleted = $query->execute();
277 4
            assert(is_int($numberOfDeleted) === true);
278
279 4
            return $numberOfDeleted;
280 3
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
281 3
            $message = 'Resource deletion failed.';
282 3
            throw new RepositoryException($message, 0, $exception);
283
        }
284
    }
285
286
    /**
287
     * @param string|int $primaryKey
288
     * @param iterable   $foreignKeys
289
     * @param string     $intTableName
290
     * @param string     $intPrimaryKeyName
291
     * @param string     $intForeignKeyName
292
     *
293
     * @return void
294
     *
295
     * @throws RepositoryException
296
     */
297 20
    protected function createBelongsToManyRelationship(
298
        $primaryKey,
299
        iterable $foreignKeys,
300
        string $intTableName,
301
        string $intPrimaryKeyName,
302
        string $intForeignKeyName
303
    ): void {
304 20
        assert(is_string($primaryKey) === true || is_int($primaryKey) === true);
305
306
        try {
307
            $this->inTransaction(function () use (
308 20
                $intTableName,
309 20
                $intPrimaryKeyName,
310 20
                $intForeignKeyName,
311 20
                $primaryKey,
312 20
                $foreignKeys
313
            ): void {
314 20
                $connection = $this->getConnection();
315 20
                $query      = $connection->createQueryBuilder();
316
317 20
                $query->insert($intTableName)->values([$intPrimaryKeyName => '?', $intForeignKeyName => '?']);
318 20
                $statement = $connection->prepare($query->getSQL());
319
320 18
                foreach ($foreignKeys as $value) {
321 18
                    assert(is_string($value) === true || is_int($value) === true);
322 18
                    $statement->bindValue(1, $primaryKey);
323 18
                    $statement->bindValue(2, $value);
324 18
                    $statement->execute();
325
                }
326 20
            });
327 2
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
328 2
            $message = 'Belongs-to-Many relationship creation failed.';
329 2
            throw new RepositoryException($message, 0, $exception);
330
        }
331
    }
332
333
    /**
334
     * @param string|int $identifier
335
     * @param string     $intTableName
336
     * @param string     $intPrimaryKeyName
337
     * @param string     $intForeignKeyName
338
     *
339
     * @return string[]
340
     *
341
     * @throws RepositoryException
342
     */
343 22
    protected function readBelongsToManyRelationshipIdentifiers(
344
        $identifier,
345
        string $intTableName,
346
        string $intPrimaryKeyName,
347
        string $intForeignKeyName
348
    ): array {
349
        try {
350 22
            $connection = $this->getConnection();
351 22
            $query      = $connection->createQueryBuilder();
352
353
            $query
354 22
                ->select($intForeignKeyName)
355 22
                ->from($intTableName)
356 22
                ->where($intPrimaryKeyName . '=' . $this->createTypedParameter($query, $identifier));
357
358 22
            $statement = $query->execute();
359 20
            $statement->setFetchMode(PDO::FETCH_NUM);
360 20
            $result = array_column($statement->fetchAll(), 0);
361
362 20
            return $result;
363 2
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
364 2
            $message = 'Belongs-to-Many relationship reading failed.';
365 2
            throw new RepositoryException($message, 0, $exception);
366
        }
367
    }
368
369
    /**
370
     * @param string     $intTableName
371
     * @param string     $intPrimaryKeyName
372
     * @param string|int $identifier
373
     *
374
     * @return int
375
     *
376
     * @throws RepositoryException
377
     */
378 5
    protected function deleteBelongsToManyRelationshipIdentifiers(
379
        string $intTableName,
380
        string $intPrimaryKeyName,
381
        $identifier
382
    ): int {
383
        try {
384 5
            $connection = $this->getConnection();
385 5
            $query      = $connection->createQueryBuilder();
386
387
            $query
388 5
                ->delete($intTableName)
389 5
                ->where($intPrimaryKeyName . '=' . $this->createTypedParameter($query, $identifier));
390
391 5
            $numberOfDeleted = $query->execute();
392 3
            assert(is_int($numberOfDeleted) === true);
393
394 3
            return $numberOfDeleted;
395 2
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
396 2
            $message = 'Belongs-to-Many relationship deletion failed.';
397 2
            throw new RepositoryException($message, 0, $exception);
398
        }
399
    }
400
401
    /**
402
     * @param string|int $identifier
403
     * @param string     $hasManyTableName
404
     * @param string     $hasManyColumn
405
     * @param string     $hasManyFkName
406
     *
407
     * @return string[]
408
     *
409
     * @throws RepositoryException
410
     */
411 16
    protected function readHasManyRelationshipColumn(
412
        $identifier,
413
        string $hasManyTableName,
414
        string $hasManyColumn,
415
        string $hasManyFkName
416
    ): array {
417
        try {
418 16
            $connection = $this->getConnection();
419 16
            $query      = $connection->createQueryBuilder();
420
421
            $query
422 16
                ->select($hasManyColumn)
423 16
                ->from($hasManyTableName)
424 16
                ->where($hasManyFkName . '=' . $this->createTypedParameter($query, $identifier));
425
426 16
            $statement = $query->execute();
427 15
            $statement->setFetchMode(PDO::FETCH_NUM);
428 15
            $result = array_column($statement->fetchAll(), 0);
429
430 15
            return $result;
431 1
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (DBALException $exception) {
432 1
            $message = 'Has-Many relationship reading failed.';
433 1
            throw new RepositoryException($message, 0, $exception);
434
        }
435
    }
436
437
    /**
438
     * @param DateTimeInterface $dateTime
439
     *
440
     * @return string
441
     *
442
     * @throws RepositoryException
443
     */
444 43
    protected function getDateTimeForDb(DateTimeInterface $dateTime): string
445
    {
446
        try {
447 43
            return Type::getType(Type::DATETIME)
448 43
                ->convertToDatabaseValue($dateTime, $this->getConnection()->getDatabasePlatform());
449 1
        } catch (DBALException $exception) {
450 1
            $message = 'DateTime conversion to database format failed.';
451 1
            throw new RepositoryException($message, 0, $exception);
452
        }
453
    }
454
455
    /**
456
     * @return DatabaseSchemaInterface
457
     */
458 63
    protected function getDatabaseSchema(): DatabaseSchemaInterface
459
    {
460 63
        return $this->databaseSchema;
461
    }
462
463
    /**
464
     * @param DatabaseSchemaInterface $databaseSchema
465
     *
466
     * @return self
467
     */
468 71
    protected function setDatabaseSchema(DatabaseSchemaInterface $databaseSchema): self
469
    {
470 71
        $this->databaseSchema = $databaseSchema;
471
472 71
        return $this;
473
    }
474
475
    /**
476
     * @param QueryBuilder $query
477
     * @param mixed        $value
478
     *
479
     * @return string
480
     *
481
     * @SuppressWarnings(PHPMD.ElseExpression)
482
     *
483
     * @throws RepositoryException
484
     */
485 55
    protected function createTypedParameter(QueryBuilder $query, $value): string
486
    {
487 55
        if (is_bool($value) === true) {
488 28
            $type = PDO::PARAM_BOOL;
489 55
        } elseif (is_int($value) === true) {
490 17
            $type = PDO::PARAM_INT;
491 50
        } elseif ($value === null) {
492 35
            $type = PDO::PARAM_NULL;
493 50
        } elseif ($value instanceof DateTimeInterface) {
494 42
            $value = $this->getDateTimeForDb($value);
495 42
            $type  = PDO::PARAM_STR;
496
        } else {
497 45
            $type = PDO::PARAM_STR;
498
        }
499
500 55
        return $query->createNamedParameter($value, $type);
501
    }
502
503
    /**
504
     * Helps to ignore exception handling for cases when they do not arise (e.g. having current date and time).
505
     *
506
     * @param Closure $closure
507
     * @param mixed   $defaultValue
508
     *
509
     * @return mixed|null
510
     */
511 43
    protected function ignoreException(Closure $closure, $defaultValue = null)
0 ignored issues
show
Unused Code introduced by
The parameter $defaultValue is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
512
    {
513
        try {
514 43
            $defaultValue = call_user_func($closure);
515 1
        } catch (Exception $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
516
        }
517
518 43
        return $defaultValue;
519
    }
520
}
521