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.
Completed
Push — master ( b2e0bf...70b9c4 )
by Charlotte
03:30 queued 01:40
created

Client::close()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 5
nop 0
dl 0
loc 24
ccs 14
cts 14
cp 1
crap 4
rs 9.8333
c 0
b 0
f 0
1
<?php
2
/**
3
 * Plasma Core component
4
 * Copyright 2018-2019 PlasmaPHP, All Rights Reserved
5
 *
6
 * Website: https://github.com/PlasmaPHP
7
 * License: https://github.com/PlasmaPHP/core/blob/master/LICENSE
8
*/
9
10
namespace Plasma;
11
12
/**
13
 * The plasma client, responsible for pooling and connections.
14
 */
15
class Client implements ClientInterface {
16
    use \Evenement\EventEmitterTrait;
17
    
18
    /**
19
     * @var \Plasma\DriverFactoryInterface
20
     */
21
    protected $factory;
22
    
23
    /**
24
     * @var string
25
     */
26
    protected $uri;
27
    
28
    /**
29
     * @var array
30
     */
31
    protected $options = array(
32
        'connections.max' => 5,
33
        'connections.lazy' => false
34
    );
35
    
36
    /**
37
     * @var \React\Promise\PromiseInterface
38
     */
39
    protected $goingAway;
40
    
41
    /**
42
     * @var \CharlotteDunois\Collect\Set
43
     */
44
    protected $connections;
45
    
46
    /**
47
     * @var \CharlotteDunois\Collect\Set
48
     */
49
    protected $busyConnections;
50
    
51
    /**
52
     * Creates a client with the specified factory and options.
53
     *
54
     * Available options:
55
     * ```
56
     * array(
57
     *     'connections.max' => int, (the maximum amount of connections to open, defaults to 5)
58
     *     'connections.lazy' => bool, (whether the first connection should be established lazily (on first request), defaults to false)
59
     * )
60
     * ```
61
     *
62
     * @param \Plasma\DriverFactoryInterface  $factory
63
     * @param string                          $uri
64
     * @param array                           $options
65
     * @throws \InvalidArgumentException
66
     */
67 52
    function __construct(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()) {
68 52
        $this->validateOptions($options);
69
        
70 52
        $this->factory = $factory;
71 52
        $this->uri = $uri;
72 52
        $this->options = \array_merge($this->options, $options);
73
        
74 52
        $this->connections = new \CharlotteDunois\Collect\Set();
75 52
        $this->busyConnections = new \CharlotteDunois\Collect\Set();
76
        
77 52
        if(!$this->options['connections.lazy']) {
78 27
            $connection = $this->createNewConnection();
79 27
            if($connection->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_OK) {
80
                $this->busyConnections->add($connection);
81
            }
82
        }
83 52
    }
84
    
85
    /**
86
     * Creates a client with the specified factory and options.
87
     * @param \Plasma\DriverFactoryInterface  $factory
88
     * @param string                          $uri
89
     * @param array                           $options
90
     * @return \Plasma\ClientInterface
91
     * @throws \Throwable  The client implementation may throw any exception during this operation.
92
     * @see Client::__construct()
93
     */
94 52
    static function create(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()): \Plasma\ClientInterface {
95 52
        return (new static($factory, $uri, $options));
96
    }
97
    
98
    /**
99
     * Get the amount of connections.
100
     * @return int
101
     */
102 4
    function getConnectionCount(): int {
103 4
        return ($this->connections->count() + $this->busyConnections->count());
104
    }
105
    
106
    /**
107
     * Checks a connection back in, if usable and not closing.
108
     * @param \Plasma\DriverInterface  $driver
109
     * @return void
110
     */
111 19
    function checkinConnection(\Plasma\DriverInterface $driver): void {
112 19
        if($driver->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_UNUSABLE && !$this->goingAway) {
113 11
            $this->connections->add($driver);
114 11
            $this->busyConnections->delete($driver);
115
        }
116 19
    }
117
    
118
    /**
119
     * Begins a transaction. Resolves with a `Transaction` instance.
120
     *
121
     * Checks out a connection until the transaction gets committed or rolled back. If the transaction goes out of scope
122
     * and thus deallocated, the `Transaction` must check the connection back into the client.
123
     *
124
     * Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL)
125
     * statement such as DROP TABLE or CREATE TABLE is issued within a transaction.
126
     * The implicit COMMIT will prevent you from rolling back any other changes within the transaction boundary.
127
     * @param int  $isolation  See the `TransactionInterface` constants.
128
     * @return \React\Promise\PromiseInterface
129
     * @throws \Plasma\Exception
130
     * @see \Plasma\Transaction
131
     */
132 3
    function beginTransaction(int $isolation = \Plasma\TransactionInterface::ISOLATION_COMMITTED): \React\Promise\PromiseInterface {
133 3
        if($this->goingAway) {
134 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
135
        }
136
        
137 2
        $connection = $this->getOptimalConnection();
138
        
139
        return $connection->beginTransaction($this, $isolation)->then(null, function (\Throwable $error) use (&$connection) {
140 1
            $this->checkinConnection($connection);
141 1
            throw $error;
142 2
        });
143
    }
144
    
145
    /**
146
     * Executes a plain query. Resolves with a `QueryResult` instance.
147
     * @param string  $query
148
     * @return \React\Promise\PromiseInterface
149
     * @see \Plasma\QueryResultInterface
150
     */
151 5
    function query(string $query): \React\Promise\PromiseInterface {
152 5
        if($this->goingAway) {
153 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
154
        }
155
        
156 4
        $connection = $this->getOptimalConnection();
157
        
158
        return $connection->query($this, $query)->then(null, function (\Throwable $error) use (&$connection) {
159 1
            $this->checkinConnection($connection);
160 1
            throw $error;
161 4
        });
162
    }
163
    
164
    /**
165
     * Prepares a query. Resolves with a `StatementInterface` instance.
166
     * @param string  $query
167
     * @return \React\Promise\PromiseInterface
168
     * @see \Plasma\StatementInterface
169
     */
170 3
    function prepare(string $query): \React\Promise\PromiseInterface {
171 3
        if($this->goingAway) {
172 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
173
        }
174
        
175 2
        $connection = $this->getOptimalConnection();
176
        
177
        return $connection->prepare($this, $query)->then(null, function (\Throwable $error) use (&$connection) {
178 1
            $this->checkinConnection($connection);
179 1
            throw $error;
180 2
        });
181
    }
182
    
183
    /**
184
     * Prepares and executes a query. Resolves with a `QueryResultInterface` instance.
185
     * This is equivalent to prepare -> execute -> close.
186
     * If you need to execute a query multiple times, prepare the query manually for performance reasons.
187
     * @param string  $query
188
     * @param array   $params
189
     * @return \React\Promise\PromiseInterface
190
     * @throws \Plasma\Exception
191
     * @see \Plasma\StatementInterface
192
     */
193 3
    function execute(string $query, array $params = array()): \React\Promise\PromiseInterface {
194 3
        if($this->goingAway) {
195 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
196
        }
197
        
198 2
        $connection = $this->getOptimalConnection();
199
        
200
        return $connection->execute($this, $query, $params)->then(function ($value) use (&$connection) {
201 1
            $this->checkinConnection($connection);
202 1
            return $value;
203
        }, function (\Throwable $error) use (&$connection) {
204 1
            $this->checkinConnection($connection);
205 1
            throw $error;
206 2
        });
207
    }
208
    
209
    /**
210
     * Quotes the string for use in the query.
211
     * @param string  $str
212
     * @param int     $type  For types, see the driver interface constants.
213
     * @return string
214
     * @throws \LogicException    Thrown if the driver does not support quoting.
215
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
216
     */
217 2
    function quote(string $str, int $type = \Plasma\DriverInterface::QUOTE_TYPE_VALUE): string {
218 2
        if($this->goingAway) {
219 1
            throw new \Plasma\Exception('Client is closing all connections');
220
        }
221
        
222 1
        $connection = $this->getOptimalConnection();
223
        
224
        try {
225 1
            $quoted = $connection->quote($str, $type);
226 1
        } finally {
227 1
            $this->checkinConnection($connection);
228
        }
229
        
230 1
        return $quoted;
231
    }
232
    
233
    /**
234
     * Closes all connections gracefully after processing all outstanding requests.
235
     * @return \React\Promise\PromiseInterface
236
     */
237 3
    function close(): \React\Promise\PromiseInterface {
238 3
        if($this->goingAway) {
239 1
            return $this->goingAway;
240
        }
241
        
242 2
        $deferred = new \React\Promise\Deferred();
243 2
        $this->goingAway = $deferred->promise();
244
        
245 2
        $closes = array();
246
        
247
        /** @var \Plasma\DriverInterface  $conn */
248 2
        foreach($this->connections->all() as $conn) {
249 1
            $closes[] = $conn->close();
250 1
            $this->connections->delete($conn);
251
        }
252
        
253
        /** @var \Plasma\DriverInterface  $conn */
254 2
        foreach($this->busyConnections->all() as $conn) {
255 1
            $closes[] = $conn->close();
256 1
            $this->busyConnections->delete($conn);
257
        }
258
        
259 2
        \React\Promise\all($closes)->then(array($deferred, 'resolve'), array($deferred, 'reject'));
260 2
        return $this->goingAway;
261
    }
262
    
263
    /**
264
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all oustanding requests.
265
     * @return void
266
     */
267 10
    function quit(): void {
268 10
        if($this->goingAway) {
269 1
            return;
270
        }
271
        
272 9
        $this->goingAway = \React\Promise\resolve();
273
        
274
        /** @var \Plasma\DriverInterface  $conn */
275 9
        foreach($this->connections->all() as $conn) {
276 8
            $conn->quit();
277 8
            $this->connections->delete($conn);
278
        }
279
        
280
        /** @var \Plasma\DriverInterface  $conn */
281 9
        foreach($this->busyConnections->all() as $conn) {
282 1
            $conn->quit();
283 1
            $this->busyConnections->delete($conn);
284
        }
285 9
    }
286
    
287
    /**
288
     * Runs the given command.
289
     * @param \Plasma\CommandInterface  $command
290
     * @return mixed  Return depends on command and driver.
291
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
292
     */
293 3
    function runCommand(\Plasma\CommandInterface $command) {
294 3
        if($this->goingAway) {
295 1
            throw new \Plasma\Exception('Client is closing all connections');
296
        }
297
        
298 2
        $connection = $this->getOptimalConnection();
299
        
300
        try {
301 2
            return $connection->runCommand($this, $command);
302
        } finally {
303 2
            $this->checkinConnection($connection);
304
        }
305
    }
306
    
307
    /**
308
     * Runs the given querybuilder on an underlying driver instance.
309
     * The driver CAN throw an exception if the given querybuilder is not supported.
310
     * An example would be a SQL querybuilder and a Cassandra driver.
311
     * @param \Plasma\QuerybuilderInterface  $query
312
     * @return \React\Promise\PromiseInterface
313
     * @throws \Plasma\Exception
314
     */
315 2
    function runQuery(\Plasma\QuerybuilderInterface $query): \React\Promise\PromiseInterface {
316 2
        if($this->goingAway) {
317 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
318
        }
319
        
320 1
        $connection = $this->getOptimalConnection();
321
        
322
        try {
323 1
            return $connection->runQuery($this, $query);
324
        } finally {
325 1
            $this->checkinConnection($connection);
326
        }
327
    }
328
    
329
    /**
330
     * Get the optimal connection.
331
     * @return \Plasma\DriverInterface
332
     */
333 14
    protected function getOptimalConnection(): \Plasma\DriverInterface {
334 14
        if(\count($this->connections) === 0) {
335 1
            $connection = $this->createNewConnection();
336 1
            $this->busyConnections->add($connection);
337
            
338 1
            return $connection;
339
        }
340
        
341
        /** @var \Plasma\DriverInterface  $connection */
342 13
        $this->connections->rewind();
343 13
        $connection = $this->connections->current();
344
        
345 13
        $backlog = $connection->getBacklogLength();
346 13
        $state = $connection->getBusyState();
347
        
348
        /** @var \Plasma\DriverInterface  $conn */
349 13
        foreach($this->connections as $conn) {
350 13
            $cbacklog = $conn->getBacklogLength();
351 13
            $cstate = $conn->getBusyState();
352
            
353 13
            if($cbacklog === 0 && $conn->getConnectionState() === \Plasma\DriverInterface::CONNECTION_OK && $cstate == \Plasma\DriverInterface::STATE_IDLE) {
354 13
                $this->connections->delete($conn);
355 13
                $this->busyConnections->add($conn);
356
                
357 13
                return $conn;
358
            }
359
            
360
            if($backlog > $cbacklog || $state > $cstate) {
361
                $connection = $conn;
362
                $backlog = $cbacklog;
363
                $state = $cstate;
364
            }
365
        }
366
        
367
        if($this->getConnectionCount() < $this->options['connections.max']) {
368
            $connection = $this->createNewConnection();
369
        }
370
        
371
        $this->connections->delete($connection);
372
        $this->busyConnections->add($connection);
373
        
374
        return $connection;
375
    }
376
    
377
    /**
378
     * Create a new connection.
379
     * @return \Plasma\DriverInterface
380
     */
381 28
    protected function createNewConnection(): \Plasma\DriverInterface {
382 28
        $connection = $this->factory->createDriver();
383
        
384
        // We relay a driver's specific events forward, e.g. PostgreSQL notifications
385
        $connection->on('eventRelay', function (string $eventName, ...$args) use (&$connection) {
386 1
            $args[] = $connection;
387 1
            $this->emit($eventName, $args);
388 28
        });
389
        
390
        $connection->on('close', function () use (&$connection) {
391 2
            $this->connections->delete($connection);
392 2
            $this->busyConnections->delete($connection);
393
            
394 2
            $this->emit('close', array($connection));
395 28
        });
396
        
397
        $connection->on('error', function (\Throwable $error) use (&$connection) {
398 1
            $this->emit('error', array($error, $connection));
399 28
        });
400
        
401
        $connection->connect($this->uri)->then(function () use (&$connection) {
402 28
            $this->connections->add($connection);
403 28
            $this->busyConnections->delete($connection);
404
        }, function (\Throwable $error) use (&$connection) {
405
            $this->connections->delete($connection);
406
            $this->busyConnections->delete($connection);
407
            
408
            $this->emit('error', array($error, $connection));
409 28
        });
410
        
411 28
        return $connection;
412
    }
413
    
414
    /**
415
     * Validates the given options.
416
     * @param array  $options
417
     * @return void
418
     * @throws \InvalidArgumentException
419
     */
420 52
    protected function validateOptions(array $options) {
421 52
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
422 52
            'connections.max' => 'integer|min:1',
423
            'connections.lazy' => 'boolean'
424
        ));
425
        
426 52
        $validator->throw(\InvalidArgumentException::class);
427 52
    }
428
}
429