Completed
Push — master ( e70d22...5bd967 )
by Kamil
12s
created

Redis::isBusy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 329.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace Dazzle\Redis;
4
5
use Clue\Redis\Protocol\Model\ErrorReply;
6
use Clue\Redis\Protocol\Model\ModelInterface;
7
use Clue\Redis\Protocol\Parser\ParserException;
8
use Dazzle\Event\BaseEventEmitter;
9
use Dazzle\Loop\LoopAwareTrait;
10
use Dazzle\Loop\LoopInterface;
11
use Dazzle\Promise\Deferred;
12
use Dazzle\Promise\PromiseInterface;
13
use Dazzle\Redis\Driver\Request;
14
use Dazzle\Redis\Driver\Driver;
15
use Dazzle\Redis\Driver\DriverInterface;
16
use Dazzle\Socket\Socket;
17
use Dazzle\Socket\SocketInterface;
18
use Dazzle\Throwable\Exception\Runtime\ExecutionException;
19
use Dazzle\Throwable\Exception\Runtime\UnderflowException;
20
use Error;
21
use Exception;
22
23
class Redis extends BaseEventEmitter implements RedisInterface
24
{
25
    use LoopAwareTrait;
26
    use Command\Compose\ApiChannelTrait;
27
    use Command\Compose\ApiClusterTrait;
28
    use Command\Compose\ApiConnTrait;
29
    use Command\Compose\ApiCoreTrait;
30
    use Command\Compose\ApiGeospatialTrait;
31
    use Command\Compose\ApiHyperLogTrait;
32
    use Command\Compose\ApiKeyValTrait;
33
    use Command\Compose\ApiListTrait;
34
    use Command\Compose\ApiSetTrait;
35
    use Command\Compose\ApiSetHashTrait;
36
    use Command\Compose\ApiSetSortedTrait;
37
    use Command\Compose\ApiTransactionTrait;
38
39
    /**
40
     * @var string
41
     */
42
    protected $endpoint;
43
44
    /**
45
     * @var SocketInterface
46
     */
47
    protected $stream;
48
49
    /**
50
     * @var DriverInterface
51
     */
52
    protected $driver;
53
54
    /**
55
     * @var bool
56
     */
57
    protected $isConnected;
58
59
    /**
60
     * @var bool
61
     */
62
    protected $isBeingDisconnected;
63
64
    /**
65
     * @var array
66
     */
67
    private $reqs;
68
69
    /**
70
     * @param string $endpoint
71
     * @param LoopInterface $loop
72
     */
73
    public function __construct($endpoint, LoopInterface $loop)
74
    {
75
        $this->endpoint = $endpoint;
76
        $this->loop = $loop;
77
        $this->stream = null;
78
        $this->driver = new Driver();
79
80
        $this->isConnected = false;
81
        $this->isBeingDisconnected = false;
82
83
        $this->reqs = [];
84
    }
85
86
    /**
87
     *
88
     */
89
    public function __destruct()
90
    {
91
        $this->stop();
92
    }
93
94
    /**
95
     * @override
96
     * @inheritDoc
97
     */
98 116
    public function isStarted()
99
    {
100 116
        return $this->isConnected;
101
    }
102
103
    /**
104
     * @override
105
     * @inheritDoc
106
     */
107 116
    public function isBusy()
108
    {
109 116
        return !empty($this->reqs);
110
    }
111
112
    /**
113
     * @override
114
     * @inheritDoc
115
     */
116 116
    public function start()
117
    {
118 116
        if ($this->isStarted())
119
        {
120
            return false;
121
        }
122
123 116
        $ex = null;
124 116
        $stream = null;
125
126
        try
127
        {
128 116
            $stream = $this->createClient($this->endpoint);
129
        }
130
        catch (Error $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
131
        {}
132
        catch (Exception $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
133
        {}
134
135 116
        if ($ex !== null)
136
        {
137
            return false;
138
        }
139
140 116
        $this->isConnected = true;
141 116
        $this->isBeingDisconnected = false;
142 116
        $this->stream = $stream;
143
144
        // TODO patch missing pub/sub, pipeline, auth
145 116
        $this->handleStart();
146 116
        $this->emit('start', [ $this ]);
147
148 116
        return true;
149
    }
150
151
    /**
152
     * @override
153
     * @inheritDoc
154
     */
155 116
    public function stop()
156
    {
157 116
        if (!$this->isStarted())
158
        {
159 116
            return false;
160
        }
161
162 116
        $this->isBeingDisconnected = true;
163 116
        $this->isConnected = false;
164
165 116
        $this->stream->close();
166 116
        $this->stream = null;
167
168 116
        foreach ($this->reqs as $req)
169
        {
170 1
            $req->reject(new ExecutionException('Connection has been closed!'));
171
        }
172
173 116
        $this->reqs = [];
174
175
         // TODO patch missing pub/sub, pipeline, auth
176 116
        $this->handleStop();
177 116
        $this->emit('stop', [ $this ]);
178
179 116
        return true;
180
    }
181
182
    /**
183
     * @override
184
     * @inheritDoc
185
     */
186
    public function end()
187
    {
188
        if (!$this->isStarted() || $this->isBeingDisconnected)
189
        {
190
            return false;
191
        }
192
193
        $this->isBeingDisconnected = true;
194
195
        return true;
196
    }
197
198
    /**
199
     * Dispatch Redis request.
200
     *
201
     * @param Request $command
202
     * @return PromiseInterface
203
     */
204 116
    protected function dispatch(Request $command)
205
    {
206 116
        $request = new Deferred();
207 116
        $promise = $request->getPromise();
208
209 116
        if ($this->isBeingDisconnected)
210
        {
211
            $request->reject(new ExecutionException('Redis client connection is being stopped now.'));
212
        }
213
        else
214
        {
215 116
            $this->stream->write($this->driver->commands($command));
216 116
            $this->reqs[] = $request;
217
        }
218
219 116
        return $promise;
220
    }
221
222
    /**
223
     * @internal
224
     */
225 116 View Code Duplication
    protected function handleStart()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
    {
227 116
        if ($this->stream !== null)
228
        {
229 116
            $this->stream->on('data', [ $this, 'handleData' ]);
230 116
            $this->stream->on('close', [ $this, 'stop' ]);
231
        }
232 116
    }
233
234
    /**
235
     * @internal
236
     */
237 116 View Code Duplication
    protected function handleStop()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
238
    {
239 116
        if ($this->stream !== null)
240
        {
241
            $this->stream->removeListener('data', [ $this, 'handleData' ]);
242
            $this->stream->removeListener('close', [ $this, 'stop' ]);
243
        }
244 116
    }
245
246
    /**
247
     * @internal
248
     * @param SocketInterface $stream
249
     * @param string $chunk
250
     */
251 116
    public function handleData($stream, $chunk)
252
    {
253
        try
254
        {
255 116
            $models = $this->driver->parseResponse($chunk);
256
        }
257
        catch (ParserException $error)
258
        {
259
            $this->emit('error', [ $this, $error ]);
260
            $this->stop();
261
            return;
262
        }
263
264 116
        foreach ($models as $data)
265
        {
266
            try
267
            {
268 116
                $this->handleMessage($data);
269
            }
270
            catch (UnderflowException $error)
271
            {
272
                $this->emit('error', [ $this, $error ]);
273
                $this->stop();
274 116
                return;
275
            }
276
        }
277 116
    }
278
279
    /**
280
     * @internal
281
     * @param ModelInterface $message
282
     */
283 116
    protected function handleMessage(ModelInterface $message)
284
    {
285 116
        if (!$this->reqs)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->reqs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
286
        {
287
            throw new UnderflowException('Unexpected reply received, no matching request found');
288
        }
289
290 116
        $request = array_shift($this->reqs);
291
292 116
        if ($message instanceof ErrorReply)
293
        {
294
            $request->reject($message);
295
        }
296
        else
297
        {
298 116
            $request->resolve($message->getValueNative());
299
        }
300
301 116
        if ($this->isBeingDisconnected && !$this->isBusy())
302
        {
303 116
            $this->stop();
304
        }
305 116
    }
306
307
    /**
308
     * Create socket client with connection to Redis database.
309
     *
310
     * @param string $endpoint
311
     * @return SocketInterface
312
     * @throws ExecutionException
313
     */
314 116
    protected function createClient($endpoint)
315
    {
316 116
        $ex = null;
317
318
        try
319
        {
320 116
            return new Socket($endpoint, $this->loop);
0 ignored issues
show
Bug introduced by
It seems like $this->loop can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
321
        }
322
        catch (Error $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
323
        {}
324
        catch (Exception $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
325
        {}
326
327
        throw new ExecutionException('Redis connection socket could not be created!', 0, $ex);
328
    }
329
};
330