Passed
Push — next ( 2342c5...a64239 )
by Bas
06:41 queued 03:16
created

Connection::escapeArray()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 12
ccs 0
cts 7
cp 0
crap 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent;
6
7
use ArangoClient\ArangoClient;
8
use Illuminate\Database\Connection as IlluminateConnection;
9
use Illuminate\Database\Schema\Grammars\Grammar as IlluminateGrammar;
10
use LaravelFreelancerNL\Aranguent\Concerns\DetectsDeadlocks;
11
use LaravelFreelancerNL\Aranguent\Concerns\DetectsLostConnections;
12
use LaravelFreelancerNL\Aranguent\Concerns\HandlesArangoDb;
13
use LaravelFreelancerNL\Aranguent\Concerns\ManagesTransactions;
14
use LaravelFreelancerNL\Aranguent\Concerns\RunsQueries;
15
use LaravelFreelancerNL\Aranguent\Query\Grammar as QueryGrammar;
16
use LaravelFreelancerNL\Aranguent\Query\Processor;
17
use LaravelFreelancerNL\Aranguent\Schema\Builder as SchemaBuilder;
18
use LaravelFreelancerNL\FluentAQL\QueryBuilder as ArangoQueryBuilder;
19
use RuntimeException;
20
use Spatie\DataTransferObject\Exceptions\UnknownProperties;
21
22
class Connection extends IlluminateConnection
23
{
24
    use HandlesArangoDb;
25
    use DetectsDeadlocks;
26
    use DetectsLostConnections;
27
    use ManagesTransactions;
28
    use RunsQueries;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aranguent\Concerns\RunsQueries requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Connection: $binds, $query
Loading history...
29
30
    protected ?ArangoClient $arangoClient = null;
31
32
    /**
33
     * The ArangoDB driver name.
34
     */
35
    protected string $driverName = 'arangodb';
36
37
    /**
38
     * Connection constructor.
39
     *
40
     * @param  array<mixed>  $config
41
     *
42
     * @throws UnknownProperties
43
     */
44 431
    public function __construct($config = [])
45
    {
46 431
        $this->config = $config;
47
48 431
        $this->database = (isset($this->config['database'])) ? $this->config['database'] : '';
49 431
        $this->tablePrefix = $this->config['tablePrefix'] ?? '';
50
51
        // activate and set the database client connection
52 431
        $this->arangoClient = new ArangoClient($this->config);
53
54
        // We need to initialize a query grammar and the query post processors
55
        // which are both very important parts of the database abstractions
56
        // so, we initialize these to their default values while starting.
57 431
        $this->useDefaultQueryGrammar();
58
59 431
        $this->useDefaultPostProcessor();
60
    }
61
62
    /**
63
     * Get a schema builder instance for the connection.
64
     */
65 96
    public function getSchemaBuilder(): SchemaBuilder
66
    {
67 96
        return new SchemaBuilder($this);
68
    }
69
70
    /**
71
     * Get the default query grammar instance.
72
     */
73
    //    protected function getDefaultQueryGrammar(): QueryGrammar
74
    //    {
75
    //        return new QueryGrammar();
76
    //    }
77
78
    /**
79
     * Get the default post processor instance.
80
     */
81 431
    protected function getDefaultPostProcessor(): Processor
82
    {
83 431
        return new Processor();
84
    }
85
86
    /**
87
     * Get the default query grammar instance.
88
     *
89
     * @return QueryGrammar
90
     */
91 431
    protected function getDefaultQueryGrammar()
92
    {
93 431
        ($grammar = new QueryGrammar())->setConnection($this);
94
95 431
        return $grammar;
96
    }
97
98
    /**
99
     * Get the schema grammar used by the connection.
100
     *
101
     * @return IlluminateGrammar
102
     */
103 96
    public function getSchemaGrammar()
104
    {
105 96
        return $this->schemaGrammar;
106
    }
107
108
    /**
109
     * Disconnect from the underlying ArangoDB connection.
110
     *
111
     * @return void
112
     */
113 137
    public function disconnect()
114
    {
115 137
        $this->arangoClient = null;
116
    }
117
118
    /**
119
     * Reconnect to the database.
120
     *
121
     * @throws \LogicException
122
     */
123 3
    public function reconnect()
124
    {
125 3
        if (is_callable($this->reconnector)) {
126 3
            return call_user_func($this->reconnector, $this);
127
        }
128
    }
129
130
    /**
131
     * Reconnect to the database if an ArangoDB connection is missing.
132
     *
133
     * @return void
134
     */
135 319
    public function reconnectIfMissingConnection()
136
    {
137 319
        if (is_null($this->arangoClient)) {
138
            $this->reconnect();
139
        }
140
    }
141
142 115
    public function getArangoClient(): ArangoClient|null
143
    {
144 115
        return $this->arangoClient;
145
    }
146
147
    public function setArangoClient(ArangoClient $arangoClient): void
148
    {
149
        $this->arangoClient = $arangoClient;
150
    }
151
152
    /**
153
     * Set the name of the connected database.
154
     *
155
     * @param  string  $database
156
     * @return $this
157
     */
158 2
    public function setDatabaseName($database)
159
    {
160 2
        $this->database = $database;
161
162 2
        if ($this->arangoClient !== null) {
163 2
            $this->arangoClient->setDatabase($database);
164
        }
165
166 2
        return $this;
167
    }
168
169 2
    public function getDatabaseName(): string
170
    {
171 2
        return $this->database;
172
    }
173
174
    public static function aqb(): ArangoQueryBuilder
175
    {
176
        return new ArangoQueryBuilder();
177
    }
178
179
    /**
180
     * Escape a binary value for safe SQL embedding.
181
     *
182
     * @param  string  $value
183
     * @return string
184
     */
185
    protected function escapeBinary($value)
186
    {
187
        if (mb_detect_encoding($value, ['UTF-8'])) {
188
            return $value;
189
        }
190
191
        return base64_encode($value);
192
    }
193
194
    /**
195
     * Escape a value for safe SQL embedding.
196
     *
197
     * @param  array<mixed>|string|float|int|bool|null  $value
198
     * @param  bool  $binary
199
     * @return string
200
     *
201
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
202
     */
203 4
    public function escape($value, $binary = false)
204
    {
205 4
        return match (gettype($value)) {
206
            'array' => $this->escapeArray($value),
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type boolean and double and integer and null and string; however, parameter $array of LaravelFreelancerNL\Aran...nnection::escapeArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

206
            'array' => $this->escapeArray(/** @scrutinizer ignore-type */ $value),
Loading history...
207
            'boolean' => $this->escapeBool($value),
208
            'double' => (string) $value,
209
            'integer' => (string) $value,
210
            'NULL' => 'null',
211 4
            default => $this->escapeString($value, $binary = false),
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type array<mixed,mixed>; however, parameter $value of LaravelFreelancerNL\Aran...nection::escapeString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

211
            default => $this->escapeString(/** @scrutinizer ignore-type */ $value, $binary = false),
Loading history...
212 4
        };
213
    }
214
215
    /**
216
     * Escape a string value for safe SQL embedding.
217
     *
218
     * @param  string  $value
219
     * @return string
220
     *
221
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
222
     */
223 4
    protected function escapeString($value, bool $binary = false)
224
    {
225 4
        if ($binary === true) {
226
            return $this->escapeBinary($value);
227
        }
228
229 4
        if (str_contains($value, "\00")) {
230
            throw new RuntimeException('Strings with null bytes cannot be escaped. Use the binary escape option.');
231
        }
232
233 4
        if (preg_match('//u', $value) === false) {
234
            throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.');
235
        }
236
237 4
        return '"' . str_replace(
238 4
            ['\\', "\0", "\n", "\r", "'", '"', "\x1a"],
239 4
            ['\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'],
240 4
            $value,
241 4
        ) . '"';
242
    }
243
244
    /**
245
     * Escape an array value for safe SQL embedding.
246
     *
247
     * @param  array<mixed>  $array
248
     * @return string
249
     */
250
    protected function escapeArray(array $array): string
251
    {
252
        foreach ($array as $key => $value) {
253
            $array[$key] = $this->escape($value);
254
        }
255
256
        if (array_is_list($array)) {
257
            return '[' . implode(', ', $array) . ']';
258
        }
259
260
        $grammar = $this->getDefaultQueryGrammar();
261
        return $grammar->generateAqlObject($array);
262
    }
263
264
    /**
265
     * Escape a boolean value for safe SQL embedding.
266
     *
267
     * @param  bool  $value
268
     * @return string
269
     */
270
    protected function escapeBool($value)
271
    {
272
        return $value ? 'true' : 'false';
273
    }
274
275
    /**
276
     * Get the elapsed time since a given starting point.
277
     *
278
     * @param  int|float  $start
279
     * @return float
280
     */
281 308
    protected function getElapsedTime($start)
282
    {
283 308
        return round((microtime(true) - $start) * 1000, 2);
284
    }
285
286
    /**
287
     * Get the number of open connections for the database.
288
     *
289
     * @return int|null
290
     */
291 1
    public function threadCount()
292
    {
293 1
        if (!$this->arangoClient) {
294
            return null;
295
        }
296
297 1
        return $this->arangoClient->monitor()->getCurrentConnections();
298
    }
299
300
    /**
301
     * Get the server version for the connection.
302
     *
303
     * @return string
304
     */
305 1
    public function getServerVersion(): string
306
    {
307 1
        if (!$this->arangoClient) {
308
            return '';
309
        }
310
311 1
        $rawVersion = $this->arangoClient->admin()->getVersion();
312
313 1
        return $rawVersion->version;
314
    }
315
}
316