Completed
Push — master ( 612bb3...e30540 )
by Kamil
04:19
created

Redis   D

Complexity

Total Complexity 34

Size/Duplication

Total Lines 322
Duplicated Lines 4.97 %

Coupling/Cohesion

Components 1
Dependencies 24

Test Coverage

Coverage 57.41%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 24
dl 16
loc 322
ccs 62
cts 108
cp 0.5741
rs 4.819
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A __destruct() 0 4 1
A isStarted() 0 4 1
A isBusy() 0 4 1
B start() 0 32 5
B stop() 0 31 4
A end() 0 17 3
A dispatch() 0 17 2
A handleStart() 8 8 2
A handleStop() 8 8 2
B handleData() 0 27 4
B handleMessage() 0 23 5
A createClient() 0 15 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 25 and the first side effect is on line 346.

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\Promise;
13
use Dazzle\Promise\PromiseInterface;
14
use Dazzle\Redis\Driver\Request;
15
use Dazzle\Redis\Driver\Driver;
16
use Dazzle\Redis\Driver\DriverInterface;
17
use Dazzle\Socket\Socket;
18
use Dazzle\Socket\SocketInterface;
19
use Dazzle\Throwable\Exception\Runtime\ExecutionException;
20
use Dazzle\Throwable\Exception\Runtime\UnderflowException;
21
use Dazzle\Throwable\Exception\Runtime\WriteException;
22
use Error;
23
use Exception;
24
25
class Redis extends BaseEventEmitter implements RedisInterface
26
{
27
    use LoopAwareTrait;
28
    use Command\Compose\ApiChannelTrait;
29
    use Command\Compose\ApiClusterTrait;
30
    use Command\Compose\ApiConnTrait;
31
    use Command\Compose\ApiCoreTrait;
32
    use Command\Compose\ApiGeospatialTrait;
33
    use Command\Compose\ApiHyperLogTrait;
34
    use Command\Compose\ApiKeyValTrait;
35
    use Command\Compose\ApiListTrait;
36
    use Command\Compose\ApiSetTrait;
37
    use Command\Compose\ApiSetHashTrait;
38
    use Command\Compose\ApiSetSortedTrait;
39
    use Command\Compose\ApiTransactionTrait;
40
41
    /**
42
     * @var string
43
     */
44
    protected $endpoint;
45
46
    /**
47
     * @var SocketInterface
48
     */
49
    protected $stream;
50
51
    /**
52
     * @var DriverInterface
53
     */
54
    protected $driver;
55
56
    /**
57
     * @var bool
58
     */
59
    protected $isConnected;
60
61
    /**
62
     * @var bool
63
     */
64
    protected $isBeingDisconnected;
65
66
    /**
67
     * @var PromiseInterface|null;
68
     */
69
    protected $endPromise;
70
71
    /**
72
     * @var array
73
     */
74
    private $reqs;
75
76
    /**
77
     * @param string $endpoint
78
     * @param LoopInterface $loop
79
     */
80
    public function __construct($endpoint, LoopInterface $loop)
81
    {
82
        $this->endpoint = $endpoint;
83
        $this->loop = $loop;
84
        $this->stream = null;
85
        $this->driver = new Driver();
86
87
        $this->isConnected = false;
88
        $this->isBeingDisconnected = false;
89
        $this->endPromise = null;
90
91
        $this->reqs = [];
92
    }
93
94
    /**
95
     *
96
     */
97
    public function __destruct()
98
    {
99
        $this->stop();
100
    }
101
102
    /**
103
     * @override
104
     * @inheritDoc
105
     */
106 137
    public function isStarted()
107
    {
108 137
        return $this->isConnected;
109
    }
110
111
    /**
112
     * @override
113
     * @inheritDoc
114
     */
115 137
    public function isBusy()
116
    {
117 137
        return !empty($this->reqs);
118
    }
119
120
    /**
121
     * @override
122
     * @inheritDoc
123
     */
124 137
    public function start()
125
    {
126 137
        if ($this->isStarted())
127
        {
128
            return Promise::doResolve($this);
129
        }
130
131 137
        $ex = null;
132 137
        $stream = null;
133
134
        try
135
        {
136 137
            $stream = $this->createClient($this->endpoint);
137
        }
138
        catch (Error $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
139
        {}
140
        catch (Exception $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
141
        {}
142
143 137
        if ($ex !== null)
144
        {
145
            return Promise::doReject($ex);
146
        }
147
148 137
        $this->isConnected = true;
149 137
        $this->isBeingDisconnected = false;
150 137
        $this->stream = $stream;
151 137
        $this->handleStart();
152 137
        $this->emit('start', [ $this ]);
153
154 137
        return Promise::doResolve($this);
155
    }
156
157
    /**
158
     * @override
159
     * @inheritDoc
160
     */
161 137
    public function stop()
162
    {
163 137
        if (!$this->isStarted())
164
        {
165 137
            return Promise::doResolve($this);
166
        }
167
168 137
        $this->isBeingDisconnected = true;
169 137
        $this->isConnected = false;
170
171 137
        $this->stream->close();
172 137
        $this->stream = null;
173
174 137
        foreach ($this->reqs as $req)
175
        {
176 2
            $req->reject(new ExecutionException('Connection has been closed!'));
177
        }
178
179 137
        $this->reqs = [];
180 137
        $this->handleStop();
181 137
        $this->emit('stop', [ $this ]);
182
183 137
        if ($this->endPromise !== null)
184
        {
185
            $promise = $this->endPromise;
186
            $this->endPromise = null;
187
            $promise->resolve($this);
0 ignored issues
show
Bug introduced by
The method resolve cannot be called on $promise (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
188
        }
189
190 137
        return Promise::doResolve($this);
191
    }
192
193
    /**
194
     * @override
195
     * @inheritDoc
196
     */
197
    public function end()
198
    {
199
        if (!$this->isStarted())
200
        {
201
            return Promise::doResolve($this);
202
        }
203
        if ($this->isBeingDisconnected)
204
        {
205
            return Promise::doReject(new WriteException('Tried to double end same connection.'));
206
        }
207
208
        $promise = new Promise();
209
        $this->isBeingDisconnected = true;
210
        $this->endPromise = $promise;
211
212
        return $promise;
213
    }
214
215
    /**
216
     * Dispatch Redis request.
217
     *
218
     * @param Request $command
219
     * @return PromiseInterface
220
     */
221 137
    protected function dispatch(Request $command)
222
    {
223 137
        $request = new Deferred();
224 137
        $promise = $request->getPromise();
225
226 137
        if ($this->isBeingDisconnected)
227
        {
228
            $request->reject(new ExecutionException('Redis client connection is being stopped now.'));
229
        }
230
        else
231
        {
232 137
            $this->stream->write($this->driver->commands($command));
233 137
            $this->reqs[] = $request;
234
        }
235
236 137
        return $promise;
237
    }
238
239
    /**
240
     * @internal
241
     */
242 137 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...
243
    {
244 137
        if ($this->stream !== null)
245
        {
246 137
            $this->stream->on('data', [ $this, 'handleData' ]);
247 137
            $this->stream->on('close', [ $this, 'stop' ]);
248
        }
249 137
    }
250
251
    /**
252
     * @internal
253
     */
254 137 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...
255
    {
256 137
        if ($this->stream !== null)
257
        {
258
            $this->stream->removeListener('data', [ $this, 'handleData' ]);
259
            $this->stream->removeListener('close', [ $this, 'stop' ]);
260
        }
261 137
    }
262
263
    /**
264
     * @internal
265
     * @param SocketInterface $stream
266
     * @param string $chunk
267
     */
268 137
    public function handleData($stream, $chunk)
269
    {
270
        try
271
        {
272 137
            $models = $this->driver->parseResponse($chunk);
273
        }
274
        catch (ParserException $error)
275
        {
276
            $this->emit('error', [ $this, $error ]);
277
            $this->stop();
278
            return;
279
        }
280
281 137
        foreach ($models as $data)
282
        {
283
            try
284
            {
285 137
                $this->handleMessage($data);
286
            }
287
            catch (UnderflowException $error)
288
            {
289
                $this->emit('error', [ $this, $error ]);
290
                $this->stop();
291 137
                return;
292
            }
293
        }
294 137
    }
295
296
    /**
297
     * @internal
298
     * @param ModelInterface $message
299
     */
300 137
    protected function handleMessage(ModelInterface $message)
301
    {
302 137
        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...
303
        {
304
            throw new UnderflowException('Unexpected reply received, no matching request found');
305
        }
306
307 137
        $request = array_shift($this->reqs);
308
309 137
        if ($message instanceof ErrorReply)
310
        {
311
            $request->reject($message);
312
        }
313
        else
314
        {
315 137
            $request->resolve($message->getValueNative());
316
        }
317
318 137
        if ($this->isBeingDisconnected && !$this->isBusy())
319
        {
320 137
            $this->stop();
321
        }
322 137
    }
323
324
    /**
325
     * Create socket client with connection to Redis database.
326
     *
327
     * @param string $endpoint
328
     * @return SocketInterface
329
     * @throws ExecutionException
330
     */
331 137
    protected function createClient($endpoint)
332
    {
333 137
        $ex = null;
334
335
        try
336
        {
337 137
            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...
338
        }
339
        catch (Error $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
340
        {}
341
        catch (Exception $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
342
        {}
343
344
        throw new ExecutionException('Redis connection socket could not be created!', 0, $ex);
345
    }
346
};
347