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 | declare(strict_types=1); |
||
4 | |||
5 | namespace Adgoal\DBALFaultTolerance; |
||
6 | |||
7 | use Adgoal\DBALFaultTolerance\Driver\DriverInterface; |
||
8 | use Adgoal\DBALFaultTolerance\Events\Args\ReconnectEventArgs; |
||
9 | use Doctrine\Common\EventManager; |
||
10 | use Doctrine\DBAL\Cache\QueryCacheProfile; |
||
11 | use Doctrine\DBAL\Configuration; |
||
12 | use Doctrine\DBAL\DBALException; |
||
13 | use Doctrine\DBAL\Driver; |
||
14 | use Doctrine\DBAL\Driver\ResultStatement; |
||
15 | use Doctrine\DBAL\Exception\InvalidArgumentException; |
||
16 | use ReflectionClass; |
||
17 | use ReflectionException; |
||
18 | use ReflectionProperty; |
||
19 | use Throwable; |
||
20 | |||
21 | /** |
||
22 | * Trait ConnectionTrait. |
||
23 | */ |
||
24 | trait ConnectionTrait |
||
25 | { |
||
26 | /** |
||
27 | * The used DBAL driver. |
||
28 | * |
||
29 | * Overwrite for type hint |
||
30 | * |
||
31 | * @var DriverInterface |
||
32 | */ |
||
33 | protected $_driver; |
||
34 | |||
35 | /** |
||
36 | * @var int |
||
37 | */ |
||
38 | protected $reconnectAttempts = 0; |
||
39 | |||
40 | /** |
||
41 | * @var int|bool |
||
42 | */ |
||
43 | protected $forceIgnoreTransactionLevel; |
||
44 | |||
45 | /** |
||
46 | * @var ReflectionProperty|null |
||
47 | */ |
||
48 | private $selfReflectionNestingLevelProperty; |
||
49 | |||
50 | /** |
||
51 | * ConnectionTrait constructor. |
||
52 | * |
||
53 | * @param mixed[] $params |
||
54 | * @param Driver $driver |
||
55 | * @param Configuration|null $config |
||
56 | * @param EventManager|null $eventManager |
||
57 | * |
||
58 | * @throws DBALException |
||
59 | */ |
||
60 | 14 | public function __construct( |
|
61 | array $params, |
||
62 | Driver $driver, |
||
63 | Configuration $config = null, |
||
64 | EventManager $eventManager = null |
||
65 | ) { |
||
66 | 14 | if (! $driver instanceof DriverInterface) { |
|
67 | throw new InvalidArgumentException( |
||
68 | sprintf('%s needs a driver that implements DriverInterface', static::class) |
||
69 | ); |
||
70 | } |
||
71 | |||
72 | 14 | if (isset($params['driverOptions']['x_reconnect_attempts'])) { |
|
73 | 14 | $this->reconnectAttempts = (int) $params['driverOptions']['x_reconnect_attempts']; |
|
74 | } |
||
75 | |||
76 | 14 | if (isset($params['driverOptions']['force_ignore_transaction_level'])) { |
|
77 | $this->forceIgnoreTransactionLevel = (int) $params['driverOptions']['force_ignore_transaction_level']; |
||
78 | } |
||
79 | |||
80 | 14 | parent::__construct($params, $driver, $config, $eventManager); |
|
81 | } |
||
82 | |||
83 | /** |
||
84 | * @param string $query |
||
85 | * @param array $params |
||
86 | * @param array $types |
||
87 | * @param QueryCacheProfile $qcp |
||
88 | * |
||
89 | * @return ResultStatement the executed statement |
||
90 | * |
||
91 | * @throws Throwable |
||
92 | */ |
||
93 | public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null) |
||
94 | { |
||
95 | $stmt = null; |
||
96 | $attempt = 0; |
||
97 | $retry = true; |
||
98 | View Code Duplication | while ($retry) { |
|
0 ignored issues
–
show
|
|||
99 | $retry = false; |
||
100 | |||
101 | try { |
||
102 | $stmt = parent::executeQuery($query, $params, $types, $qcp); |
||
103 | } catch (Throwable $e) { |
||
104 | if ($this->canTryAgain($attempt) && $this->isRetryableException($e, $query)) { |
||
105 | $this->close(); |
||
0 ignored issues
–
show
It seems like
close() must be provided by classes using this trait. How about adding it as abstract method to this trait?
This check looks for methods that are used by a trait but not required by it. To illustrate, let’s look at the following code example trait Idable {
public function equalIds(Idable $other) {
return $this->getId() === $other->getId();
}
}
The trait Adding the ![]() |
|||
106 | ++$attempt; |
||
107 | $retry = true; |
||
0 ignored issues
–
show
$retry is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
108 | |||
109 | $this->getEventManager()->dispatchEvent( |
||
110 | Events\Events::RECONNECT_TO_DATABASE, |
||
111 | new ReconnectEventArgs(__FUNCTION__, $attempt, $query) |
||
112 | ); |
||
113 | } |
||
114 | |||
115 | throw $e; |
||
116 | } |
||
117 | } |
||
118 | |||
119 | return $stmt; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * @return \Doctrine\DBAL\Driver\Statement |
||
124 | * |
||
125 | * @throws Throwable |
||
126 | */ |
||
127 | public function query() |
||
128 | { |
||
129 | $stmt = null; |
||
130 | $args = func_get_args(); |
||
131 | $attempt = 0; |
||
132 | $retry = true; |
||
133 | while ($retry) { |
||
134 | $retry = false; |
||
135 | |||
136 | try { |
||
137 | switch (count($args)) { |
||
138 | case 1: |
||
139 | $stmt = parent::query($args[0]); |
||
140 | |||
141 | break; |
||
142 | case 2: |
||
143 | $stmt = parent::query($args[0], $args[1]); |
||
144 | |||
145 | break; |
||
146 | View Code Duplication | case 3: |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
147 | $stmt = parent::query($args[0], $args[1], $args[2]); |
||
148 | |||
149 | break; |
||
150 | View Code Duplication | case 4: |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
151 | $stmt = parent::query($args[0], $args[1], $args[2], $args[3]); |
||
152 | |||
153 | break; |
||
154 | default: |
||
155 | $stmt = parent::query(); |
||
156 | } |
||
157 | } catch (Throwable $e) { |
||
158 | if ($this->canTryAgain($attempt) && $this->isRetryableException($e, $args[0])) { |
||
159 | $this->close(); |
||
0 ignored issues
–
show
It seems like
close() must be provided by classes using this trait. How about adding it as abstract method to this trait?
This check looks for methods that are used by a trait but not required by it. To illustrate, let’s look at the following code example trait Idable {
public function equalIds(Idable $other) {
return $this->getId() === $other->getId();
}
}
The trait Adding the ![]() |
|||
160 | ++$attempt; |
||
161 | $retry = true; |
||
162 | |||
163 | $this->getEventManager()->dispatchEvent( |
||
164 | Events\Events::RECONNECT_TO_DATABASE, |
||
165 | new ReconnectEventArgs(__FUNCTION__, $attempt, count($args) ? $args[0] : '', $args) |
||
166 | ); |
||
167 | } else { |
||
168 | throw $e; |
||
169 | } |
||
170 | } |
||
171 | } |
||
172 | |||
173 | return $stmt; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * @param string $query |
||
178 | * @param array $params |
||
179 | * @param array $types |
||
180 | * |
||
181 | * @return int the number of affected rows |
||
182 | * |
||
183 | * @throws Throwable |
||
184 | */ |
||
185 | public function executeUpdate($query, array $params = [], array $types = []) |
||
186 | { |
||
187 | $stmt = null; |
||
188 | $attempt = 0; |
||
189 | $retry = true; |
||
190 | View Code Duplication | while ($retry) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
191 | $retry = false; |
||
192 | |||
193 | try { |
||
194 | $stmt = parent::executeUpdate($query, $params, $types); |
||
195 | } catch (Throwable $e) { |
||
196 | if ($this->canTryAgain($attempt) && $this->isRetryableException($e)) { |
||
197 | $this->close(); |
||
0 ignored issues
–
show
It seems like
close() must be provided by classes using this trait. How about adding it as abstract method to this trait?
This check looks for methods that are used by a trait but not required by it. To illustrate, let’s look at the following code example trait Idable {
public function equalIds(Idable $other) {
return $this->getId() === $other->getId();
}
}
The trait Adding the ![]() |
|||
198 | ++$attempt; |
||
199 | $retry = true; |
||
200 | |||
201 | $this->getEventManager()->dispatchEvent( |
||
202 | Events\Events::RECONNECT_TO_DATABASE, |
||
203 | new ReconnectEventArgs(__FUNCTION__, $attempt, $query) |
||
204 | ); |
||
205 | } else { |
||
206 | throw $e; |
||
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | return $stmt; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * @return bool|void |
||
216 | * |
||
217 | * @throws ReflectionException |
||
218 | * @throws Throwable |
||
219 | */ |
||
220 | public function beginTransaction() |
||
221 | { |
||
222 | if ($this->getTransactionNestingLevel() !== 0) { |
||
223 | return parent::beginTransaction(); |
||
224 | } |
||
225 | |||
226 | $attempt = 0; |
||
227 | $retry = true; |
||
228 | while ($retry) { |
||
229 | $retry = false; |
||
230 | |||
231 | try { |
||
232 | parent::beginTransaction(); |
||
233 | } catch (Throwable $e) { |
||
234 | if ( |
||
235 | $this->canTryAgain($attempt, true) |
||
236 | && |
||
237 | $this->_driver->isGoneAwayException($e) |
||
238 | ) { |
||
239 | $this->close(); |
||
0 ignored issues
–
show
It seems like
close() must be provided by classes using this trait. How about adding it as abstract method to this trait?
This check looks for methods that are used by a trait but not required by it. To illustrate, let’s look at the following code example trait Idable {
public function equalIds(Idable $other) {
return $this->getId() === $other->getId();
}
}
The trait Adding the ![]() |
|||
240 | if ($this->getTransactionNestingLevel() > 0) { |
||
241 | $this->resetTransactionNestingLevel(); |
||
242 | } |
||
243 | ++$attempt; |
||
244 | $retry = true; |
||
245 | |||
246 | $this->getEventManager()->dispatchEvent( |
||
247 | Events\Events::RECONNECT_TO_DATABASE, |
||
248 | new ReconnectEventArgs(__FUNCTION__, $attempt, 'BEGIN TRANSACTION') |
||
249 | ); |
||
250 | } else { |
||
251 | throw $e; |
||
252 | } |
||
253 | } |
||
254 | } |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * Prepares an SQL statement. |
||
259 | * |
||
260 | * @param string $sql |
||
261 | * |
||
262 | * @return Statement the prepared statement |
||
263 | */ |
||
264 | public function prepare($sql) |
||
265 | { |
||
266 | return $this->prepareWrapped($sql); |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * do not use, only used by Statement-class |
||
271 | * needs to be public for access from the Statement-class. |
||
272 | * |
||
273 | * @param $sql |
||
274 | * |
||
275 | * @return Driver\Statement |
||
276 | * |
||
277 | * @throws DBALException |
||
278 | * |
||
279 | * @internal |
||
280 | */ |
||
281 | public function prepareUnwrapped(string $sql): \Doctrine\DBAL\Driver\Statement |
||
282 | { |
||
283 | // returns the actual statement |
||
284 | return parent::prepare($sql); |
||
0 ignored issues
–
show
It seems like you call parent on a different method (
prepare() instead of prepareUnwrapped() ). Are you sure this is correct? If so, you might want to change this to $this->prepare() .
This check looks for a call to a parent method whose name is different than the method from which it is called. Consider the following code: class Daddy
{
protected function getFirstName()
{
return "Eidur";
}
protected function getSurName()
{
return "Gudjohnsen";
}
}
class Son
{
public function getFirstName()
{
return parent::getSurname();
}
}
The ![]() |
|||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Forces reconnection by doing a dummy query. |
||
289 | * |
||
290 | * @throws Throwable |
||
291 | */ |
||
292 | public function refresh(): void |
||
293 | { |
||
294 | $this->query('SELECT 1')->execute(); |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param $attempt |
||
299 | * @param bool $ignoreTransactionLevel |
||
300 | * |
||
301 | * @return bool |
||
302 | */ |
||
303 | public function canTryAgain(int $attempt, bool $ignoreTransactionLevel = false): bool |
||
304 | { |
||
305 | $canByAttempt = ($attempt < $this->reconnectAttempts); |
||
306 | $ignoreTransactionLevel = $this->forceIgnoreTransactionLevel ? true : $ignoreTransactionLevel; |
||
307 | |||
308 | $canByTransactionNestingLevel = $ignoreTransactionLevel ? true : $this->getTransactionNestingLevel() === 0; |
||
309 | |||
310 | return $canByAttempt && $canByTransactionNestingLevel; |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * @param Throwable $e |
||
315 | * @param string|null $query |
||
316 | * |
||
317 | * @return bool |
||
318 | */ |
||
319 | public function isRetryableException(Throwable $e, string $query = null): bool |
||
320 | { |
||
321 | if ($query === null || $this->isUpdateQuery($query)) { |
||
322 | return $this->_driver->isGoneAwayInUpdateException($e); |
||
323 | } |
||
324 | |||
325 | return $this->_driver->isGoneAwayException($e); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * @param string $query |
||
330 | * |
||
331 | * @return bool |
||
332 | */ |
||
333 | 12 | public function isUpdateQuery($query): bool |
|
334 | { |
||
335 | 12 | return ! preg_match('/^[\s\n\r\t(]*(select|show|describe)[\s\n\r\t(]+/i', $query); |
|
336 | } |
||
337 | |||
338 | /** |
||
339 | * returns a reconnect-wrapper for Statements. |
||
340 | * |
||
341 | * @param $sql |
||
342 | * |
||
343 | * @return Statement |
||
344 | */ |
||
345 | protected function prepareWrapped(string $sql): Driver\Statement |
||
346 | { |
||
347 | return new Statement($sql, $this); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * This is required because beginTransaction increment transactionNestingLevel |
||
352 | * before the real query is executed, and results incremented also on gone away error. |
||
353 | * This should be safe for a new established connection. |
||
354 | * |
||
355 | * @throws ReflectionException |
||
356 | * @throws ReflectionException |
||
357 | */ |
||
358 | private function resetTransactionNestingLevel(): void |
||
359 | { |
||
360 | if (! $this->selfReflectionNestingLevelProperty instanceof ReflectionProperty) { |
||
361 | $reflection = new ReflectionClass(\Doctrine\DBAL\Connection::class); |
||
362 | |||
363 | // Private property has been renamed in DBAL 2.9.0+ |
||
364 | if ($reflection->hasProperty('transactionNestingLevel')) { |
||
365 | $this->selfReflectionNestingLevelProperty = $reflection->getProperty('transactionNestingLevel'); |
||
366 | } else { |
||
367 | $this->selfReflectionNestingLevelProperty = $reflection->getProperty('_transactionNestingLevel'); |
||
368 | } |
||
369 | |||
370 | $this->selfReflectionNestingLevelProperty->setAccessible(true); |
||
371 | } |
||
372 | |||
373 | $this->selfReflectionNestingLevelProperty->setValue($this, 0); |
||
374 | } |
||
375 | } |
||
376 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.