Passed
Push — main ( b8537a...489945 )
by Greg
07:15
created

DB::connect()   B

Complexity

Conditions 9
Paths 12

Size

Total Lines 66
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 28
nc 12
nop 11
dl 0
loc 66
rs 8.0555
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 Illuminate\Database\Capsule\Manager;
23
use Illuminate\Database\Query\Builder;
24
use Illuminate\Database\Query\Expression;
25
use PDO;
26
use PDOException;
27
use RuntimeException;
28
use SensitiveParameter;
1 ignored issue
show
Bug introduced by
The type SensitiveParameter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
30
/**
31
 * Database abstraction
32
 */
33
class DB extends Manager
34
{
35
    // Supported drivers
36
    public const MYSQL      = 'mysql';
37
    public const POSTGRES   = 'pgsql';
38
    public const SQLITE     = 'sqlite';
39
    public const SQL_SERVER = 'sqlsrv';
40
41
    private const COLLATION_ASCII = [
42
        self::MYSQL      => 'ascii_bin',
43
        self::POSTGRES   => 'C',
44
        self::SQLITE     => 'C',
45
        self::SQL_SERVER => 'Latin1_General_Bin',
46
    ];
47
48
    private const COLLATION_UTF8 = [
49
        self::MYSQL      => 'utf8mb4_unicode_ci',
50
        self::POSTGRES   => 'und-x-icu',
51
        self::SQLITE     => 'nocase',
52
        self::SQL_SERVER => 'utf8_CI_AI',
53
    ];
54
55
    private const REGEX_OPERATOR = [
56
        self::MYSQL      => 'REGEXP',
57
        self::POSTGRES   => '~',
58
        self::SQLITE     => 'REGEXP',
59
        self::SQL_SERVER => 'REGEXP',
60
    ];
61
62
    private const DRIVER_INITIALIZATION = [
63
        self::MYSQL      => "SET NAMES utf8mb4, sql_mode := 'ANSI,STRICT_ALL_TABLES', TIME_ZONE := '+00:00', SQL_BIG_SELECTS := 1, GROUP_CONCAT_MAX_LEN := 1048576",
64
        self::POSTGRES   => '',
65
        self::SQLITE     => 'PRAGMA foreign_keys = ON',
66
        self::SQL_SERVER => 'SET language us_english', // For timestamp columns
67
    ];
68
69
    public static function connect(
70
        #[SensitiveParameter]
71
        string $driver,
72
        #[SensitiveParameter]
73
        string $host,
74
        #[SensitiveParameter]
75
        string $port,
76
        #[SensitiveParameter]
77
        string $database,
78
        #[SensitiveParameter]
79
        string $username,
80
        #[SensitiveParameter]
81
        string $password,
82
        #[SensitiveParameter]
83
        string $prefix,
84
        #[SensitiveParameter]
85
        string $key,
86
        #[SensitiveParameter]
87
        string $certificate,
88
        #[SensitiveParameter]
89
        string $ca,
90
        #[SensitiveParameter]
91
        bool $verify_certificate,
92
    ): void {
93
        $options = [
94
            // Some drivers do this and some don't. Make them consistent.
95
            PDO::ATTR_STRINGIFY_FETCHES => true,
96
        ];
97
98
        // MySQL/MariaDB support encrypted connections
99
        if ($driver === self::MYSQL && $key !== '' && $certificate !== '' && $ca !== '') {
100
            $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $verify_certificate;
101
            $options[PDO::MYSQL_ATTR_SSL_KEY]                = Webtrees::ROOT_DIR . 'data/' . $key;
102
            $options[PDO::MYSQL_ATTR_SSL_CERT]               = Webtrees::ROOT_DIR . 'data/' . $certificate;
103
            $options[PDO::MYSQL_ATTR_SSL_CA]                 = Webtrees::ROOT_DIR . 'data/' . $ca;
104
        }
105
106
        if ($driver === self::SQLITE && $database !== ':memory:') {
107
            $database = Webtrees::ROOT_DIR . 'data/' . $database . '.sqlite';
108
        }
109
110
        $capsule = new self();
111
        $capsule->addConnection([
112
            'driver'         => $driver,
113
            'host'           => $host,
114
            'port'           => $port,
115
            'database'       => $database,
116
            'username'       => $username,
117
            'password'       => $password,
118
            'prefix'         => $prefix,
119
            'prefix_indexes' => true,
120
            'options'        => $options,
121
        ]);
122
        $capsule->setAsGlobal();
123
124
        // Eager-load the connection, to prevent database credentials appearing in error logs.
125
        try {
126
            self::pdo();
127
        } catch (PDOException $exception) {
128
            throw new RuntimeException($exception->getMessage());
129
        }
130
131
        $sql = self::DRIVER_INITIALIZATION[$driver];
132
133
        if ($sql !== '') {
134
            self::exec($sql);
135
        }
136
    }
137
138
    public static function driverName(): string
139
    {
140
        return self::pdo()->getAttribute(PDO::ATTR_DRIVER_NAME);
141
    }
142
143
    public static function exec(string $sql): int|false
144
    {
145
        return self::pdo()->exec($sql);
146
    }
147
148
    public static function lastInsertId(): int
149
    {
150
        $return = self::pdo()->lastInsertId();
151
152
        if ($return === false) {
153
            throw new RuntimeException('Unable to retrieve last insert ID');
154
        }
155
156
        // All IDs are integers in our schema.
157
        return (int) $return;
158
    }
159
160
    private static function pdo(): PDO
161
    {
162
        return parent::connection()->getPdo();
163
    }
164
165
    public static function prefix(string $identifier = ''): string
166
    {
167
        return parent::connection()->getTablePrefix() . $identifier;
168
    }
169
170
    public static function rollBack(): void
171
    {
172
        parent::connection()->rollBack();
173
    }
174
175
    /**
176
     * @internal
177
     */
178
    public static function iLike(): string
179
    {
180
        if (self::driverName() === self::POSTGRES) {
181
            return 'ILIKE';
182
        }
183
184
        if (self::driverName() === self::SQL_SERVER) {
185
            return 'COLLATE SQL_UTF8_General_CI_AI LIKE';
186
        }
187
188
        return 'LIKE';
189
    }
190
191
    /**
192
     * @internal
193
     */
194
    public static function groupConcat(string $column): string
195
    {
196
        switch (self::driverName()) {
197
            case self::POSTGRES:
198
            case self::SQL_SERVER:
199
                return 'STRING_AGG(' . $column . ", ',')";
200
201
            case self::MYSQL:
202
            case self::SQLITE:
203
            default:
204
                return 'GROUP_CONCAT(' . $column . ')';
205
        }
206
    }
207
208
    public static function binaryColumn(string $column, string|null $alias = null): Expression
209
    {
210
        if (self::driverName() === self::MYSQL) {
211
            $sql = 'CAST(' . $column . ' AS binary)';
212
        } else {
213
            $sql = $column;
214
        }
215
216
        if ($alias !== null) {
217
            $sql .= ' AS ' . $alias;
218
        }
219
220
        return new Expression($sql);
221
    }
222
223
    public static function regexOperator(): string
224
    {
225
        return self::REGEX_OPERATOR[self::driverName()];
226
    }
227
228
    /**
229
     * PHPSTAN can't detect the magic methods in the parent class.
230
     */
231
    public static function query(): Builder
232
    {
233
        return parent::connection()->query();
234
    }
235
}
236