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
![]() |
|||||
29 | |||||
30 | /** |
||||
31 | * @var ArangoClient|null |
||||
32 | */ |
||||
33 | protected $arangoClient; |
||||
34 | |||||
35 | /** |
||||
36 | * The ArangoDB driver name. |
||||
37 | */ |
||||
38 | protected string $driverName = 'arangodb'; |
||||
39 | |||||
40 | /** |
||||
41 | * Connection constructor. |
||||
42 | * |
||||
43 | * @param array<mixed> $config |
||||
44 | * |
||||
45 | * @throws UnknownProperties |
||||
46 | */ |
||||
47 | 498 | public function __construct($config = []) |
|||
48 | { |
||||
49 | 498 | $this->config = $config; |
|||
50 | |||||
51 | 498 | $this->database = (isset($this->config['database'])) ? $this->config['database'] : ''; |
|||
52 | 498 | $this->tablePrefix = $this->config['tablePrefix'] ?? ''; |
|||
53 | |||||
54 | // activate and set the database client connection |
||||
55 | 498 | $this->arangoClient = new ArangoClient($this->config); |
|||
56 | |||||
57 | // We need to initialize a query grammar and the query post processors |
||||
58 | // which are both very important parts of the database abstractions |
||||
59 | // so, we initialize these to their default values while starting. |
||||
60 | 498 | $this->useDefaultQueryGrammar(); |
|||
61 | |||||
62 | 498 | $this->useDefaultPostProcessor(); |
|||
63 | } |
||||
64 | |||||
65 | /** |
||||
66 | * Get a schema builder instance for the connection. |
||||
67 | */ |
||||
68 | 134 | public function getSchemaBuilder(): SchemaBuilder |
|||
69 | { |
||||
70 | 134 | return new SchemaBuilder($this); |
|||
71 | } |
||||
72 | |||||
73 | /** |
||||
74 | * Get the default query grammar instance. |
||||
75 | */ |
||||
76 | // protected function getDefaultQueryGrammar(): QueryGrammar |
||||
77 | // { |
||||
78 | // return new QueryGrammar(); |
||||
79 | // } |
||||
80 | |||||
81 | /** |
||||
82 | * Get the default post processor instance. |
||||
83 | */ |
||||
84 | 498 | protected function getDefaultPostProcessor(): Processor |
|||
85 | { |
||||
86 | 498 | return new Processor(); |
|||
87 | } |
||||
88 | |||||
89 | /** |
||||
90 | * Get the default query grammar instance. |
||||
91 | * |
||||
92 | * @return QueryGrammar |
||||
93 | */ |
||||
94 | 498 | protected function getDefaultQueryGrammar() |
|||
95 | { |
||||
96 | 498 | $grammar = new QueryGrammar($this); |
|||
97 | |||||
98 | 498 | return $grammar; |
|||
99 | } |
||||
100 | |||||
101 | /** |
||||
102 | * Get the schema grammar used by the connection. |
||||
103 | * |
||||
104 | * @return IlluminateGrammar |
||||
105 | */ |
||||
106 | 134 | public function getSchemaGrammar() |
|||
107 | { |
||||
108 | 134 | return $this->schemaGrammar; |
|||
109 | } |
||||
110 | |||||
111 | /** |
||||
112 | * Disconnect from the underlying ArangoDB connection. |
||||
113 | * |
||||
114 | * @return void |
||||
115 | */ |
||||
116 | 157 | public function disconnect() |
|||
117 | { |
||||
118 | 157 | $this->arangoClient = null; |
|||
119 | } |
||||
120 | |||||
121 | /** |
||||
122 | * Reconnect to the database. |
||||
123 | * |
||||
124 | * @throws \LogicException |
||||
125 | */ |
||||
126 | 3 | public function reconnect() |
|||
127 | { |
||||
128 | 3 | if (is_callable($this->reconnector)) { |
|||
129 | 3 | return call_user_func($this->reconnector, $this); |
|||
130 | } |
||||
131 | } |
||||
132 | |||||
133 | /** |
||||
134 | * Reconnect to the database if an ArangoDB connection is missing. |
||||
135 | * |
||||
136 | * @return void |
||||
137 | */ |
||||
138 | 366 | public function reconnectIfMissingConnection() |
|||
139 | { |
||||
140 | 366 | if (is_null($this->arangoClient)) { |
|||
141 | $this->reconnect(); |
||||
142 | } |
||||
143 | } |
||||
144 | |||||
145 | 153 | public function getArangoClient(): ArangoClient|null |
|||
146 | { |
||||
147 | 153 | return $this->arangoClient; |
|||
148 | } |
||||
149 | |||||
150 | public function setArangoClient(ArangoClient $arangoClient): void |
||||
151 | { |
||||
152 | $this->arangoClient = $arangoClient; |
||||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * Set the name of the connected database. |
||||
157 | * |
||||
158 | * @param string $database |
||||
159 | * @return $this |
||||
160 | */ |
||||
161 | 2 | public function setDatabaseName($database) |
|||
162 | { |
||||
163 | 2 | $this->database = $database; |
|||
164 | |||||
165 | 2 | if ($this->arangoClient !== null) { |
|||
166 | 2 | $this->arangoClient->setDatabase($database); |
|||
167 | } |
||||
168 | |||||
169 | 2 | return $this; |
|||
170 | } |
||||
171 | |||||
172 | 2 | public function getDatabaseName(): string |
|||
173 | { |
||||
174 | 2 | return $this->database; |
|||
175 | } |
||||
176 | |||||
177 | public static function aqb(): ArangoQueryBuilder |
||||
178 | { |
||||
179 | return new ArangoQueryBuilder(); |
||||
180 | } |
||||
181 | |||||
182 | /** |
||||
183 | * Escape a binary value for safe SQL embedding. |
||||
184 | * |
||||
185 | * @param string $value |
||||
186 | * @return string |
||||
187 | */ |
||||
188 | protected function escapeBinary($value) |
||||
189 | { |
||||
190 | if (mb_detect_encoding($value, ['UTF-8'])) { |
||||
191 | return $value; |
||||
192 | } |
||||
193 | |||||
194 | return base64_encode($value); |
||||
195 | } |
||||
196 | |||||
197 | /** |
||||
198 | * Escape a value for safe SQL embedding. |
||||
199 | * |
||||
200 | * @param array<mixed>|string|float|int|bool|null $value |
||||
201 | * @param bool $binary |
||||
202 | * @return string |
||||
203 | * |
||||
204 | * @SuppressWarnings("PHPMD.BooleanArgumentFlag") |
||||
205 | */ |
||||
206 | 4 | public function escape($value, $binary = false) |
|||
207 | { |
||||
208 | 4 | return match (gettype($value)) { |
|||
209 | 'array' => $this->escapeArray($value), |
||||
0 ignored issues
–
show
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
![]() |
|||||
210 | 'boolean' => $this->escapeBool($value), |
||||
211 | 'double' => (string) $value, |
||||
212 | 'integer' => (string) $value, |
||||
213 | 'NULL' => 'null', |
||||
214 | 4 | default => $this->escapeString($value, $binary = false), |
|||
0 ignored issues
–
show
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
![]() |
|||||
215 | 4 | }; |
|||
216 | } |
||||
217 | |||||
218 | /** |
||||
219 | * Escape a string value for safe SQL embedding. |
||||
220 | * |
||||
221 | * @param string $value |
||||
222 | * @return string |
||||
223 | * |
||||
224 | * @SuppressWarnings("PHPMD.BooleanArgumentFlag") |
||||
225 | */ |
||||
226 | 4 | protected function escapeString($value, bool $binary = false) |
|||
227 | { |
||||
228 | 4 | if ($binary === true) { |
|||
229 | return $this->escapeBinary($value); |
||||
230 | } |
||||
231 | |||||
232 | 4 | if (str_contains($value, "\00")) { |
|||
233 | throw new RuntimeException('Strings with null bytes cannot be escaped. Use the binary escape option.'); |
||||
234 | } |
||||
235 | |||||
236 | 4 | if (preg_match('//u', $value) === false) { |
|||
237 | throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.'); |
||||
238 | } |
||||
239 | |||||
240 | 4 | return '"' . str_replace( |
|||
241 | 4 | ['\\', "\0", "\n", "\r", "'", '"', "\x1a"], |
|||
242 | 4 | ['\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'], |
|||
243 | 4 | $value, |
|||
244 | 4 | ) . '"'; |
|||
245 | } |
||||
246 | |||||
247 | /** |
||||
248 | * Escape an array value for safe SQL embedding. |
||||
249 | * |
||||
250 | * @param array<mixed> $array |
||||
251 | * @return string |
||||
252 | */ |
||||
253 | protected function escapeArray(array $array): string |
||||
254 | { |
||||
255 | foreach ($array as $key => $value) { |
||||
256 | $array[$key] = $this->escape($value); |
||||
257 | } |
||||
258 | |||||
259 | if (array_is_list($array)) { |
||||
260 | return '[' . implode(', ', $array) . ']'; |
||||
261 | } |
||||
262 | |||||
263 | $grammar = $this->getDefaultQueryGrammar(); |
||||
264 | return $grammar->generateAqlObject($array); |
||||
265 | } |
||||
266 | |||||
267 | /** |
||||
268 | * Escape a boolean value for safe SQL embedding. |
||||
269 | * |
||||
270 | * @param bool $value |
||||
271 | * @return string |
||||
272 | */ |
||||
273 | protected function escapeBool($value) |
||||
274 | { |
||||
275 | return $value ? 'true' : 'false'; |
||||
276 | } |
||||
277 | |||||
278 | /** |
||||
279 | * Get the elapsed time since a given starting point. |
||||
280 | * |
||||
281 | * @param int|float $start |
||||
282 | * @return float |
||||
283 | */ |
||||
284 | 355 | protected function getElapsedTime($start) |
|||
285 | { |
||||
286 | 355 | return round((microtime(true) - $start) * 1000, 2); |
|||
287 | } |
||||
288 | |||||
289 | /** |
||||
290 | * Get the number of open connections for the database. |
||||
291 | * |
||||
292 | * @return int|null |
||||
293 | */ |
||||
294 | 7 | public function threadCount() |
|||
295 | { |
||||
296 | 7 | if (!$this->arangoClient) { |
|||
297 | return null; |
||||
298 | } |
||||
299 | |||||
300 | 7 | return $this->arangoClient->monitor()->getCurrentConnections(); |
|||
301 | } |
||||
302 | |||||
303 | /** |
||||
304 | * Get the server version for the connection. |
||||
305 | * |
||||
306 | * @return string |
||||
307 | */ |
||||
308 | 1 | public function getServerVersion(): string |
|||
309 | { |
||||
310 | 1 | if (!$this->arangoClient) { |
|||
311 | return ''; |
||||
312 | } |
||||
313 | |||||
314 | 1 | $rawVersion = $this->arangoClient->admin()->getVersion(); |
|||
315 | |||||
316 | 1 | return $rawVersion->version; |
|||
317 | } |
||||
318 | } |
||||
319 |