Passed
Push — dbal ( c9bdec...b420d6 )
by Greg
14:23
created

DB::lastInsertId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2023 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees;
21
22
use Doctrine\DBAL\Configuration as DBALConfiguration;
23
use Doctrine\DBAL\Connection as DBALConnection;
24
use Doctrine\DBAL\Driver as DBALDriver;
25
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
26
use Doctrine\DBAL\Query\QueryBuilder;
27
use DomainException;
28
use Fisharebest\Webtrees\DB\Column;
29
use Fisharebest\Webtrees\DB\ColumnType;
30
use Fisharebest\Webtrees\DB\Drivers\DriverInterface;
31
use Fisharebest\Webtrees\DB\Drivers\MySQLDriver;
32
use Fisharebest\Webtrees\DB\Drivers\PostgreSQLDriver;
33
use Fisharebest\Webtrees\DB\Drivers\SQLiteDriver;
34
use Fisharebest\Webtrees\DB\Drivers\SQLServerDriver;
35
use Fisharebest\Webtrees\DB\ForeignKey;
36
use Fisharebest\Webtrees\DB\Index;
37
use Fisharebest\Webtrees\DB\PrimaryKey;
38
use Fisharebest\Webtrees\DB\UniqueIndex;
39
use Illuminate\Database\Capsule\Manager;
40
use Illuminate\Database\Query\Builder;
41
use PDO;
42
43
use function str_starts_with;
44
45
/**
46
 * Static access to doctrine/dbal and laravel database
47
 */
