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 ( 8aa335...7f592f )
by Charlotte
02:04
created

Client::prepare()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Plasma Core component
4
 * Copyright 2018 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
        'maxConnections' => 5,
33
        'connect.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
     *     'maxConnections' => int, (the maximum amount of connections to open, defaults to 5)
58
     *     'connect.lazy' => bool, (whether the 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 50
    function __construct(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()) {
68 50
        $this->validateOptions($options);
69
        
70 50
        $this->factory = $factory;
71 50
        $this->uri = $uri;
72 50
        $this->options = \array_merge($this->options, $options);
73
        
74 50
        $this->connections = new \CharlotteDunois\Collect\Set();
75 50
        $this->busyConnections = new \CharlotteDunois\Collect\Set();
76
        
77 50
        if(!$this->options['connect.lazy']) {
78 25
            $connection = $this->createNewConnection();
79 25
            if($connection->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_OK) {
80
                $this->busyConnections->add($connection);
81
            }
82
        }
83 50
    }
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 50
    static function create(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()): \Plasma\ClientInterface {
95 50
        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 16
    function checkinConnection(\Plasma\DriverInterface $driver): void {
112 16
        if($driver->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_UNUSABLE && !$this->goingAway) {
113 8
            $this->connections->add($driver);
114 8
            $this->busyConnections->delete($driver);
115
        }
116 16
    }
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
     * @return string
213
     * @throws \LogicException    Thrown if the driver does not support quoting.
214
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
215
     */
216 2
    function quote(string $str): string {
217 2
        if($this->goingAway) {
218 1
            throw new \Plasma\Exception('Client is closing all connections');
219
        }
220
        
221 1
        $connection = $this->getOptimalConnection();
222 1
        $quoted = $connection->quote($str);
223
        
224 1
        $this->checkinConnection($connection);
225 1
        return $quoted;
226
    }
227
    
228
    /**
229
     * Closes all connections gracefully after processing all outstanding requests.
230
     * @return \React\Promise\PromiseInterface
231
     */
232 3
    function close(): \React\Promise\PromiseInterface {
233 3
        if($this->goingAway) {
234 1
            return $this->goingAway;
235
        }
236
        
237 2
        $deferred = new \React\Promise\Deferred();
238 2
        $this->goingAway = $deferred->promise();
239
        
240 2
        $closes = array();
241
        
242
        /** @var \Plasma\DriverInterface  $conn */
243 2
        foreach($this->connections as $conn) {
244 1
            $closes[] = $conn->close();
245 1
            $this->connections->delete($conn);
246
        }
247
        
248
        /** @var \Plasma\DriverInterface  $conn */
249 2
        foreach($this->busyConnections as $conn) {
250 1
            $closes[] = $conn->close();
251 1
            $this->busyConnections->delete($conn);
252
        }
253
        
254 2
        \React\Promise\all($closes)->then(array($deferred, 'resolve'), array($deferred, 'reject'));
255 2
        return $this->goingAway;
256
    }
257
    
258
    /**
259
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all oustanding requests.
260
     * @return void
261
     */
262 9
    function quit(): void {
263 9
        if($this->goingAway) {
264 1
            return;
265
        }
266
        
267 8
        $this->goingAway = \React\Promise\resolve();
268
        
269
        /** @var \Plasma\DriverInterface  $conn */
270 8
        foreach($this->connections as $conn) {
271 7
            $conn->quit();
272 7
            $this->connections->delete($conn);
273
        }
274
        
275
        /** @var \Plasma\DriverInterface  $conn */
276 8
        foreach($this->busyConnections as $conn) {
277 1
            $conn->quit();
278 1
            $this->busyConnections->delete($conn);
279
        }
280 8
    }
281
    
282
    /**
283
     * Runs the given command.
284
     * @param \Plasma\CommandInterface  $command
285
     * @return mixed  Return depends on command and driver.
286
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
287
     */
288 3
    function runCommand(\Plasma\CommandInterface $command) {
289 3
        if($this->goingAway) {
290 1
            throw new \Plasma\Exception('Client is closing all connections');
291
        }
292
        
293 2
        $connection = $this->getOptimalConnection();
294 2
        return $connection->runCommand($this, $command);
295
    }
296
    
297
    /**
298
     * Get the optimal connection.
299
     * @return \Plasma\DriverInterface
300
     */
301 13
    protected function getOptimalConnection(): \Plasma\DriverInterface {
302 13
        if(\count($this->connections) === 0) {
303 1
            return $this->createNewConnection();
304
        }
305
        
306
        /** @var \Plasma\DriverInterface  $connection */
307 12
        $this->connections->rewind();
308 12
        $connection = $this->connections->current();
309
        
310 12
        $backlog = $connection->getBacklogLength();
311 12
        $state = $connection->getBusyState();
312
        
313
        /** @var \Plasma\DriverInterface  $conn */
314 12
        foreach($this->connections as $conn) {
315 12
            $cbacklog = $conn->getBacklogLength();
316 12
            $cstate = $conn->getBusyState();
317
            
318 12
            if($cbacklog === 0 && $conn->getConnectionState() === \Plasma\DriverInterface::CONNECTION_OK && $cstate == \Plasma\DriverInterface::STATE_IDLE) {
319 12
                $this->connections->delete($conn);
320 12
                $this->busyConnections->add($connection);
321
                
322 12
                return $conn;
323
            }
324
            
325
            if($backlog > $cbacklog || $state > $cstate) {
326
                $connection = $conn;
327
                $backlog = $cbacklog;
328
                $state = $cstate;
329
            }
330
        }
331
        
332
        if($this->getConnectionCount() < $this->options['maxConnections']) {
333
            return $this->createNewConnection();
334
        }
335
        
336
        $this->connections->delete($connection);
337
        $this->busyConnections->add($connection);
338
        
339
        return $connection;
340
    }
341
    
342
    /**
343
     * Create a new connection.
344
     * @return \Plasma\DriverInterface
345
     */
346 26
    protected function createNewConnection(): \Plasma\DriverInterface {
347 26
        $connection = $this->factory->createDriver();
348
        
349
        // We relay a driver's specific events forward, e.g. PostgreSQL notifications
350
        $connection->on('eventRelay', function (string $eventName, ...$args) use (&$connection) {
351 1
            $args[] = $connection;
352 1
            $this->emit($eventName, $args);
353 26
        });
354
        
355
        $connection->on('close', function () use (&$connection) {
356 2
            $this->connections->delete($connection);
357 2
            $this->busyConnections->delete($connection);
358
            
359 2
            $this->emit('close', array($connection));
360 26
        });
361
        
362
        $connection->on('error', function (\Throwable $error) use (&$connection) {
363 1
            $this->emit('error', array($error, $connection));
364 26
        });
365
        
366
        $connection->connect($this->uri)->then(function () use (&$connection) {
367 26
            $this->connections->add($connection);
368 26
            $this->busyConnections->delete($connection);
369
        }, function (\Throwable $error) use (&$connection) {
370
            $this->connections->delete($connection);
371
            $this->busyConnections->delete($connection);
372
            
373
            $this->emit('error', array($error, $connection));
374 26
        });
375
        
376 26
        return $connection;
377
    }
378
    
379
    /**
380
     * Validates the given options.
381
     * @param array  $options
382
     * @return void
383
     * @throws \InvalidArgumentException
384
     */
385 50
    protected function validateOptions(array $options) {
386 50
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
387 50
            'maxConnections' => 'integer|min:1',
388
            'connect.lazy' => 'boolean'
389
        ));
390
        
391 50
        $validator->throw(\InvalidArgumentException::class);
392 50
    }
393
}
394