Issues (22)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/ConnectionTrait.php (10 issues)

Upgrade to new PHP Analysis Engine

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
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.

Loading history...
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 Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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 $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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.

Loading history...
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.

Loading history...
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 Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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.

Loading history...
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 Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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 Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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
Comprehensibility Bug introduced by
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 getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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