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 ( a78989...3b208e )
by Charlotte
05:28
created

Driver::runCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 13
ccs 4
cts 8
cp 0.5
crap 1.125
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
        $uri = 'mysql://'.ltrim($uri, 'mysql://');
130
        
131 11
        $parts = \parse_url($uri);
132 11
        if(!isset($parts['host'])) {
133
            return \React\Promise\reject((new \InvalidArgumentException('Invalid connect uri given')));
134
        }
135
        
136 11
        $host = $parts['host'].':'.($parts['port'] ?? 3306);
137 11
        $this->connectionState = static::CONNECTION_STARTED;
138
        
139
        return $this->connector->connect($host)->then(function (\React\Socket\ConnectionInterface $connection) use ($parts) {
140
            // See description of property encryption
141 4
            if(!($connection instanceof \React\Socket\Connection)) {
142
                throw new \LogicException('Custom connection class is NOT supported yet (encryption limitation)');
143
            }
144
            
145 4
            $this->busy = static::STATE_BUSY;
146 4
            $this->connectionState = static::CONNECTION_MADE;
147 4
            $this->connection = $connection;
148
            
149
            $this->connection->on('error', function (\Throwable $error) {
150
                $this->emit('error', array($error));
151 4
            });
152
            
153
            $this->connection->on('close', function () {
154 4
                $this->connection = null;
155 4
                $this->connectionState = static::CONNECTION_UNUSABLE;
156
                
157 4
                $this->emit('close');
158 4
            });
159
            
160 4
            $this->parser = new \Plasma\Drivers\MySQL\ProtocolParser($this, $this->connection);
161
            
162 4
            $user = ($parts['user'] ?? 'root');
163 4
            $password = ($parts['password'] ?? '');
164 4
            $db = (!empty($parts['path']) ? \ltrim($parts['path'], '/') : '');
165
            
166 4
            $credentials = \compact('user', 'password', 'db');
167 4
            $deferred = new \React\Promise\Deferred();
168
            
169 4
            $this->startHandshake($db, $credentials, $deferred);
170 4
            return $deferred->promise();
171 11
        });
172
    }
173
    
174
    /**
175
     * Pauses the underlying stream I/O consumption.
176
     * If consumption is already paused, this will do nothing.
177
     * @return bool  Whether the operation was successful.
178
     */
179
    function pauseStreamConsumption(): bool {
180
        $this->connection->pause();
181
        return true;
182
    }
183
    
184
    /**
185
     * Resumes the underlying stream I/O consumption.
186
     * If consumption is not paused, this will do nothing.
187
     * @return bool  Whether the operation was successful.
188
     */
189
    function resumeStreamConsumption(): bool {
190
        $this->connection->resume();
191
        return true;
192
    }
193
    
194
    /**
195
     * Closes all connections gracefully after processing all outstanding requests.
196
     * @return \React\Promise\PromiseInterface
197
     */
198
    function close(): \React\Promise\PromiseInterface {
199
        if($this->goingAway) {
200
            return $this->goingAway->promise();
201
        }
202
        
203
        $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...
204
        
205
        if(\count($this->queue) === 0) {
206
            $this->goingAway->resolve();
207
        }
208
        
209
        return $this->goingAway->promise()->then(function () {
210
            $deferred = new \React\Promise\Deferred();
211
            
212
            $quit = new \Plasma\Drivers\MySQL\Commands\QuitCommand();
213
            
214
            $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...
215
                $deferred->resolve();
216
            });
217
            
218
            $quit->once('end', function () {
219
                $this->connection->close();
220
            });
221
            
222
            $this->parser->invokeCommand($quit);
223
            
224
            return $deferred->promise();
225
        });
226
    }
227
    
228
    /**
229
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all outstanding requests.
230
     * @return void
231
     */
232
    function quit(): void {
233
        /** @var \Plasma\Drivers\MySQL\Commands\CommandInterface  $command */
234
        while($command = \array_shift($this->queue)) {
235
            $command->emit('error', array((new \Plasma\Exception('Connection is going away'))));
236
        }
237
        
238
        $quit = new \Plasma\Drivers\MySQL\Commands\QuitCommand();
239
        $this->parser->invokeCommand($quit);
240
        
241
        $this->connection->close();
242
    }
243
    
244
    /**
245
     * Whether this driver is currently in a transaction.
246
     * @return bool
247
     */
248
    function isInTransaction(): bool {
249
        return $this->transaction;
250
    }
251
    
252
    /**
253
     * Executes a plain query. Resolves with a `QueryResultInterface` instance.
254
     * When the command is done, the driver must check itself back into the client.
255
     * @param \Plasma\ClientInterface  $client
256
     * @param string                   $query
257
     * @return \React\Promise\PromiseInterface
258
     * @throws \Plasma\Exception
259
     * @see \Plasma\QueryResultInterface
260
     */
261
    function query(\Plasma\ClientInterface $client, string $query): \React\Promise\PromiseInterface {
262
        $command = new \Plasma\Drivers\MySQL\Commands\QueryCommand($this, $query);
263
        $this->executeCommand($command);
264
        
265
        $command->once('end', function () use (&$client) {
266
            $client->checkinConnection($this);
267
        });
268
        
269
        return $command->getPromise();
270
    }
271
    
272
    /**
273
     * Prepares a query. Resolves with a `StatementInterface` instance.
274
     * When the command is done, the driver must check itself back into the client.
275
     * @param \Plasma\ClientInterface  $client
276
     * @param string                   $query
277
     * @return \React\Promise\PromiseInterface
278
     * @throws \Plasma\Exception
279
     * @see \Plasma\StatementInterface
280
     */
281
    function prepare(\Plasma\ClientInterface $client, string $query): \React\Promise\PromiseInterface {
282
        $command = new \Plasma\Drivers\MySQL\Commands\PrepareCommand($client, $this, $query);
283
        $this->executeCommand($command);
284
        
285
        return $command->getPromise();
286
    }
287
    
288
    /**
289
     * Prepares and executes a query. Resolves with a `QueryResultInterface` instance.
290
     * This is equivalent to prepare -> execute -> close.
291
     * If you need to execute a query multiple times, prepare the query manually for performance reasons.
292
     * @param \Plasma\ClientInterface  $client
293
     * @param string                   $query
294
     * @param array                    $params
295
     * @return \React\Promise\PromiseInterface
296
     * @throws \Plasma\Exception
297
     * @see \Plasma\StatementInterface
298
     */
299
    function execute(\Plasma\ClientInterface $client, string $query, array $params = array()): \React\Promise\PromiseInterface {
300
        return $this->prepare($client, $query)->then(function (\Plasma\StatementInterface $statement) use ($params) {
301
            return $statement->execute($params)->then(function (\Plasma\QueryResultInterface $result) use (&$statement) {
302
                return $statement->close()->then(function () use ($result) {
303
                    return $result;
304
                });
305
            }, function (\Throwable $error) use (&$statement) {
306
                return $statement->close()->then(function () use ($error) {
307
                    throw $error;
308
                });
309
            });
310
        })->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

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

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

365
        return $this->query(/** @scrutinizer ignore-type */ $query)->then(function () {
Loading history...
366
            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

366
            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

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

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