48
class DB extends Manager
49
{
50
    private static DBALConnection $dbal_connection;
51
52
    private static string $prefix;
53
54
    private static DBALDriver&DriverInterface $driver;
55
56
    public static function connect(PDO $pdo, string $prefix): void
57
    {
58
        $driver_name = $pdo->getAttribute(attribute: PDO::ATTR_DRIVER_NAME);
59
60
        self::$driver = match ($driver_name) {
61
            'mysql'    => new MySQLDriver(pdo: $pdo),
62
            'postgres' => new PostgreSQLDriver(pdo: $pdo),
63
            'sqlite'   => new SQLiteDriver(pdo: $pdo),
64
            'sqlsrv'   => new SQLServerDriver(pdo: $pdo),
65
            default    => throw new DomainException(message: 'No driver available for ' . $driver_name),
66
        };
67
68
        $prefix_filter = static fn (string $name): bool => str_starts_with(haystack: $name, needle: $prefix);
69
        $configuration = new DBALConfiguration();
70
        $configuration->setSchemaAssetsFilter(schemaAssetsFilter: $prefix_filter);
71
72
        self::$dbal_connection = new DBALConnection(params: [], driver: self::$driver, config: $configuration);
73
        self::$prefix          = $prefix;
74
    }
75
76
    public static function driverName(): string
77
    {
78
        return self::$driver->driverName();
79
    }
80
81
    public static function getDBALConnection(): DBALConnection
82
    {
83
        return self::$dbal_connection;
84
    }
85
86
    public static function prefix(string $identifier = ''): string
87
    {
88
        return self::$prefix . $identifier;
89
    }
90
91
    public static function lastInsertId(): int
92
    {
93
        return (int) self::getDBALConnection()->lastInsertId();
94
    }
95
96
    public static function select(string ...$expressions): QueryBuilder
97
    {
98
        return self::$dbal_connection
99
            ->createQueryBuilder()
100
            ->select(...$expressions);
101
    }
102
103
    public static function update(string $table): QueryBuilder
104
    {
105
        return self::$dbal_connection->update(DB::prefix($table));
0 ignored issues
show
Bug introduced by
The call to Doctrine\DBAL\Connection::update() has too few arguments starting with data. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

105
        return self::$dbal_connection->/** @scrutinizer ignore-call */ update(DB::prefix($table));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
106
    }
107
108
    /**
109
     * @param string                                                $table
110
     * @param array<array-key,array<string,int|float|string|null>>  $rows
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,array<st...int|float|string|null>> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,array<string,int|float|string|null>>.
Loading history...
111
     */
112
    public static function insert(string $table, array $rows): void
113
    {
114
        foreach ($rows as $row) {
115
            DB::getDBALConnection()->insert(DB::prefix($table), $row);
116
        }
117
    }
118
119
    public static function delete(string ...$expressions): QueryBuilder
120
    {
121
        return self::$dbal_connection
122
            ->createQueryBuilder()
123
            ->delete(...$expressions);
0 ignored issues
show
Bug introduced by
$expressions is expanded, but the parameter $table of Doctrine\DBAL\Query\QueryBuilder::delete() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
            ->delete(/** @scrutinizer ignore-type */ ...$expressions);
Loading history...
124
    }
125
126
    public static function expression(): ExpressionBuilder
127
    {
128
        return self::$dbal_connection->createExpressionBuilder();
129
    }
130
131
    public static function char(string $name, int $length): Column
132
    {
133
        return new Column(
134
            name: $name,
135
            type: ColumnType::Char,
136
            length: $length,
137
            fixed: true,
138
            collation: self::$driver->collationASCII(),
139
        );
140
    }
141
142
    public static function varchar(string $name, int $length): Column
143
    {
144
        return new Column(
145
            name: $name,
146
            type: ColumnType::Char,
147
            length: $length,
148
            collation: self::$driver->collationASCII(),
149
        );
150
    }
151
152
    public static function nchar(string $name, int $length): Column
153
    {
154
        return new Column(
155
            name: $name,
156
            type: ColumnType::NChar,
157
            length: $length,
158
            fixed: true,
159
            collation: self::$driver->collationUTF8(),
160
        );
161
    }
162
163
    public static function nvarchar(string $name, int $length): Column
164
    {
165
        return new Column(
166
            name: $name,
167
            type: ColumnType::NVarChar,
168
            length: $length,
169
            collation: self::$driver->collationUTF8(),
170
        );
171
    }
172
173
    public static function integer(string $name): Column
174
    {
175
        return new Column(name: $name, type: ColumnType::Integer);
176
    }
177
178
    public static function float(string $name): Column
179
    {
180
        return new Column(name: $name, type: ColumnType::Float);
181
    }
182
183
    public static function text(string $name): Column
184
    {
185
        return new Column(
186
            name: $name,
187
            type: ColumnType::Text,
188
            collation: self::$driver->collationUTF8(),
189
        );
190
    }
191
192
    public static function timestamp(string $name, int $precision = 0): Column
193
    {
194
        return new Column(name: $name, type: ColumnType::Timestamp, precision: $precision);
195
    }
196
197
    /**
198
     * @param array<array-key,string> $columns
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,string>.
Loading history...
199
     *
200
     * @return PrimaryKey
201
     */
202
    public static function primaryKey(array $columns): PrimaryKey
203
    {
204
        return new PrimaryKey(columns: $columns);
205
    }
206
207
    /**
208
     * @param array<array-key,string> $columns
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,string>.
Loading history...
209
     *
210
     * @return Index
211
     */
212
    public static function index(array $columns): Index
213
    {
214
        return new Index(columns: $columns);
215
    }
216
217
    /**
218
     * @param array<array-key,string> $columns
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,string>.
Loading history...
219
     *
220
     * @return UniqueIndex
221
     */
222
    public static function uniqueIndex(array $columns): UniqueIndex
223
    {
224
        return new UniqueIndex(columns: $columns);
225
    }
226
227
    /**
228
     * @param array<array-key,string> $local_columns
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,string>.
Loading history...
229
     * @param string                  $foreign_table
230
     * @param array<array-key,string> $foreign_columns
231
     *
232
     * @return ForeignKey
233
     */
234
    public static function foreignKey(array $local_columns, string $foreign_table, array $foreign_columns = null): ForeignKey
235
    {
236
        return new ForeignKey(
237
            local_columns: $local_columns,
238
            foreign_table: $foreign_table,
239
            foreign_columns: $foreign_columns ?? $local_columns,
240
        );
241
    }
242
243
    /**
244
     * @internal
245
     */
246
    public static function caseInsensitiveLikeOperator(): string
247
    {
248
        if (self::driverName() === 'pgsql') {
249
            return 'ILIKE';
250
        }
251
252
        if (self::driverName() === 'sqlsrv') {
253
            return 'COLLATE SQL_UTF8_General_CI_AI LIKE';
254
        }
255
256
        return 'LIKE';
257
    }
258
259
    /**
260
     * @internal
261
     */
262
    public static function groupConcat(string $column): string
263
    {
264
        switch (DB::driverName()) {
265
            case 'pgsql':
266
            case 'sqlsrv':
267
                return 'STRING_AGG(' . $column . ", ',')";
268
269
            case 'mysql':
270
            case 'sqlite':
271
            default:
272
                return 'GROUP_CONCAT(' . $column . ')';
273
        }
274
    }
275
276
    /**
277
     * PHPSTAN can't detect the magic methods in the parent class.
278
     */
279
    public static function query(): Builder
280
    {
281
        return self::connection()->query();
282
    }
283
}
284