Failed Conditions
Push — refactor/improve-static-analys... ( ba8000...c6edde )
by Bas
14:06
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
rs 10
ccs 0
cts 0
cp 0
crap 12
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 LogicException;
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  $config
43
     *
44 215
     * @throws UnknownProperties
45
     */
46 215
    public function __construct($config = [])
47
    {
48 215
        $this->config = $config;
49 215
50
        $this->database = (isset($this->config['database'])) ? $this->config['database'] : null;
51
        $this->tablePrefix = $this->config['tablePrefix'] ?? null;
52 215
53
        // activate and set the database client connection
54
        $this->arangoClient = new ArangoClient($this->config);
55
56
        // We need to initialize a query grammar and the query post processors
57 215
        // which are both very important parts of the database abstractions
58
        // so, we initialize these to their default values while starting.
59 215
        $this->useDefaultQueryGrammar();
60 215
61
        $this->useDefaultPostProcessor();
62
    }
63
64
    /**
65
     * Get a schema builder instance for the connection.
66
     */
67 163
    public function getSchemaBuilder(): SchemaBuilder
68
    {
69 163
        if (is_null($this->schemaGrammar)) {
70
            $this->useDefaultSchemaGrammar();
71
        }
72
73 163
        return new SchemaBuilder($this);
74
    }
75
76
    /**
77
     * Get the default query grammar instance.
78
     */
79
//    protected function getDefaultQueryGrammar(): QueryGrammar
80
//    {
81 215
//        return new QueryGrammar();
82
//    }
83 215
84
    /**
85
     * Get the default post processor instance.
86
     */
87
    protected function getDefaultPostProcessor(): Processor
88
    {
89
        return new Processor();
90
    }
91 215
92
    /**
93 215
     * Get the default query grammar instance.
94
     *
95
     * @return QueryGrammar
96
     */
97
    protected function getDefaultQueryGrammar()
98
    {
99
        ($grammar = new QueryGrammar())->setConnection($this);
100
101
        return $grammar;
102
    }
103
104
    /**
105
     * Get the schema grammar used by the connection.
106
     *
107
     * @return IlluminateGrammar
108
     */
109
    public function getSchemaGrammar()
110
    {
111 7
        return $this->schemaGrammar;
112
    }
113 7
114 7
    /**
115
     * Get the collection prefix for the connection.
116
     */
117
    public function getTablePrefix(): string
118
    {
119
        return $this->tablePrefix;
120
    }
121
122
    /**
123 3
     * Disconnect from the underlying ArangoDB connection.
124
     *
125 3
     * @return void
126 3
     */
127
    public function disconnect()
128 3
    {
129
        $this->arangoClient = null;
130 3
    }
131
132
    /**
133
     * Reconnect to the database.
134
     *
135
     * @throws \LogicException
136
     */
137
    public function reconnect()
138
    {
139
        if (is_callable($this->reconnector)) {
140
            //            $this->arangoClient = null;
141 174
142
            $result = call_user_func($this->reconnector, $this);
143 174
144
            return $result;
145
        }
146 174
147
        throw new LogicException('Lost connection and no reconnector available.');
148 175
    }
149
150 175
    /**
151
     * Reconnect to the database if an ArangoDB connection is missing.
152
     *
153
     * @return void
154
     */
155
    public function reconnectIfMissingConnection()
156 1
    {
157
        if (is_null($this->arangoClient)) {
158 1
            $this->reconnect();
159 1
        }
160 1
    }
161
162 2
    public function getArangoClient(): ArangoClient|null
163
    {
164 2
        return $this->arangoClient;
165
    }
166
167 4
    /**
168
     * @param  string|null  $database
169 4
     * @return $this
170
     */
171
    public function setDatabaseName($database)
172
    {
173
        $this->database = $database;
174
        $this->arangoClient->setDatabase($database);
0 ignored issues
show
Bug introduced by
The method setDatabase() does not exist on null. ( Ignorable by Annotation )

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

174
        $this->arangoClient->/** @scrutinizer ignore-call */ 
175
                             setDatabase($database);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
It seems like $database can also be of type null; however, parameter $name of ArangoClient\ArangoClient::setDatabase() 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

174
        $this->arangoClient->setDatabase(/** @scrutinizer ignore-type */ $database);
Loading history...
175
176
        return $this;
177
    }
178
179
    public function getDatabaseName(): string
180
    {
181
        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...
182
    }
183
184
    public static function aqb(): ArangoQueryBuilder
185
    {
186
        return new ArangoQueryBuilder();
187
    }
188
189
    /**
190
     * Escape a binary value for safe SQL embedding.
191
     *
192
     * @param  string  $value
193
     * @return string
194
     */
195
    protected function escapeBinary($value)
196
    {
197
        if (mb_detect_encoding($value, ['UTF-8'])) {
198
            return $value;
199
        }
200
201
        return base64_encode($value);
202
    }
203
204
    /**
205
     * Escape a value for safe SQL embedding.
206
     *
207
     * @param  array|string|float|int|bool|null  $value
208
     * @param  bool  $binary
209
     * @return string
210
     */
211
    public function escape($value, $binary = false)
212
    {
213
        if ($value === null) {
214
            return 'null';
215
        } elseif ($binary) {
216
            return $this->escapeBinary($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type array; however, parameter $value of LaravelFreelancerNL\Aran...nection::escapeBinary() 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
            return $this->escapeBinary(/** @scrutinizer ignore-type */ $value);
Loading history...
217
        } elseif (is_int($value) || is_float($value)) {
218
            return (string) $value;
219
        } elseif (is_bool($value)) {
220
            return $this->escapeBool($value);
221
        } elseif (is_array($value)) {
222
            return $this->escapeArray($value);
223
        } else {
224
            if (str_contains($value, "\00")) {
225
                throw new RuntimeException('Strings with null bytes cannot be escaped. Use the binary escape option.');
0 ignored issues
show
Bug introduced by
The type LaravelFreelancerNL\Aranguent\RuntimeException was not found. Did you mean RuntimeException? If so, make sure to prefix the type with \.
Loading history...
226
            }
227
228
            if (preg_match('//u', $value) === false) {
229
                throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.');
230
            }
231
232
            return $this->escapeString($value);
233
        }
234
    }
235
236
    /**
237
     * Escape an array value for safe SQL embedding.
238
     *
239
     * @param  array  $value
240
     * @return string
241
     */
242
    protected function escapeArray(array $array): string
243
    {
244
        foreach($array as $key => $value) {
245
            $array[$key] = $this->escape($value);
246
        }
247
248
        if (array_is_list($array)) {
249
            return '['.implode(', ', $array).']';
250
        }
251
252
        $grammar = $this->getDefaultQueryGrammar();
253
        return $grammar->generateAqlObject($array);
254
    }
255
256
    /**
257
     * Escape a boolean value for safe SQL embedding.
258
     *
259
     * @param  bool  $value
260
     * @return string
261
     */
262
    protected function escapeBool($value)
263
    {
264
        return $value ? 'true' : 'false';
265
    }
266
267
    /**
268
     * Escape a string value for safe SQL embedding.
269
     *
270
     * @param  string  $value
271
     * @return string
272
     */
273
    protected function escapeString($value)
274
    {
275
        return '"'.str_replace(
276
            ['\\', "\0", "\n", "\r", "'", '"', "\x1a"],
277
            ['\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'],
278
            $value
279
        ).'"';
280
    }
281
}
282