Completed
Push — master ( 5512c1...8bce76 )
by Vasily
02:23
created

DNode::onPacket()   F

Complexity

Conditions 18
Paths 257

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 257
nop 1
dl 0
loc 49
rs 3.3208
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace PHPDaemon\DNode;
3
4
use PHPDaemon\Exceptions\ProtocolError;
5
use PHPDaemon\Exceptions\UndefinedMethodCalled;
6
7
/**
8
 * DNode
9
 * @package PHPDaemon\WebSocket\Traits
10
 * @author  Vasily Zorin <[email protected]>
11
 */
12
trait DNode
13
{
14
    /**
15
     * @var array Associative array of callback functions registered by callRemote()
16
     */
17
    protected $callbacks = [];
18
19
    /**
20
     * @var array Associative array of persistent callback functions registered by callRemote()
21
     */
22
    protected $persistentCallbacks = [];
23
24
    /**
25
     * @var boolean If true, callRemote() will register callbacks as persistent ones
26
     */
27
    protected $persistentMode = false;
28
29
    /**
30
     * @var integer Incremental counter of callback functions registered by callRemote()
31
     */
32
    protected $counter = 0;
33
34
    /**
35
     * @var array Associative array of registered remote methods (received in 'methods' call)
36
     */
37
    protected $remoteMethods = [];
38
39
    /**
40
     * @var array Associative array of local methods, set by defineLocalMethods()
41
     */
42
    protected $localMethods = [];
43
44
    /**
45
     * @var boolean Was this object cleaned up?
46
     */
47
    protected $cleaned = false;
48
49
    /**
50
     * @var boolean Should __call method call parent::__call()?
51
     */
52
    protected $magicCallParent = false;
53
54
    /**
55
     * Ensures that the variable passed by reference holds a valid callback-function
56
     * If it doesn't, its value will be reset to null
57
     * @param  mixed &$arg Argument
58
     * @return boolean
59
     */
60
    public static function ensureCallback(&$arg)
61
    {
62
        if ($arg instanceof \Closure) {
63
            return true;
64
        }
65
        if (is_array($arg) && sizeof($arg) === 2) {
66
            if (isset($arg[0]) && $arg[0] instanceof \PHPDaemon\WebSocket\Route) {
67 View Code Duplication
                if (isset($arg[1]) && is_string($arg[1]) && strncmp($arg[1], 'remote_', 7) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
68
                    return true;
69
                }
70
            }
71
        }
72
        $arg = null;
73
        return false;
74
    }
75
76
    /**
77
     * Encodes value into JSON for debugging purposes
78
     * @param mixed $m Data
79
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
80
     */
81
    public static function toJsonDebug($m)
82
    {
83
        static::toJsonDebugResursive($m);
84
        return static::toJson($m);
85
    }
86
87
    /**
88
     * Recursion handler for toJsonDebug()
89
     * @param  array &$a Data
90
     * @return void
91
     */
92
    public static function toJsonDebugResursive(&$m)
93
    {
94
        if ($m instanceof \Closure) {
95
            $m = '__CALLBACK__';
96
        } elseif (is_array($m)) {
97
            if (sizeof($m) === 2 && isset($m[0]) && $m[0] instanceof \PHPDaemon\WebSocket\Route) {
98 View Code Duplication
                if (isset($m[1]) && is_string($m[1]) && strncmp($m[1], 'remote_', 7) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
99
                    $m = '__CALLBACK__';
100
                }
101
            } else {
102
                foreach ($m as &$v) {
103
                    static::toJsonDebugResursive($v);
104
                }
105
            }
106
        } elseif (is_object($m)) {
107
            foreach ($m as &$v) {
108
                static::toJsonDebugResursive($v);
109
            }
110
        }
111
    }
112
113
    /**
114
     * Default onHandshake() method
115
     * @return void
116
     */
117
    public function onHandshake()
118
    {
119
        $this->defineLocalMethods();
120
    }
121
122
    /**
123
     * Defines local methods
124
     * @param  array $arr Associative array of callbacks (methodName => callback)
125
     * @return void
126
     */
127
    protected function defineLocalMethods($arr = [])
128
    {
129
        foreach (get_class_methods($this) as $m) {
130
            if (substr($m, -6) === 'Method') {
131
                $k = substr($m, 0, -6);
132
                if ($k === 'methods') {
133
                    continue;
134
                }
135
                $arr[$k] = [$this, $m];
136
            }
137
        }
138
        foreach ($arr as $k => $v) {
139
            $this->localMethods[$k] = $v;
140
        }
141
        $this->persistentMode = true;
142
        $this->callRemote('methods', $arr);
143
        $this->persistentMode = false;
144
    }
145
146
    /**
147
     * Calls a remote method
148
     * @param  string $method Method name
149
     * @param  mixed ...$args Arguments
150
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be DNode?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
151
     */
152
    public function callRemote($method, ...$args)
153
    {
154
        $this->callRemoteArray($method, $args);
155
        return $this;
156
    }
157
158
159
    /**
160
     * Export object methods
161
     * @param $object
162
     * @return array
163
     */
164
    public static function exportObjectMethods($object) {
165
        $methods = [];
166
        foreach (get_class_methods($object) as $method) {
167
            if ($method[0] === '_') {
168
                continue;
169
            }
170
            $methods[$method] = [$object, $method];
171
        }
172
        return $methods;
173
    }
174
175
    /**
176
     * Calls a remote method with array of arguments
177
     * @param  string $method Method name
178
     * @param  array $args Arguments
179
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be DNode?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
180
     */
181
    public function callRemoteArray($method, $args)
182
    {
183
        if (isset($this->remoteMethods[$method])) {
184
            $this->remoteMethods[$method](...$args);
185
            return $this;
186
        }
187
        $pct = [
188
            'method' => $method,
189
        ];
190
        if (sizeof($args)) {
191
            $callbacks = [];
192
            $path = [];
193
            $this->extractCallbacks($args, $callbacks, $path);
194
            $pct['arguments'] = $args;
195
            if (sizeof($callbacks)) {
196
                $pct['callbacks'] = $callbacks;
197
            }
198
        }
199
        $this->sendPacket($pct);
200
        return $this;
201
    }
202
203
    /**
204
     * Extracts callback functions from array of arguments
205
     * @param  array &$args Arguments
206
     * @param  array &$list Output array for 'callbacks' property
207
     * @param  array &$path Recursion path holder
208
     * @return void
209
     */
210
    protected function extractCallbacks(&$args, &$list, &$path)
211
    {
212
        foreach ($args as $k => &$v) {
213
            if (is_array($v)) {
214
                if (sizeof($v) === 2) {
215
                    if (isset($v[0]) && is_object($v[0])) {
216
                        if (isset($v[1]) && is_string($v[1])) {
217
                            $id = ++$this->counter;
218 View Code Duplication
                            if ($this->persistentMode) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
219
                                $this->persistentCallbacks[$id] = $v;
220
                            } else {
221
                                $this->callbacks[$id] = $v;
222
                            }
223
                            $v = '';
224
                            $list[$id] = $path;
225
                            $list[$id][] = $k;
226
                            continue;
227
                        }
228
                    }
229
                }
230
                $path[] = $k;
231
                $this->extractCallbacks($v, $list, $path);
232
                array_pop($path);
233
            } elseif ($v instanceof \Closure) {
234
                $id = ++$this->counter;
235 View Code Duplication
                if ($this->persistentMode) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
236
                    $this->persistentCallbacks[$id] = $v;
237
                } else {
238
                    $this->callbacks[$id] = $v;
239
                }
240
                $v = '';
241
                $list[$id] = $path;
242
                $list[$id][] = $k;
243
            }
244
        }
