Passed
Push — refactor/improve-static-analys... ( efcf20...37f12c )
by Bas
03:04
created

Connection::escapeString()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.25

Importance

Changes 0
Metric Value
cc 4
eloc 11
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 19
ccs 9
cts 12
cp 0.75
crap 4.25
rs 9.9
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
    protected $database;
33
34
    /**
35
     * The ArangoDB driver name.
36
     */
37
    protected string $driverName = 'arangodb';
38
39
    /**
40
     * Connection constructor.
41
     *
42
     * @param  array<mixed>  $config
43
     *
44
     * @throws UnknownProperties
45
     */
46 310
    public function __construct($config = [])
47
    {
48 310
        $this->config = $config;
49
50 310
        $this->database = (isset($this->config['database'])) ? $this->config['database'] : null;
51 310
        $this->tablePrefix = $this->config['tablePrefix'] ?? null;
52
53
        // activate and set the database client connection
54 310
        $this->arangoClient = new ArangoClient($this->config);
55
56
        // We need to initialize a query grammar and the query post processors
57
        // which are both very important parts of the database abstractions
58
        // so, we initialize these to their default values while starting.
59 310
        $this->useDefaultQueryGrammar();
60
61 310
        $this->useDefaultPostProcessor();
62
    }
63
64
    /**
65
     * Get a schema builder instance for the connection.
66
     */
67 35
    public function getSchemaBuilder(): SchemaBuilder
68
    {
69 35
        return new SchemaBuilder($this);
70
    }
71
72
    /**
73
     * Get the default query grammar instance.
74
     */
75
    //    protected function getDefaultQueryGrammar(): QueryGrammar
76
    //    {
77
    //        return new QueryGrammar();
78
    //    }
79
80
    /**
81
     * Get the default post processor instance.
82
     */
83 310
    protected function getDefaultPostProcessor(): Processor
84
    {
85 310
        return new Processor();
86
    }
87
88
    /**
89
     * Get the default query grammar instance.
90
     *
91
     * @return QueryGrammar
92
     */
93 310
    protected function getDefaultQueryGrammar()
94
    {
95 310
        ($grammar = new QueryGrammar())->setConnection($this);
96
97 310
        return $grammar;
98
    }
99
100
    /**
101
     * Get the schema grammar used by the connection.
102
     *
103
     * @return IlluminateGrammar
104
     */
105 35
    public function getSchemaGrammar()
106
    {
107 35
        return $this->schemaGrammar;
108
    }
109
110
    /**
111
     * Get the collection prefix for the connection.
112
     */
113
    public function getTablePrefix(): string
114
    {
115
        return $this->tablePrefix;
116
    }
117
118
    /**
119
     * Disconnect from the underlying ArangoDB connection.
120
     *
121
     * @return void
122
     */
123 111
    public function disconnect()
124
    {
125 111
        $this->arangoClient = null;
126
    }
127
128
    /**
129
     * Reconnect to the database.
130
     *
131
     * @throws \LogicException
132
     */
133 3
    public function reconnect()
134
    {
135 3
        if (is_callable($this->reconnector)) {
136 3
            return call_user_func($this->reconnector, $this);
137
        }
138
    }
139
140
    /**
141
     * Reconnect to the database if an ArangoDB connection is missing.
142
     *
143
     * @return void
144
     */
145 225
    public function reconnectIfMissingConnection()
146
    {
147 225
        if (is_null($this->arangoClient)) {
148
            $this->reconnect();
149
        }
150
    }
151
152 50
    public function getArangoClient(): ArangoClient|null
153
    {
154 50
        return $this->arangoClient;
155
    }
156
157
    /**
158
     * Set the name of the connected database.
159
     *
160
     * @param  string  $database
161
     * @return $this
162
     */
163 2
    public function setDatabaseName($database)
164
    {
165 2
        $this->database = $database;
166
167 2
        if ($this->arangoClient !== null) {
168 2
            $this->arangoClient->setDatabase($database);
169
        }
170
171 2
        return $this;
172
    }
173
174 2
    public function getDatabaseName(): string
175
    {
176 2
        return $this->database;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->database could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
177
    }
178
179
    public static function aqb(): ArangoQueryBuilder
180
    {
181
        return new ArangoQueryBuilder();
182
    }
183
184
    /**
185
     * Escape a binary value for safe SQL embedding.
186
     *
187
     * @param  string  $value
188
     * @return string
189
     */
190
    protected function escapeBinary($value)
191
    {
192
        if (mb_detect_encoding($value, ['UTF-8'])) {
193
            return $value;
194
        }
195
196
        return base64_encode($value);
197
    }
198
199
    /**
200
     * Escape a value for safe SQL embedding.
201
     *
202
     * @param  array<mixed>|string|float|int|bool|null  $value
203
     * @param  bool  $binary
204
     * @return string
205
     *
206
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
207
     */
208 4
    public function escape($value, $binary = false)
209
    {
210 4
        return match(gettype($value)) {
211 4
            '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

211
            'array' => $this->escapeArray(/** @scrutinizer ignore-type */ $value),
Loading history...
212 4
            'boolean' => $this->escapeBool($value),
213 4
            'double' => (string) $value,
214 4
            'integer' => (string) $value,
215 4
            'NULL' => 'null',
216 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

216
            default => $this->escapeString(/** @scrutinizer ignore-type */ $value, $binary = false),
Loading history...
217 4
        };
218
    }
219
220
    /**
221
     * Escape a string value for safe SQL embedding.
222
     *
223
     * @param  string  $value
224
     * @return string
225
     *
226
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
227
     */
228 4
    protected function escapeString($value, bool $binary = false)
229
    {
230 4
        if ($binary === true) {
231
            return $this->escapeBinary($value);
232
        }
233
234 4
        if (str_contains($value, "\00")) {
235
            throw new RuntimeException('Strings with null bytes cannot be escaped. Use the binary escape option.');
236
        }
237
238 4
        if (preg_match('//u', $value) === false) {
239
            throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.');
240
        }
241
242 4
        return '"' . str_replace(
243 4
            ['\\', "\0", "\n", "\r", "'", '"', "\x1a"],
244 4
            ['\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'],
245 4
            $value
246 4
        ) . '"';
247
    }
248
249
    /**
250
     * Escape an array value for safe SQL embedding.
251
     *
252
     * @param  array<mixed>  $array
253
     * @return string
254
     */
255
    protected function escapeArray(array $array): string
256
    {
257
        foreach($array as $key => $value) {
258
            $array[$key] = $this->escape($value);
259
        }
260
261
        if (array_is_list($array)) {
262
            return '[' . implode(', ', $array) . ']';
263
        }
264
265
        $grammar = $this->getDefaultQueryGrammar();
266
        return $grammar->generateAqlObject($array);
267
    }
268
269
    /**
270
     * Escape a boolean value for safe SQL embedding.
271
     *
272
     * @param  bool  $value
273
     * @return string
274
     */
275
    protected function escapeBool($value)
276
    {
277
        return $value ? 'true' : 'false';
278
    }
279
280
    /**
281
     * Get the elapsed time since a given starting point.
282
     *
283
     * @param  int|float  $start
284
     * @return float
285
     */
286 213
    protected function getElapsedTime($start)
287
    {
288 213
        return round((microtime(true) - $start) * 1000, 2);
289
    }
290
}
291