GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Push — master ( 18c025...2702d5 )
by Charlotte
03:32
created

Driver::getConnectionState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Plasma Driver MySQL component
4
 * Copyright 2018 PlasmaPHP, All Rights Reserved
5
 *
6
 * Website: https://github.com/PlasmaPHP
7
 * License: https://github.com/PlasmaPHP/driver-mysql/blob/master/LICENSE
8
*/
9
10
namespace Plasma\Drivers\MySQL;
11
12
/**
13
 * The MySQL Driver.
14
 * @internal
15
 */
16
class Driver implements \Plasma\DriverInterface {
17
    use \Evenement\EventEmitterTrait;
18
    
19
    /**
20
     * @var \React\EventLoop\LoopInterface
21
     */
22
    protected $loop;
23
    
24
    /**
25
     * @var array
26
     */
27
    protected $options = array(
28
        'tls.context' => array(),
29
        'tls.force' => false,
30
        'tls.ignoreIPs' => array(
31
            'localhost',
32
            '127.0.0.1'
33
        )
34
    );
35
    
36
    /**
37
     * @var \React\Socket\ConnectorInterface
38
     */
39
    protected $connector;
40
    
41
    /**
42
     * Internal class is intentional used, as there's no other way currently.
43
     * @var \React\Socket\StreamEncryption
44
     * @see https://github.com/reactphp/socket/issues/180
45
     */
46
    protected $encryption;
47
    
48
    /**
49
     * @var \React\Socket\Connection
50
     */
51
    protected $connection;
52
    
53
    /**
54
     * @var int
55
     */
56
    protected $connectionState = \Plasma\DriverInterface::CONNECTION_CLOSED;
57
    
58
    /**
59
     * @var \Plasma\Drivers\MySQL\ProtocolParser
60
     */
61
    protected $parser;
62
    
63
    /**
64
     * @var array
65
     */
66
    protected $queue;
67
    
68
    /**
69
     * @var int
70
     */
71
    protected $busy = \Plasma\DriverInterface::STATE_IDLE;
72
    
73
    /**
74
     * @var bool
75
     */
76
    protected $transaction = false;
77
    
78
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $loop should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $options should have a doc-comment as per coding-style.
Loading history...
79
     * Constructor.
80
     */
81 15
    function __construct(\React\EventLoop\LoopInterface $loop, array $options) {
82 15
        $this->validateOptions($options);
83
        
84 15
        $this->loop = $loop;
85 15
        $this->options = \array_merge($this->options, $options);
86
        
87 15
        $this->connector = ($options['connector'] ?? (new \React\Socket\Connector($loop)));
88 15
        $this->encryption = new \React\Socket\StreamEncryption($this->loop, false);
89 15
        $this->queue = array();
90 15
    }
91
    
92
    /**
93
     * Returns the event loop.
94
     * @var \React\EventLoop\LoopInterface
95
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
96 1
    function getLoop(): \React\EventLoop\LoopInterface {
97 1
        return $this->loop;
98
    }
99
    
100
    /**
101
     * Retrieves the current connection state.
102
     * @return int
103
     */
104 8
    function getConnectionState(): int {
105 8
        return $this->connectionState;
106
    }
107
    
108
    /**
109
     * Retrieves the current busy state.
110
     * @return int
111
     */
112 1
    function getBusyState(): int {
113 1
        return $this->busy;
114
    }
115
    
116
    /**
117
     * Get the length of the driver backlog queue.
118
     * @return int
119
     */
120 1
    function getBacklogLength(): int {
121 1
        return \count($this->queue);
122
    }
123
    
124
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $uri should have a doc-comment as per coding-style.
Loading history...
125
     * Connects to the given URI.
126
     * @return \React\Promise\PromiseInterface
127
     */
128 11
    function connect(string $uri): \React\Promise\PromiseInterface {
129 11
        $parts = \parse_url($uri);
130 11
        if(!isset($parts['host'])) {
131 10
            return \React\Promise\reject((new \InvalidArgumentException('Invalid connect uri given')));
132
        }
133
        
134 1
        $host = $parts['host'].':'.($parts['port'] ?? 3306);
135 1
        $this->connectionState = static::CONNECTION_STARTED;
136
        
137
        return $this->connector->connect($host)->then(function (\React\Socket\ConnectionInterface $connection) use ($parts) {
138
            // See description of property encryption
139 1
            if(!($connection instanceof \React\Socket\Connection)) {
140
                throw new \LogicException('Custom connection class is NOT supported yet (encryption limitation)');
141
            }
142
            
143 1
            $this->busy = static::STATE_BUSY;
144 1
            $this->connectionState = static::CONNECTION_MADE;
145 1
            $this->connection = $connection;
146
            
147
            $this->connection->on('error', function (\Throwable $error) {
148
                $this->emit('error', array($error));
149 1
            });
150
            
151
            $this->connection->on('close', function () {
152 1
                $this->connection = null;
153 1
                $this->connectionState = static::CONNECTION_UNUSABLE;
154
                
155 1
                $this->emit('close');
156 1
            });
157
            
158 1
            $this->parser = new \Plasma\Drivers\MySQL\ProtocolParser($this, $this->connection);
159
            
160 1
            $user = ($parts['user'] ?? 'root');
161 1
            $password = ($parts['password'] ?? '');
162 1
            $db = (!empty($parts['path']) ? \ltrim($parts['path'], '/') : '');
163
            
164 1
            $credentials = \compact('user', 'password', 'db');
165 1
            $deferred = new \React\Promise\Deferred();
166
            
167 1
            $this->startHandshake($db, $credentials, $deferred);
168 1
            return $deferred->promise();
169 1
        });
170
    }
171
    
172
    /**
173
     * Pauses the underlying stream I/O consumption.
174
     * If consumption is already paused, this will do nothing.
175
     * @return bool  Whether the operation was successful.
176
     */
177
    function pauseStreamConsumption(): bool {
178
        $this->connection->pause();
179
        return true;
180
    }
181
    
182
    /**
183
     * Resumes the underlying stream I/O consumption.
184
     * If consumption is not paused, this will do nothing.
185
     * @return bool  Whether the operation was successful.
186
     */
187
    function resumeStreamConsumption(): bool {
188
        $this->connection->resume();
189
        return true;
190
    }
191
    
192
    /**
193
     * Closes all connections gracefully after processing all outstanding requests.
194
     * @return \React\Promise\PromiseInterface
195
     */
196
    function close(): \React\Promise\PromiseInterface {
197
        if($this->goingAway) {
198
            return $this->goingAway->promise();
199
        }
200
        
201
        $this->goingAway = new \React\Promise\Deferred();
0 ignored issues
show
Bug Best Practice introduced by
The property goingAway does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
202
        
203
        if(\count($this->queue) === 0) {
204
            $this->goingAway->resolve();
205
        }
206
        
207
        return $this->goingAway->promise()->then(function () {
208
            $deferred = new \React\Promise\Deferred();
209
            
210
            $quit = new \Plasma\Drivers\MySQL\Commands\QuitCommand();
211
            
212
            $connection->once('close', function () use (&$deferred) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $connection seems to be never defined.
Loading history...
213
                $deferred->resolve();
214
            });
215
            
216
            $quit->once('end', function () {
217
                $this->connection->close();
218
            });
219
            
220
            $this->parser->invokeCommand($quit);
221
            
222
            return $deferred->promise();
223
        });
224
    }
