Completed
Push — master ( 93ca92...ff64f1 )
by Kamil
10s
created

Redis   C

Complexity

Total Complexity 33

Size/Duplication

Total Lines 307
Duplicated Lines 5.21 %

Coupling/Cohesion

Components 1
Dependencies 22

Test Coverage

Coverage 60.61%

Importance

Changes 0
Metric Value
dl 16
loc 307
c 0
b 0
f 0
wmc 33
lcom 1
cbo 22
ccs 60
cts 99
cp 0.6061
rs 5.4421

13 Methods

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

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 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 76
    public function isStarted()
99
    {
100 76
        return $this->isConnected;
101
    }
102
103
    /**
104
     * @override
105
     * @inheritDoc
106
     */
107 76
    public function isBusy()
108
    {
109 76
        return !empty($this->reqs);
110
    }
111
112
    /**
113
     * @override
114
     * @inheritDoc
115
     */
116 76
    public function start()
117
    {
118 76
        if ($this->isStarted())
119
        {
120
            return false;
121
        }
122
123 76
        $ex = null;
124 76
        $stream = null;
125
126
        try
127
        {
128 76
            $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 76
        if ($ex !== null)
136
        {
137
            return false;
138
        }
139
140 76
        $this->isConnected = true;
141 76
        $this->isBeingDisconnected = false;
142 76
        $this->stream = $stream;
143
144
        // TODO patch missing pub/sub, pipeline, auth
145 76
        $this->handleStart();
146 76
        $this->emit('start', [ $this ]);
147
148 76
        return true;
149
    }
150
151
    /**
152
     * @override
153
     * @inheritDoc
154
     */
155 76
    public function stop()
156
    {
157 76
        if (!$this->isStarted())
158
        {
159 76
            return false;
160
        }
161
162 76
        $this->isBeingDisconnected = true;
163 76
        $this->isConnected = false;
164
165 76
        $this->stream->close();
166 76
        $this->stream = null;
167
168 76
        foreach ($this->reqs as $req)
169
        {
170
            $req->reject(new ExecutionException('Connection has been closed!'));
171
        }
172
173 76
        $this->reqs = [];
174
175
         // TODO patch missing pub/sub, pipeline, auth
176 76
        $this->handleStop();
177 76
        $this->emit('stop', [ $this ]);
178
179 76
        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 76
    protected function dispatch(Request $command)
205
    {
206 76
        $request = new Deferred();
207 76
        $promise = $request->getPromise();
208
209 76
        if ($this->isBeingDisconnected)
210
        {
211
            $request->reject(new ExecutionException('Redis client connection is being stopped now.'));
212
        }
213
        else
214
        {
215 76
            $this->stream->write($this->driver->commands($command));
216 76
            $this->reqs[] = $request;
217
        }
218
219 76
        return $promise;
220
    }
221
222
    /**
223
     * @internal
224
     */
225 76 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 76
        if ($this->stream !== null)
228
        {
229 76
            $this->stream->on('data', [ $this, 'handleData' ]);
230 76
            $this->stream->on('close', [ $this, 'stop' ]);
231
        }
232 76
    }
233
234
    /**
235
     * @internal
236
     */
237 76 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 76
        if ($this->stream !== null)
240
        {
241
            $this->stream->removeListener('data', [ $this, 'handleData' ]);
242
            $this->stream->removeListener('close', [ $this, 'stop' ]);
243
        }
244 76
    }
245
246
    /**
247
     * @internal
248
     * @param SocketInterface $stream
249
     * @param string $chunk
250
     */
251 76
    public function handleData($stream, $chunk)
252
    {
253
        try
254
        {
255 76
            $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 76
        foreach ($models as $data)
265
        {
266
            try
267
            {
268 76
                $this->handleMessage($data);
269
            }
270
            catch (UnderflowException $error)
271
            {
272
                $this->emit('error', [ $this, $error ]);
273
                $this->stop();
274 76
                return;
275
            }
276
        }
277 76
    }
278
279
    /**
280
     * @internal
281
     * @param ModelInterface $message
282
     */
283 76
    protected function handleMessage(ModelInterface $message)
284
    {
285 76
        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 76
        $request = array_shift($this->reqs);
291
292 76
        if ($message instanceof ErrorReply)
293
        {
294
            $request->reject($message);
295
        }
296
        else
297
        {
298 76
            $request->resolve($message->getValueNative());
299
        }
300
301 76
        if ($this->isBeingDisconnected && !$this->isBusy())
302
        {
303 76
            $this->stop();
304
        }
305 76
    }
306
307
    /**
308
     * Create socket client with connection to Redis database.
309
     *
310
     * @param string $endpoint
311
     * @return SocketInterface
312
     * @throws ExecutionException
313
     */
314 76
    protected function createClient($endpoint)
315
    {
316 76
        $ex = null;
317
318
        try
319
        {
320 76
            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