This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace BenTools\SimpleDBAL\Model\Adapter\Mysqli; |
||
4 | |||
5 | use BenTools\SimpleDBAL\Contract\AdapterInterface; |
||
6 | use BenTools\SimpleDBAL\Contract\CredentialsInterface; |
||
7 | use BenTools\SimpleDBAL\Contract\ReconnectableAdapterInterface; |
||
8 | use BenTools\SimpleDBAL\Contract\ResultInterface; |
||
9 | use BenTools\SimpleDBAL\Contract\StatementInterface; |
||
10 | use BenTools\SimpleDBAL\Contract\TransactionAdapterInterface; |
||
11 | use BenTools\SimpleDBAL\Model\ConfigurableTrait; |
||
12 | use BenTools\SimpleDBAL\Model\Exception\AccessDeniedException; |
||
13 | use BenTools\SimpleDBAL\Model\Exception\DBALException; |
||
14 | use BenTools\SimpleDBAL\Model\Exception\MaxConnectAttempsException; |
||
15 | use BenTools\SimpleDBAL\Model\Exception\ParamBindingException; |
||
16 | use GuzzleHttp\Promise\Promise; |
||
17 | use GuzzleHttp\Promise\PromiseInterface; |
||
18 | use mysqli; |
||
19 | use mysqli_result; |
||
20 | use mysqli_sql_exception; |
||
21 | use Throwable; |
||
22 | |||
23 | class MysqliAdapter implements AdapterInterface, TransactionAdapterInterface, ReconnectableAdapterInterface |
||
24 | { |
||
25 | |||
26 | use ConfigurableTrait; |
||
27 | |||
28 | const OPT_RESOLVE_NAMED_PARAMS = 'resolve_named_params'; |
||
29 | const OPT_ENABLE_PARALLEL_QUERIES = 'enable_parallel_queries'; |
||
30 | const OPT_EMULATE_PREPARED_STATEMENTS = 'emulate_prepared_statements'; |
||
31 | |||
32 | /** |
||
33 | * @var mysqli |
||
34 | */ |
||
35 | private $cnx; |
||
36 | |||
37 | /** |
||
38 | * @var CredentialsInterface |
||
39 | */ |
||
40 | private $credentials; |
||
41 | |||
42 | /** |
||
43 | * @var int |
||
44 | */ |
||
45 | private $reconnectAttempts = 0; |
||
46 | |||
47 | /** |
||
48 | * MysqliAdapter constructor. |
||
49 | * @param mysqli $cnx |
||
50 | * @param CredentialsInterface|null $credentials |
||
51 | * @param array|null $options |
||
52 | */ |
||
53 | protected function __construct(mysqli $cnx, CredentialsInterface $credentials = null, array $options = null) |
||
54 | { |
||
55 | mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); |
||
56 | $this->cnx = $cnx; |
||
57 | $this->credentials = $credentials; |
||
58 | if (null !== $options) { |
||
59 | $this->options = array_replace($this->getDefaultOptions(), $options); |
||
60 | if ($this->hasOption('charset')) { |
||
61 | $this->cnx->set_charset($this->getOption('charset')); |
||
62 | } |
||
63 | } |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * @inheritDoc |
||
68 | */ |
||
69 | public function getWrappedConnection(): mysqli |
||
70 | { |
||
71 | return $this->cnx; |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * @inheritDoc |
||
76 | */ |
||
77 | public function getCredentials(): ?CredentialsInterface |
||
78 | { |
||
79 | return $this->credentials; |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * @inheritDoc |
||
84 | */ |
||
85 | public function isConnected(): bool |
||
86 | { |
||
87 | return false !== $this->cnx->stat(); |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * @inheritDoc |
||
92 | */ |
||
93 | public function shouldReconnect(): bool |
||
94 | { |
||
95 | return $this->reconnectAttempts < (int) $this->getOption(self::OPT_MAX_RECONNECT_ATTEMPTS); |
||
96 | } |
||
97 | |||
98 | private function reconnect() |
||
99 | { |
||
100 | if (0 === (int) $this->getOption(self::OPT_MAX_RECONNECT_ATTEMPTS)) { |
||
101 | throw new MaxConnectAttempsException("Connection lost."); |
||
102 | } elseif ($this->reconnectAttempts === (int) $this->getOption(self::OPT_MAX_RECONNECT_ATTEMPTS)) { |
||
103 | throw new MaxConnectAttempsException("Max attempts to connect to database has been reached."); |
||
104 | } |
||
105 | |||
106 | if (null === $this->credentials) { |
||
107 | throw new AccessDeniedException("Unable to reconnect: credentials not provided."); |
||
108 | } |
||
109 | |||
110 | try { |
||
111 | $this->cnx = self::createLink($this->getCredentials()); |
||
112 | if ($this->isConnected()) { |
||
113 | $this->reconnectAttempts = 0; |
||
114 | } else { |
||
115 | $this->reconnect(); |
||
116 | } |
||
117 | if ($this->hasOption('charset')) { |
||
118 | $this->cnx->set_charset($this->getOption('charset')); |
||
119 | } |
||
120 | } catch (Throwable $e) { |
||
121 | $this->reconnectAttempts++; |
||
122 | } |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * @param string $queryString |
||
127 | * @return bool |
||
128 | */ |
||
129 | protected function hasNamedParameters(string $queryString): bool |
||
130 | { |
||
131 | return preg_match('#:([a-zA-Z0-9_]+)#', $queryString); |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * @inheritDoc |
||
136 | */ |
||
137 | public function prepare(string $queryString, array $values = null): StatementInterface |
||
138 | { |
||
139 | |||
140 | if (true === $this->getOption(self::OPT_EMULATE_PREPARED_STATEMENTS)) { |
||
141 | return $this->emulatePrepare($queryString, $values); |
||
142 | } |
||
143 | |||
144 | if (true === $this->getOption(self::OPT_RESOLVE_NAMED_PARAMS) && $this->hasNamedParameters($queryString)) { |
||
145 | $runnableQueryString = $this->convertToRunnableQuery($queryString); |
||
146 | } else { |
||
147 | $runnableQueryString = &$queryString; |
||
148 | } |
||
149 | |||
150 | try { |
||
151 | $wrappedStmt = self::wrapWithErrorHandler(function () use ($runnableQueryString) { |
||
152 | return $this->cnx->prepare($runnableQueryString); |
||
153 | }); |
||
154 | } catch (mysqli_sql_exception $e) { |
||
155 | if (!$this->isConnected()) { |
||
156 | $this->reconnect(); |
||
157 | return $this->prepare($queryString, $values); |
||
158 | } |
||
159 | throw new DBALException($e->getMessage(), (int) $e->getCode(), $e); |
||
160 | } |
||
161 | return new Statement($this, $wrappedStmt, $values, $queryString, $runnableQueryString); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param string $queryString |
||
166 | * @param array|null $values |
||
167 | * @return EmulatedStatement |
||
168 | */ |
||
169 | private function emulatePrepare(string $queryString, array $values = null): EmulatedStatement |
||
170 | { |
||
171 | return new EmulatedStatement($this, $values, $queryString); |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * @param Statement|string $stmt |
||
176 | * @param array|null $values |
||
177 | * @return Result |
||
178 | */ |
||
179 | public function execute($stmt, array $values = null): ResultInterface |
||
180 | { |
||
181 | if (is_string($stmt)) { |
||
182 | $stmt = $this->prepare($stmt, $values); |
||
183 | } else { |
||
184 | if (!$stmt instanceof Statement) { |
||
185 | throw new \InvalidArgumentException(sprintf('Expected %s object, got %s', Statement::class, get_class($stmt))); |
||
186 | } |
||
187 | if (null !== $values) { |
||
188 | $stmt = $stmt->withValues($values); |
||
189 | } |
||
190 | } |
||
191 | |||
192 | try { |
||
193 | $result = $this->runStmt($stmt); |
||
0 ignored issues
–
show
|
|||
194 | return $result; |
||
195 | } catch (Throwable $e) { |
||
196 | if (!$this->isConnected()) { |
||
197 | $this->reconnect(); |
||
198 | return $this->execute($this->prepare((string) $stmt, $stmt->getValues())); |
||
199 | } |
||
200 | throw $e; |
||
201 | } |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * @inheritdoc |
||
206 | */ |
||
207 | public function executeAsync($stmt, array $values = null): PromiseInterface |
||
208 | { |
||
209 | |||
210 | if (true === $this->getOption(self::OPT_ENABLE_PARALLEL_QUERIES)) { |
||
211 | return $this->executeParallel($stmt, $values); |
||
212 | } |
||
213 | |||
214 | $promise = new Promise(function () use (&$promise, $stmt, $values) { |
||
215 | try { |
||
216 | $promise->resolve($this->execute($stmt, $values)); |
||
217 | } catch (Throwable $e) { |
||
218 | $promise->reject($e); |
||
219 | } |
||
220 | }); |
||
221 | return $promise; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * EXPERIMENTAL ! Executes a statement asynchronously. |
||
226 | * The promise will return a Result object. |
||
227 | * |
||
228 | * @param $stmt |
||
229 | * @param array|null $values |
||
230 | * @return PromiseInterface |
||
231 | */ |
||
232 | private function executeParallel($stmt, array $values = null): PromiseInterface |
||
233 | { |
||
234 | if (is_string($stmt)) { |
||
235 | $stmt = $this->emulatePrepare((string) $stmt, $values); |
||
236 | } else { |
||
237 | if (!$stmt instanceof Statement) { |
||
238 | throw new \InvalidArgumentException(sprintf('Expected %s object, got %s', Statement::class, get_class($stmt))); |
||
239 | } |
||
240 | if (!$stmt instanceof EmulatedStatement) { |
||
241 | $stmt = $this->emulatePrepare((string) $stmt, $values ?? $stmt->getValues()); |
||
242 | } elseif (null !== $values) { |
||
243 | $stmt = $stmt->withValues($values); |
||
244 | } |
||
245 | } |
||
246 | try { |
||
247 | // Clone connection (Mysqli Asynchronous queries require a different connection to work properly) |
||
248 | $credentials = $this->getCredentials(); |
||
249 | $cnx = self::createLink($credentials); |
||
250 | if ($this->hasOption('charset')) { |
||
251 | $cnx->set_charset($this->getOption('charset')); |
||
252 | } |
||
253 | } catch (mysqli_sql_exception $e) { |
||
254 | throw new AccessDeniedException($e->getMessage(), (int) $e->getCode(), $e); |
||
255 | } |
||
256 | |||
257 | $stmt->bind(); |
||
258 | $promise = MysqliAsync::query($stmt->getRunnableQuery(), $cnx)->then(function ($result) use ($cnx, $stmt) { |
||
259 | if (!$result instanceof mysqli_result) { |
||
260 | $result = null; |
||
261 | } |
||
262 | return new Result($cnx, $result); |
||
263 | }); |
||
264 | |||
265 | return $promise; |
||
266 | } |
||
267 | |||
268 | private function runStmt(Statement $stmt) |
||
269 | { |
||
270 | try { |
||
271 | return self::wrapWithErrorHandler(function () use ($stmt) { |
||
272 | return $stmt->createResult(); |
||
273 | }); |
||
274 | } catch (mysqli_sql_exception $e) { |
||
275 | if (false !== strpos($e->getMessage(), 'No data supplied for parameters in prepared statement')) { |
||
276 | throw new ParamBindingException($e->getMessage(), (int) $e->getCode(), $e, $stmt); |
||
277 | } elseif (false !== strpos($e->getMessage(), "Number of variables doesn't match number of parameters in prepared statement")) { |
||
278 | throw new ParamBindingException($e->getMessage(), (int) $e->getCode(), $e, $stmt); |
||
279 | } else { |
||
280 | throw new DBALException($e->getMessage(), (int) $e->getCode(), $e); |
||
281 | } |
||
282 | } |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * @inheritDoc |
||
287 | */ |
||
288 | public function beginTransaction(): void |
||
289 | { |
||
290 | $this->getWrappedConnection()->autocommit(false); |
||
0 ignored issues
–
show
The method
autocommit does only exist in mysqli , but not in PDO .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
Loading history...
|
|||
291 | } |
||
292 | |||
293 | /** |
||
294 | * @inheritDoc |
||
295 | */ |
||
296 | public function commit(): void |
||
297 | { |
||
298 | $this->getWrappedConnection()->commit(); |
||
299 | $this->getWrappedConnection()->autocommit(true); |
||
0 ignored issues
–
show
The method
autocommit does only exist in mysqli , but not in PDO .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
Loading history...
|
|||
300 | } |
||
301 | |||
302 | /** |
||
303 | * @inheritDoc |
||
304 | */ |
||
305 | public function rollback(): void |
||
306 | { |
||
307 | $this->getWrappedConnection()->rollback(); |
||
308 | $this->getWrappedConnection()->autocommit(true); |
||
0 ignored issues
–
show
The method
autocommit does only exist in mysqli , but not in PDO .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
Loading history...
|
|||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Convert a query with named parameters (not natively supported by mysqli) |
||
313 | * |
||
314 | * @param string $queryString |
||
315 | * @return string |
||
316 | */ |
||
317 | private function convertToRunnableQuery(string $queryString): string |
||
318 | { |
||
319 | return preg_replace('#:([a-zA-Z0-9_]+)#', '?', $queryString); |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * @inheritDoc |
||
324 | */ |
||
325 | public function getDefaultOptions(): array |
||
326 | { |
||
327 | return [ |
||
328 | self::OPT_MAX_RECONNECT_ATTEMPTS => self::DEFAULT_MAX_RECONNECT_ATTEMPTS, |
||
329 | self::OPT_USLEEP_AFTER_FIRST_ATTEMPT => self::DEFAULT_USLEEP_AFTER_FIRST_ATTEMPT, |
||
330 | self::OPT_RESOLVE_NAMED_PARAMS => false, |
||
331 | self::OPT_EMULATE_PREPARED_STATEMENTS => false, |
||
332 | self::OPT_ENABLE_PARALLEL_QUERIES => false, |
||
333 | ]; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * @param CredentialsInterface $credentials |
||
338 | * @param bool $resolveNamedParameters |
||
0 ignored issues
–
show
There is no parameter named
$resolveNamedParameters . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not.
Loading history...
|
|||
339 | * @return MysqliAdapter |
||
340 | */ |
||
341 | public static function factory(CredentialsInterface $credentials, array $options = null): self |
||
342 | { |
||
343 | return new static(self::createLink($credentials), $credentials, $options); |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * @param mysqli $link |
||
348 | * @param CredentialsInterface|null $credentials |
||
349 | * @return MysqliAdapter |
||
350 | */ |
||
351 | public static function createFromLink(mysqli $link, CredentialsInterface $credentials = null): self |
||
352 | { |
||
353 | return new static($link, $credentials); |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * @param CredentialsInterface $credentials |
||
358 | * @return mysqli |
||
359 | */ |
||
360 | private static function createLink(CredentialsInterface $credentials): mysqli |
||
361 | { |
||
362 | mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); |
||
363 | try { |
||
364 | return new mysqli($credentials->getHostname(), $credentials->getUser(), $credentials->getPassword(), $credentials->getDatabase(), $credentials->getPort()); |
||
365 | } catch (mysqli_sql_exception $e) { |
||
366 | throw new AccessDeniedException($e->getMessage(), (int) $e->getCode(), $e); |
||
367 | } |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * @param callable $run |
||
372 | * @return mixed|void |
||
373 | */ |
||
374 | private static function wrapWithErrorHandler(callable $run) |
||
375 | { |
||
376 | $errorHandler = function ($errno, $errstr) { |
||
377 | throw new mysqli_sql_exception($errstr, $errno); |
||
378 | }; |
||
379 | set_error_handler($errorHandler, E_WARNING); |
||
380 | $result = $run(); |
||
381 | restore_error_handler(); |
||
382 | return $result; |
||
383 | } |
||
384 | } |
||
385 |
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.
Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.