225
    
226
    /**
227
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all outstanding requests.
228
     * @return void
229
     */
230
    function quit(): void {
231
        /** @var \Plasma\Drivers\MySQL\Commands\CommandInterface  $command */
232
        while($command = \array_shift($this->queue)) {
233
            $command->emit('error', array((new \Plasma\Exception('Connection is going away'))));
234
        }
235
        
236
        $quit = new \Plasma\Drivers\MySQL\Commands\QuitCommand();
237
        $this->parser->invokeCommand($quit);
238
        
239
        $this->connection->close();
240
    }
241
    
242
    /**
243
     * Whether this driver is currently in a transaction.
244
     * @return bool
245
     */
246
    function isInTransaction(): bool {
247
        return $this->transaction;
248
    }
249
    
250
    /**
251
     * Executes a plain query. Resolves with a `QueryResultInterface` instance.
252
     * When the command is done, the driver must check itself back into the client.
253
     * @param \Plasma\ClientInterface  $client
254
     * @param string                   $query
255
     * @return \React\Promise\PromiseInterface
256
     * @throws \Plasma\Exception
257
     * @see \Plasma\QueryResultInterface
258
     */
259
    function query(\Plasma\ClientInterface $client, string $query): \React\Promise\PromiseInterface {
260
        $command = new \Plasma\Drivers\MySQL\Commands\QueryCommand($this, $query);
261
        $this->executeCommand($command);
262
        
263
        $command->once('end', function () use (&$client) {
264
            $client->checkinConnection($this);
265
        });
266
        
267
        return $command->getPromise();
268
    }
