Completed
Push — master ( 9e125a...9a1e57 )
by Kamil
02:25
created

Redis::bitField()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 33
Code Lines 24

Duplication

Lines 10
Ratio 30.3 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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