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) { |
|
| 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(); |
||
| 106 | ++$attempt; |
||
| 107 | $retry = true; |
||
|
0 ignored issues
–
show
|
|||
| 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: |
|
| 147 | $stmt = parent::query($args[0], $args[1], $args[2]); |
||
| 148 | |||
| 149 | break; |
||
| 150 | View Code Duplication | case 4: |
|
| 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(); |
||
| 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) { |
|
| 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(); |
||
| 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) { |
||
|
0 ignored issues
–
show
The method
getTransactionNestingLevel() does not exist on Adgoal\DBALFaultTolerance\ConnectionTrait. Did you maybe mean resetTransactionNestingLevel()?
This check marks calls to methods that do not seem to exist on an object. This is most likely the result of a method being renamed without all references to it being renamed likewise. Loading history...
|
|||
| 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(); |
||
| 240 | if ($this->getTransactionNestingLevel() > 0) { |
||
|
0 ignored issues
–
show
The method
getTransactionNestingLevel() does not exist on Adgoal\DBALFaultTolerance\ConnectionTrait. Did you maybe mean resetTransactionNestingLevel()?
This check marks calls to methods that do not seem to exist on an object. This is most likely the result of a method being renamed without all references to it being renamed likewise. Loading history...
|
|||
| 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); |
||
| 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; |
||
|
0 ignored issues
–
show
The method
getTransactionNestingLevel() does not exist on Adgoal\DBALFaultTolerance\ConnectionTrait. Did you maybe mean resetTransactionNestingLevel()?
This check marks calls to methods that do not seem to exist on an object. This is most likely the result of a method being renamed without all references to it being renamed likewise. Loading history...
|
|||
| 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 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVarassignment in line 1 and the$higherassignment in line 2 are dead. The first because$myVaris never used and the second because$higheris always overwritten for every possible time line.