269
    
270
    /**
271
     * Prepares a query. Resolves with a `StatementInterface` instance.
272
     * When the command is done, the driver must check itself back into the client.
273
     * @param \Plasma\ClientInterface  $client
274
     * @param string                   $query
275
     * @return \React\Promise\PromiseInterface
276
     * @throws \Plasma\Exception
277
     * @see \Plasma\StatementInterface
278
     */
279
    function prepare(\Plasma\ClientInterface $client, string $query): \React\Promise\PromiseInterface {
280
        $command = new \Plasma\Drivers\MySQL\Commands\PrepareCommand($client, $this, $query);
281
        $this->executeCommand($command);
282
        
283
        return $command->getPromise();
284
    }
285
    
286
    /**
287
     * Prepares and executes a query. Resolves with a `QueryResultInterface` instance.
288
     * This is equivalent to prepare -> execute -> close.
289
     * If you need to execute a query multiple times, prepare the query manually for performance reasons.
290
     * @param \Plasma\ClientInterface  $client
291
     * @param string                   $query
292
     * @param array                    $params
293
     * @return \React\Promise\PromiseInterface
294
     * @throws \Plasma\Exception
295
     * @see \Plasma\StatementInterface
296
     */
297
    function execute(\Plasma\ClientInterface $client, string $query, array $params = array()): \React\Promise\PromiseInterface {
298
        return $this->prepare($client, $query)->then(function (\Plasma\StatementInterface $statement) use ($params) {
299
            return $statement->execute($params)->then(function (\Plasma\QueryResultInterface $result) use (&$statement) {
300
                return $statement->close()->then(function () use ($result) {
301
                    return $result;
302
                });
303
            }, function (\Throwable $error) use (&$statement) {
304
                return $statement->close()->then(function () use ($error) {
305
                    throw $error;
306
                });
307
            });
308
        })->always(function () use (&$client) {
0 ignored issues
show
Bug introduced by
The method always() does not exist on React\Promise\PromiseInterface. It seems like you code against a sub-type of said class. However, the method does not exist in React\Promise\SimpleFulfilledTestPromise or React\Promise\SimpleRejectedTestPromise or React\Promise\CancellablePromiseInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

308
        })->/** @scrutinizer ignore-call */ always(function () use (&$client) {
Loading history...
309
            $client->checkinConnection($this);
310
        });
311
    }
312
    
313
    /**
314
     * Quotes the string for use in the query.
315
     * @param string  $str
316
     * @return string
317
     * @throws \LogicException  Thrown if the driver does not support quoting.
318
     * @throws \Plasma\Exception
319
     */
320
    function quote(string $str): string { // TODO
0 ignored issues
show
Coding Style introduced by
Opening brace must be the last content on the line
Loading history...
321
        throw new \LogicException('Not implemented yet');
322
    }
323
    
324
    /**
325
     * Begins a transaction. Resolves with a `TransactionInterface` instance.
326
     *
327
     * Checks out a connection until the transaction gets committed or rolled back.
328
     * It must be noted that the user is responsible for finishing the transaction. The client WILL NOT automatically
329
     * check the connection back into the pool, as long as the transaction is not finished.
330
     *
331
     * Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL)
332
     * statement such as DROP TABLE or CREATE TABLE is issued within a transaction.
333
     * The implicit COMMIT will prevent you from rolling back any other changes within the transaction boundary.
334
     * @param \Plasma\ClientInterface  $client
335
     * @param int                      $isolation  See the `TransactionInterface` constants.
336
     * @return \React\Promise\PromiseInterface
337
     * @throws \Plasma\Exception
338
     * @see \Plasma\TransactionInterface
339
     */