245
    }
246
247
    /**
248
     * Sends a packet
249
     * @param  array $pct Data
250
     * @return void
251
     */
252
    protected function sendPacket($pct)
253
    {
254
        if (!$this->client) {
0 ignored issues
show
Bug introduced by
The property client does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
255
            return;
256
        }
257
        if (is_string($pct['method']) && ctype_digit($pct['method'])) {
258
            $pct['method'] = (int)$pct['method'];
259
        }
260
        $this->client->sendFrame(static::toJson($pct) . "\n");
261
    }
262
263
    /**
264
     * Encodes value into JSON
265
     * @param  mixed $m Value
266
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
267
     */
268
    public static function toJson($m)
269
    {
270
        return json_encode($m, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
271
    }
272
273
    /**
274
     * Calls a local method
275
     * @param  string $method Method name
0 ignored issues
show
Bug introduced by
There is no parameter named $method. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
276
     * @param  mixed ...$args Arguments
277
     * @return this
0 ignored issues
show
Documentation introduced by
Should the return type not be DNode?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
278
     */
279
    public function callLocal(...$args)
280
    {
281
        if (!sizeof($args)) {
282
            return $this;
283
        }
284
        $method = array_shift($args);
285
        $p = [
286
            'method' => $method,
287
            'arguments' => $args,
288
        ];
289
        $this->onPacket($p);
290
        return $this;
291
    }
292
293
    /**
294
     * Called when new packet is received
295
     * @param  array $pct Packet
296
     * @return void
297
     */
298
    public function onPacket($pct)
299
    {
300
        if ($this->cleaned) {
301
            return;
302
        }
303
        $m = isset($pct['method']) ? $pct['method'] : null;
304
        $args = isset($pct['arguments']) ? $pct['arguments'] : [];
305
        if (isset($pct['callbacks']) && is_array($pct['callbacks'])) {
306
            foreach ($pct['callbacks'] as $id => $path) {
307
                static::setPath($args, $path, [$this, 'remote_' . $id]);
308
            }
309
        }
310
        if (isset($pct['links']) && is_array($pct['links'])) {
311
            foreach ($pct['links'] as $link) {
312
                static::setPath($args, $link['to'], static::getPath($args, $link['from']));
313
            }
314
        }
315
        try {
316
            if (is_string($m)) {
317
                if (isset($this->localMethods[$m])) {
318
                    $this->localMethods[$m](...$args);
319
                } elseif (method_exists($this, $m . 'Method')) {
320
                    $func = [$this, $m . 'Method'];
321
                    $func(...$args);
322
                } else {
323
                    $this->handleException(new UndefinedMethodCalled(
0 ignored issues
show
Documentation Bug introduced by
The method handleException does not exist on object<PHPDaemon\DNode\DNode>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
324
                        'DNode: local method ' . json_encode($m) . ' does not exist. Packet: ' . json_encode($pct)
325
                    ));
326
                }
327
            } elseif (is_int($m)) {
328
                if (isset($this->callbacks[$m])) {
329
                    if (!$this->callbacks[$m](...$args)) {
330
                        unset($this->callbacks[$m]);
331
                    }
332
                } elseif (isset($this->persistentCallbacks[$m])) {
333
                    $this->persistentCallbacks[$m](...$args);
334
                } else {
335
                    $this->handleException(new UndefinedMethodCalled(
0 ignored issues
show
Documentation Bug introduced by
The method handleException does not exist on object<PHPDaemon\DNode\DNode>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
336
                        'DNode: local callback # ' . $m . ' is not registered. Packet: ' . json_encode($pct)
337
                    ));
338
                }
339
            } else {
340
                $this->handleException(new ProtocolError(
0 ignored issues
show
Documentation Bug introduced by
The method handleException does not exist on object<PHPDaemon\DNode\DNode>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
341
                    'DNode: \'method\' must be string or integer. Packet: ' . json_encode($pct)
342
                ));
343
            }
344
        } catch (\Throwable $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
345
        }
346
    }
347
348
    /**
349
     * Sets value by materialized path
350
     * @param  array &$m
351
     * @param  array $path
352
     * @param  mixed $val
353
     * @return void
354
     */
355
    protected static function setPath(&$m, $path, $val)
356
    {
357
        foreach ($path as $p) {
358
            $m =& $m[$p];
359
        }
360
        $m = $val;
361
    }
362
363
    /**
364
     * Finds value by materialized path
365
     * @param  array &$m
366
     * @param  array $path
367
     * @return mixed Value
368
     */
369
    protected static function &getPath(&$m, $path)
370
    {
371
        foreach ($path as $p) {
372
            $m =& $m[$p];
373
        }
374
        return $m;
375
    }
376
377
    /**
378
     * Called when session is finished
379
     * @return void
380
     */
381
    public function onFinish()
382
    {
383
        $this->cleanup();
384
        parent::onFinish();
385
    }
386
387
    /**
388
     * Swipes internal structures
389
     * @return void
390
     */
391
    public function cleanup()
392
    {
393
        $this->cleaned = true;
394
        $this->remoteMethods = [];
395
        $this->localMethods = [];
396
        $this->persistentCallbacks = [];
397
        $this->callbacks = [];
398
    }
399
400
    /**
401
     * Magic __call method
402
     * @param  string $method Method name
403
     * @param  array $args Arguments
404
     * @throws UndefinedMethodCalled if method name not start from 'remote_'
405
     * @return mixed
406
     */
407
    public function __call($method, $args)
408
    {
409
        if (strncmp($method, 'remote_', 7) === 0) {
410
            $this->callRemoteArray(substr($method, 7), $args);
411
        } elseif ($this->magicCallParent) {
412
            return parent::__call($method, $args);
413
        } else {
414
            throw new UndefinedMethodCalled('Call to undefined method ' . get_class($this) . '->' . $method);
415
        }
416
    }
417
418
    /**
419
     * Called when new frame is received
420
     * @param string $data Frame's contents
421
     * @param integer $type Frame's type
422
     * @return void
423
     */
424
    public function onFrame($data, $type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
425
    {
426
        foreach (explode("\n", $data) as $pct) {
427
            if ($pct === '') {
428
                continue;
429
            }
430
            $this->onPacket(json_decode($pct, true));
431
        }
432
    }
433
434
    /**
435
     * Handler of the 'methods' method
436
     * @param  array $methods Associative array of methods
437
     * @return void
438
     */
439
    protected function methodsMethod($methods)
440
    {
441
        $this->remoteMethods = $methods;
442
    }
443
}
444