340
    function beginTransaction(\Plasma\ClientInterface $client, int $isolation = \Plasma\TransactionInterface::ISOLATION_COMMITTED): \React\Promise\PromiseInterface {
341
        if($this->transaction) {
342
            throw new \Plasma\Exception('Driver is already in transaction');
343
        }
344
        
345
        switch ($isolation) {
346
            case \Plasma\TransactionInterface::ISOLATION_UNCOMMITTED:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
347
                $query = 'SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED';
348
            break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 16 spaces, found 12
Loading history...
349
            case \Plasma\TransactionInterface::ISOLATION_COMMITTED:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
350
                $query = 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED';
351
            break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 16 spaces, found 12
Loading history...
352
            case \Plasma\TransactionInterface::ISOLATION_REPEATABLE:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
353
                $query = 'SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ';
354
            break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 16 spaces, found 12
Loading history...
355
            case \Plasma\TransactionInterface::ISOLATION_SERIALIZABLE:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
356
                $query = 'SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE';
357
            break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 16 spaces, found 12
Loading history...
358
            default:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
359
                throw new \Plasma\Exception('Invalid isolation level given');
360
            break;
361
        }
362
        
363
        return $this->query($query)->then(function () {
0 ignored issues
show
Bug introduced by
$query of type string is incompatible with the type Plasma\ClientInterface expected by parameter $client of Plasma\Drivers\MySQL\Driver::query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

363
        return $this->query(/** @scrutinizer ignore-type */ $query)->then(function () {
Loading history...
Bug introduced by
The call to Plasma\Drivers\MySQL\Driver::query() has too few arguments starting with query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

363
        return $this->/** @scrutinizer ignore-call */ query($query)->then(function () {

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
364
            return $this->query('START TRANSACTION');
0 ignored issues
show
Bug introduced by
The call to Plasma\Drivers\MySQL\Driver::query() has too few arguments starting with query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

364
            return $this->/** @scrutinizer ignore-call */ query('START TRANSACTION');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'START TRANSACTION' of type string is incompatible with the type Plasma\ClientInterface expected by parameter $client of Plasma\Drivers\MySQL\Driver::query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

364
            return $this->query(/** @scrutinizer ignore-type */ 'START TRANSACTION');
Loading history...
365
        })->then(function () use (&$client, $isolation) {
366
            $this->transaction = true;
367
            return (new \Plasma\Transaction($client, $this, $isolation));
368
        });
369
    }
370
    
371
    /**
372
     * Informationally closes a transaction. This method is used by `Transaction` to inform the driver of the end of the transaction.
373
     * @return void
374
     */
375
    function endTransaction(): void {
376
        $this->transaction = false;
377
    }
378
    
379
    /**
380
     * Runs the given command.
381
     * Returns a Promise, which resolves with the `end` event argument (defaults to `null),
382
     * or rejects with the `Throwable` of the `error` event.
383
     * When the command is done, the driver must check itself back into the client.
384
     * @param \Plasma\ClientInterface   $client
385
     * @param \Plasma\CommandInterface  $command
386
     * @return \React\Promise\PromiseInterface
387
     */
388
    function runCommand(\Plasma\ClientInterface $client, \Plasma\CommandInterface $command) {
389
        return (new \React\Promise\Promise(function (callable $resolve, callable $reject) use (&$client, &$command) {
390
            $command->once('end', function ($value = null) use (&$client, &$resolve) {
391
                $client->checkinConnection($this);
392
                $resolve($value);
393
            });
394
            
395
            $command->once('error', function (\Throwable $error) use (&$client, &$reject) {
396
                $client->checkinConnection($this);
397
                $reject($error);
398
            });
399
            
400
            $this->executeCommand($command);
401
        }));
402
    }
403
    
404
    /**
405
     * Executes a command.
406
     * @param \Plasma\CommandInterface  $command
407
     * @return void
408
     */
409
    function executeCommand(\Plasma\CommandInterface $command): void {
410
        $this->queue[] = $command;
411
        
412
        if($this->parser && $this->parser->getCurrentCommand() === null) {
413
            $this->parser->invokeCommand($this->getNextCommand());
414
        }
415
    }
416
    
417
    /**
418
     * Get the next command, or null.
419
     * @return \Plasma\CommandInterface|null
420
     */
421
    function getNextCommand(): ?\Plasma\CommandInterface {
422
        if(\count($this->queue) === 0) {
423
            if($this->goingAway) {
424
                $this->goingAway->resolve();
425
            }
426
            
427
            return null;
428
        }
429
        
430
        /** @var \Plasma\CommandInterface  $command */
431
        $command =  \array_shift($this->queue);
432
        
433
        if($command->waitForCompletion()) {
434
            $this->busy = static::STATE_BUSY;
435
        }
436
        
437
        $command->once('end', function () {
438
            $this->busy = static::STATE_IDLE;
439
            
440
            if($this->goingAway && \count($this->queue) === 0) {
441
                return $this->goingAway->resolve();
442
            }
443
            
444
            $this->parser->invokeCommand($this->getNextCommand());
445
        });
446
        
447
        return $command;
448
    }
449
    
450
    /**
451
     * Starts the handshake process.
452
     * @param string                   $db
453
     * @param array                    $credentials
454
     * @param \React\Promise\Deferred  $deferred
455
     * @return void
456
     */
457 1
    protected function startHandshake(string $db, array $credentials, \React\Promise\Deferred $deferred) {
458
        $listener = function (\Plasma\Drivers\MySQL\Messages\MessageInterface $message) use ($db, $credentials, &$deferred, &$listener) {
459
            if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
460
                $this->parser->removeListener('message', $listener);
461
                
462
                $this->connectionState = static::CONNECTION_SETENV;
463
                $clientFlags = \Plasma\Drivers\MySQL\ProtocolParser::CLIENT_CAPABILITIES;
464
                
465
                if($db !== '') {
466
                    $clientFlags |= \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_CONNECT_WITH_DB;
467
                }
468
                
469
                // Check if we support auth plugins
470
                $plugins = \Plasma\Drivers\MySQL\DriverFactory::getAuthPlugins();
471
                $plugin = null;
472
                
473
                foreach($plugins as $key => $plug) {
474
                    if(\is_int($key) && ($message->capability & $key) !== 0) {
475
                        $plugin = $plug;
476
                        $clientFlags |= \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_PLUGIN_AUTH;
477
                        break;
478
                    } elseif($key === $message->authPluginName) {
479
                        $plugin = $plug;
480
                        $clientFlags |= \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_PLUGIN_AUTH;
481
                        break;
482
                    }
483
                }
484
                
485
                $remote = $this->connection->getRemoteAddress();
486
                $ignored = \in_array($remote, $this->options['tls.ignoreIPs']);
487
                
488
                // If SSL supported, connect through SSL
489
                if(!$ignored && ($message->capability & \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_SSL) !== 0) {
490
                    $clientFlags |= \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_SSL;
491
                    
492
                    $ssl = new \Plasma\Drivers\MySQL\Commands\SSLRequestCommand($message, $clientFlags);
493
                    
494
                    $ssl->once('end', function () use ($credentials, $clientFlags, $plugin, &$deferred, &$message) {
495
                        $this->connectionState = static::CONNECTION_SSL_STARTUP;
496
                        
497
                        $this->enableTLS()->then(function () use ($credentials, $clientFlags, $plugin, &$deferred, &$message) {
498
                            $this->createHandshakeResponse($message, $credentials, $clientFlags, $plugin, $deferred);
499
                        }, function (\Throwable $error) use (&$deferred) {
500
                            $deferred->reject($$error);
501
                            $this->connection->close();
502
                        });
503
                    });
504
                    
505
                    return $this->parser->invokeCommand($ssl);
506
                } else {
507
                    $ipCheck = (\filter_var($remote, \FILTER_VALIDATE_IP) === false ||
508
                        \filter_var($remote, \FILTER_VALIDATE_IP, \FILTER_FLAG_NO_PRIV_RANGE) === false);
509
                    
510
                    if(!$ignored && $ipCheck && $this->options['tls.force']) {
511
                        $deferred->reject((new \Plasma\Exception('TLS is not supported by the server')));
512
                        $this->connection->close();
513
                        return;
514
                    }
515
                }
516
                
517
                $this->createHandshakeResponse($message, $credentials, $clientFlags, $plugin, $deferred);
518
            }
519 1
        };
520
        
521 1
        $this->parser->on('message', $listener);
522
        
523
        $this->parser->on('message', function (\Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
524
            if($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage) {
525
                $this->connectionState = static::CONNECTION_OK;
526
            }
527
            
528
            $this->emit('eventRelay', array('message', $message));
529 1
        });
530 1
    }
531
    
532
    /**
533
     * Enables TLS on the connection.
534
     * @return \React\Promise\PromiseInterface
535
     */
536
    protected function enableTLS(): \React\Promise\PromiseInterface {
537
        // Set required SSL/TLS context options
538
        foreach($this->options['tls.context'] as $name => $value) {
539
            \stream_context_set_option($this->connection->stream, 'ssl', $name, $value);
540
        }
541
        
542
        return $this->encryption->enable($this->connection)->otherwise(function (\Throwable $error) {
0 ignored issues
show
Bug introduced by
The method otherwise() does not exist on React\Promise\PromiseInterface. It seems like you code against a sub-type of said class. However, the method does not exist in React\Promise\SimpleFulfilledTestPromise or React\Promise\SimpleRejectedTestPromise or React\Promise\CancellablePromiseInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

542
        return $this->encryption->enable($this->connection)->/** @scrutinizer ignore-call */ otherwise(function (\Throwable $error) {
Loading history...
543
            $this->connection->close();
544
            throw new \RuntimeException('Connection failed during TLS handshake: '.$error->getMessage(), $error->getCode());
545
        });
546
    }
547
    
548
    /**
549
     * Sends the auth command.
550
     * @param \Plasma\Drivers\MySQL\Messages\HandshakeMessage  $message
551
     * @param array                                            $credentials
552
     * @param int                                              $clientFlags
553
     * @param string|null                                      $plugin
554
     * @param \React\Promise\Deferred                          $deferred
555
     * @return void
556
     */
557
    protected function createHandshakeResponse(
558
        \Plasma\Drivers\MySQL\Messages\HandshakeMessage $message, array $credentials, int $clientFlags, ?string $plugin, \React\Promise\Deferred $deferred
559
    ) {
560
        \extract($credentials);
561
        
562
        $auth = new \Plasma\Drivers\MySQL\Commands\HandshakeResponseCommand($this->parser, $message, $clientFlags, $plugin, $user, $password, $db);
563
        
564
        $auth->once('end', function () use (&$deferred) {
565
            $deferred->resolve();
566
        });
567
        
568
        $auth->once('error', function (\Throwable $error) use (&$deferred) {
569
            $deferred->reject($error);
570
            $this->connection->close();
571
        });
572
        
573
        if($plugin) {
574
            $listener = function (\Plasma\Drivers\MySQL\Messages\MessageInterface $message) use ($password, &$deferred, &$listener) {
575
                /** @var \Plasma\Drivers\MySQL\AuthPlugins\AuthPluginInterface|null  $plugin */
576
                static $plugin;
577
                
578
                if($message instanceof \Plasma\Drivers\MySQL\Messages\AuthSwitchRequestMessage) {
579
                    $name = $message->authPluginName;
580
                    
581
                    if($name !== null) {
582
                        foreach($plugins as $key => $plug) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $plugins does not exist. Did you maybe mean $plugin?
Loading history...
583
                            if($key === $name) {
584
                                $plugin = new $plug($this->parser, $message);
585
                                
586
                                $command = new \Plasma\Drivers\MySQL\Commands\AuthSwitchResponseCommand($message, $plugin, $password);
587
                                return $this->parser->invokeCommand($command);
588
                            }
589
                        }
590
                    }
591
                    
592
                    $deferred->reject((new \Plasma\Exception('Requested authentication method '.($name ? '"'.$name.'" ' : '').'is not supported')));
593
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\AuthMoreDataMessage) {
594
                    if($plugin === null) {
595
                        $deferred->reject((new \Plasma\Exception('No auth plugin is in use, but we received auth more data packet')));
596
                        return $this->connection->close();
597
                    }
598
                    
599
                    try {
600
                        $command = $plugin->receiveMoreData($message);
601
                        return $this->parser->invokeCommand($command);
602
                    } catch (\Plasma\Exception $e) {
603
                        $deferred->reject($e);
604
                        $this->connection->close();
605
                    }
606
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage) {
607
                    $this->parser->removeListener('message', $listener);
608
                }
609
            };
610
            
611
            $this->parser->on('message', $listener);
612
        }
613
        
614
        $this->parser->invokeCommand($auth);
615
        $this->connectionState = static::CONNECTION_AWAITING_RESPONSE;
616
    }
617
    
618
    /**
619
     * Validates the given options.
620
     * @param array  $options
621
     * @return void
622
     * @throws \InvalidArgumentException
623
     */
624 15
    protected function validateOptions(array $options) {
625 15
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
626 15
            'connector' => 'class:\React\Socket\ConnectorInterface=object',
627
            'tls.context' => 'array',
628
            'tls.force' => 'boolean'
629
        ));
630
        
631 15
        $validator->throw(\InvalidArgumentException::class);
632 15
    }
633
}
634