Completed
Push — EventLoopContainer ( c9a84d...6e77fa )
by Vasily
04:04
created

Connection   D

Complexity

Total Complexity 118

Size/Duplication

Total Lines 762
Duplicated Lines 10.76 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 5
Bugs 3 Features 0
Metric Value
dl 82
loc 762
rs 4
c 5
b 3
f 0
wmc 118
lcom 1
cbo 9

29 Methods

Rating   Name   Duplication   Size   Complexity  
A isConnected() 0 4 1
A setDgram() 0 4 1
A setPeername() 0 13 3
B __get() 0 11 5
A getSocketName() 0 8 2
A setParentSocket() 0 4 1
A onUdpPacket() 0 3 1
A onReady() 0 8 2
A onInheritanceFromRequest() 0 3 1
A onFailure() 0 7 2
A onFailureEv() 0 12 4
A __destruct() 0 6 3
A write() 0 7 3
A onConnected() 0 11 3
C importParams() 27 34 12
C initSSLContext() 4 29 7
A getUrl() 0 4 1
A getHost() 0 4 1
A getPort() 0 4 1
F connect() 8 59 17
A connectUnix() 0 19 3
C connectRaw() 0 39 7
C connectUdp() 20 50 9
C connectTcp() 23 69 17
A setKeepalive() 0 5 2
A close() 0 7 2
A setTimeouts() 0 16 2
A setOption() 0 8 2
A onFinish() 0 10 3

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Connection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Connection, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace PHPDaemon\Network;
3
4
use PHPDaemon\BoundSocket\Generic;
5
use PHPDaemon\BoundSocket\TCP;
6
use PHPDaemon\Cache\CappedStorage;
7
use PHPDaemon\Cache\CappedStorageHits;
8
use PHPDaemon\Config;
9
use PHPDaemon\Core\Daemon;
10
use PHPDaemon\Structures\StackCallbacks;
11
12
/**
13
 * Connection
14
 * @package PHPDaemon\Network
15
 * @author  Vasily Zorin <[email protected]>
16
 */
17
abstract class Connection extends IOStream
18
{
19
    /**
20
     * @var string Path
21
     */
22
    protected $path;
23
24
    /**
25
     * @var string Hostname
26
     */
27
    protected $host;
28
29
    /**
30
     * @var string Real host
31
     */
32
    protected $hostReal;
33
34
    /**
35
     * @var integer Port number
36
     */
37
    protected $port;
38
39
    /**
40
     * @var string User name
41
     */
42
    protected $user;
43
44
    /**
45
     * @var string Password
46
     */
47
    protected $password;
48
49
    /**
50
     * @var string Address
51
     */
52
    protected $addr;
53
54
    /**
55
     * @var object Stack of callbacks called when connection is established
56
     */
57
    protected $onConnected = null;
58
59
    /**
60
     * @var boolean Connected?
61
     */
62
    protected $connected = false;
63
64
    /**
65
     * @var boolean Failed?
66
     */
67
    protected $failed = false;
68
69
    /**
70
     * @var integer Timeout
71
     */
72
    protected $timeout = 120;
73
74
    /**
75
     * @var string Local address
76
     */
77
    protected $locAddr;
78
79
    /**
80
     * @var integer Local port
81
     */
82
    protected $locPort;
83
84
    /**
85
     * @var boolean Keepalive?
86
     */
87
    protected $keepalive = false;
88
89
    /**
90
     * @var string Type
91
     */
92
    protected $type;
93
94
    /**
95
     * @var Generic Parent socket
96
     */
97
    protected $parentSocket;
98
99
    /**
100
     * @var boolean Dgram connection?
101
     */
102
    protected $dgram = false;
103
104
    /**
105
     * @var boolean Enable bevConnect?
106
     */
107
    protected $bevConnectEnabled = true;
108
109
    /**
110
     * @var array URI information
111
     */
112
    protected $uri;
113
114
    /**
115
     * @var string Scheme
116
     */
117
    protected $scheme;
118
119
    /**
120
     * @var string Private key file
121
     */
122
    protected $pkfile;
123
124
    /**
125
     * @var string Certificate file
126
     */
127
    protected $certfile;
128
129
    /**
130
     * @var string Passphrase
131
     */
132
    protected $passphrase;
133
134
    /**
135
     * @var boolean Verify peer?
136
     */
137
    protected $verifypeer = false;
138
139
    /**
140
     * @var boolean Allow self-signed?
141
     */
142
    protected $allowselfsigned = true;
143
144
    /**
145
     * @var CappedStorage Context cache
146
     */
147
    protected static $contextCache;
148
149
    /**
150
     * @var number Context cache size
151
     */
152
    protected static $contextCacheSize = 64;
153
154
    /**
155
     * Connected?
156
     * @return boolean
157
     */
158
    public function isConnected()
159
    {
160
        return $this->connected;
161
    }
162
163
    /**
164
     * Sets DGRAM mode
165
     * @param  boolean $bool DGRAM Mode
166
     * @return void
167
     */
168
    public function setDgram($bool)
169
    {
170
        $this->dgram = $bool;
171
    }
172
173
    /**
174
     * Sets peer name
175
     * @param  string $host Hostname
176
     * @param  integer $port Port
177
     * @return void
178
     */
179
    public function setPeername($host, $port)
180
    {
181
        $this->host = $host;
182
        $this->port = $port;
183
        $this->addr = '[' . $this->host . ']:' . $this->port;
184
        if ($this->pool->allowedClients !== null) {
185
            if (!TCP::netMatch($this->pool->allowedClients, $this->host)) {
186
                Daemon::log('Connection is not allowed (' . $this->host . ')');
187
                $this->ready = false;
188
                $this->finish();
189
            }
190
        }
191
    }
192
193
    /**
194
     * Getter
195
     * @param  string $name Name
196
     * @return mixed
197
     */
198
    public function __get($name)
199
    {
200
        if ($name === 'connected'
201
            || $name === 'hostReal'
202
            || $name === 'host'
203
            || $name === 'port'
204
        ) {
205
            return $this->{$name};
206
        }
207
        return parent::__get($name);
208
    }
209
210
    /**
211
     * Get socket name
212
     * @param  string &$addr Addr
213
     * @param  srting &$port Port
214
     * @return void
215
     */
216
    public function getSocketName(&$addr, &$port)
217
    {
218
        if (func_num_args() === 0) {
219
            \EventUtil::getSocketName($this->bev->fd, $this->locAddr, $this->locPort);
220
            return;
221
        }
222
        \EventUtil::getSocketName($this->bev->fd, $addr, $port);
223
    }
224
225
    /**
226
     * Sets parent socket
227
     * @param \PHPDaemon\BoundSocket\Generic $sock
228
     * @return void
229
     */
230
    public function setParentSocket(Generic $sock)
231
    {
232
        $this->parentSocket = $sock;
233
    }
234
235
    /**
236
     * Called when new UDP packet received
237
     * @param  object $pct Packet
238
     * @return void
239
     */
240
    public function onUdpPacket($pct)
241
    {
242
    }
243
244
    /**
245
     * Called when the connection is handshaked (at low-level), and peer is ready to recv. data
246
     * @return void
247
     */
248
    public function onReady()
249
    {
250
        $this->connected = true;
251
        if ($this->onConnected) {
252
            $this->onConnected->executeAll($this);
253
            $this->onConnected = null;
254
        }
255
    }
256
257
    /**
258
     * Called if we inherit connection from request
259
     * @param  Request $req Parent Request
260
     * @return void
261
     */
262
    public function onInheritanceFromRequest($req)
263
    {
264
    }
265
266
    /**
267
     * Called when the connection failed to be established
268
     * @return void
269
     */
270
    public function onFailure()
271
    {
272
        if ($this->onConnected) {
273
            $this->onConnected->executeAll($this);
274
            $this->onConnected = null;
275
        }
276
    }
277
278
    /**
279
     * Called when the connection failed
280
     * @param  EventBufferEvent $bev
0 ignored issues
show
Documentation introduced by
Should the type for parameter $bev not be EventBufferEvent|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
281
     * @return void
282
     */
283
    public function onFailureEv($bev = null)
0 ignored issues
show
Unused Code introduced by
The parameter $bev 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...
284
    {
285
        try {
286
            if (!$this->connected && !$this->failed) {
287
                $this->failed = true;
288
                $this->onFailure();
289
            }
290
            $this->connected = false;
291
        } catch (\Exception $e) {
292
            Daemon::uncaughtExceptionHandler($e);
293
        }
294
    }
295
296
    /**
297
     * Destructor
298
     * @return void
299
     */
300
    public function __destruct()
301
    {
302
        if ($this->dgram && $this->parentSocket) {
303
            $this->parentSocket->unassignAddr($this->addr);
0 ignored issues
show
Documentation Bug introduced by
The method unassignAddr does not exist on object<PHPDaemon\BoundSocket\Generic>? 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...
304
        }
305
    }
306
307
    /**
308
     * Send data to the connection. Note that it just writes to buffer that flushes at every baseloop
309
     * @param  string $data Data to send
310
     * @return boolean       Success
311
     */
312
    public function write($data)
313
    {
314
        if ($this->dgram) {
315
            return $this->parentSocket->sendTo($data, $this->finished ? MSG_EOF : 0, $this->host, $this->port);
0 ignored issues
show
Documentation Bug introduced by
The method sendTo does not exist on object<PHPDaemon\BoundSocket\Generic>? 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...
316
        }
317
        return parent::write($data);
318
    }
319
320
    /**
321
     * Executes the given callback when/if the connection is handshaked
322
     * @param  callable $cb Callback
323
     * @return void
324
     */
325
    public function onConnected($cb)
326
    {
327
        if ($this->connected) {
328
            $cb($this);
329
        } else {
330
            if (!$this->onConnected) {
331
                $this->onConnected = new StackCallbacks;
332
            }
333
            $this->onConnected->push($cb);
334
        }
335
    }
336
337
    protected function importParams()
338
    {
339 View Code Duplication
        foreach ($this->uri['params'] as $key => $val) {
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...
340
            if (isset($this->{$key}) && is_bool($this->{$key})) {
341
                $this->{$key} = (bool)$val;
342
                continue;
343
            }
344
            if (!property_exists($this, $key)) {
345
                Daemon::log(get_class($this) . ': unrecognized setting \'' . $key . '\'');
346
                continue;
347
            }
348
            $this->{$key} = $val;
349
        }
350
        if (!$this->ctxname) {
351
            return;
352
        }
353 View Code Duplication
        if (!isset(Daemon::$config->{'TransportContext:' . $this->ctxname})) {
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...
354
            Daemon::log(get_class($this) . ': undefined transport context \'' . $this->ctxname . '\'');
355
            return;
356
        }
357
        $ctx = Daemon::$config->{'TransportContext:' . $this->ctxname};
358 View Code Duplication
        foreach ($ctx as $key => $entry) {
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...
359
            $value = ($entry instanceof Config\Entry\Generic) ? $entry->value : $entry;
360
            if (isset($this->{$key}) && is_bool($this->{$key})) {
361
                $this->{$key} = (bool)$value;
362
                continue;
363
            }
364
            if (!property_exists($this, $key)) {
365
                Daemon::log(get_class($this) . ': unrecognized setting in transport context \'' . $this->ctxname . '\': \'' . $key . '\'');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 139 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
366
                continue;
367
            }
368
            $this->{$key} = $value;
369
        }
370
    }
371
372
    /**
373
     * Initialize SSL context
374
     * @return object|false Context
375
     */
376
    protected function initSSLContext()
377
    {
378 View Code Duplication
        if (!\EventUtil::sslRandPoll()) {
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...
379
            Daemon::$process->log(get_class($this->pool) . ': EventUtil::sslRandPoll failed');
380
            return false;
381
        }
382
        $params = [
383
            \EventSslContext::OPT_VERIFY_PEER => $this->verifypeer,
384
            \EventSslContext::OPT_ALLOW_SELF_SIGNED => $this->allowselfsigned,
385
        ];
386
        if ($this->certfile !== null) {
387
            $params[\EventSslContext::OPT_LOCAL_CERT] = $this->certfile;
388
        }
389
        if ($this->pkfile !== null) {
390
            $params[\EventSslContext::OPT_LOCAL_PK] = $this->pkfile;
391
        }
392
        if ($this->passphrase !== null) {
393
            $params[\EventSslContext::OPT_PASSPHRASE] = $this->passphrase;
394
        }
395
        $hash = igbinary_serialize($params);
396
        if (!self::$contextCache) {
397
            self::$contextCache = new CappedStorageHits(self::$contextCacheSize);
398
        } elseif ($ctx = self::$contextCache->getValue($hash)) {
399
            return $ctx;
400
        }
401
        $ctx = new \EventSslContext(\EventSslContext::SSLv3_CLIENT_METHOD, $params);
402
        self::$contextCache->put($hash, $ctx);
403
        return $ctx;
404
    }
405
406
    /**
407
     * Get URL
408
     * @return string
409
     */
410
    public function getUrl()
411
    {
412
        return $this->url;
413
    }
414
415
    /**
416
     * Get host
417
     * @return string
418
     */
419
    public function getHost()
420
    {
421
        return $this->host;
422
    }
423
424
    /**
425
     * Get port
426
     * @return integer
427
     */
428
    public function getPort()
429
    {
430
        return $this->port;
431
    }
432
433
    /**
434
     * Connects to URL
435
     * @param  string $url URL
436
     * @param  callable $cb Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
437
     * @return boolean       Success
438
     */
439
    public function connect($url, $cb = null)
440
    {
441
        $this->uri = Config\Object::parseCfgUri($url);
0 ignored issues
show
Documentation Bug introduced by
It seems like \PHPDaemon\Config\Object::parseCfgUri($url) can also be of type false. However, the property $uri is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
442
        $u =& $this->uri;
443
        if (!$u) {
444
            return false;
445
        }
446
        $this->importParams();
447
        if (!isset($u['port'])) {
448
            if ($this->ssl) {
449 View Code Duplication
                if (isset($this->pool->config->sslport->value)) {
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...
450
                    $u['port'] = $this->pool->config->sslport->value;
451
                }
452 View Code Duplication
            } else {
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...
453
                if (isset($this->pool->config->port->value)) {
454
                    $u['port'] = $this->pool->config->port->value;
455
                }
456
            }
457
        }
458
        if (isset($u['user'])) {
459
            $this->user = $u['user'];
460
        }
461
462
        if ($this->ssl) {
463
            $this->setContext($this->initSSLContext(), \EventBufferEvent::SSL_CONNECTING);
0 ignored issues
show
Security Bug introduced by
It seems like $this->initSSLContext() targeting PHPDaemon\Network\Connection::initSSLContext() can also be of type false; however, PHPDaemon\Network\IOStream::setContext() does only seem to accept object, did you maybe forget to handle an error condition?
Loading history...
464
        }
465
466
        $this->url = $url;
467
        $this->scheme = strtolower($u['scheme']);
468
        $this->host = isset($u['host']) ? $u['host'] : null;
469
        $this->port = isset($u['port']) ? $u['port'] : 0;
470
471
        if (isset($u['pass'])) {
472
            $this->password = $u['pass'];
473
        }
474
475
        if (isset($u['path'])) {
476
            $this->path = ltrim($u['path'], '/');
477
        }
478
479
        if ($cb !== null) {
480
            $this->onConnected($cb);
481
        }
482
483
        if ($this->scheme === 'unix') {
484
            return $this->connectUnix($u['path']);
485
        }
486
        if ($this->scheme === 'raw') {
487
            return $this->connectRaw($u['host']);
488
        }
489
        if ($this->scheme === 'udp') {
490
            return $this->connectUdp($this->host, $this->port);
491
        }
492
        if ($this->scheme === 'tcp') {
493
            return $this->connectTcp($this->host, $this->port);
494
        }
495
        Daemon::log(get_class($this) . ': connect(): unrecoginized scheme \'' . $this->scheme . '\' (not unix/raw/udp/tcp) in URL: ' . $url);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 141 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
496
        return false;
497
    }
498
499
    /**
500
     * Establish UNIX socket connection
501
     * @param  string $path Path
502
     * @return boolean       Success
503
     */
504
    public function connectUnix($path)
505
    {
506
        $this->type = 'unix';
507
508
        if (!$this->bevConnectEnabled) {
509
            $fd = socket_create(AF_UNIX, SOCK_STREAM, 0);
510
            if (!$fd) {
511
                return false;
512
            }
513
            socket_set_nonblock($fd);
514
            @socket_connect($fd, $path, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
515
            $this->setFd($fd);
516
            return true;
517
        }
518
        $this->bevConnect = true;
519
        $this->addr = 'unix:' . $path;
520
        $this->setFd(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
521
        return true;
522
    }
523
524
    /**
525
     * Establish raw socket connection
526
     * @param  string $host Hostname
527
     * @return boolean       Success
528
     */
529
    public function connectRaw($host)
530
    {
531
        $this->type = 'raw';
532
        if (@inet_pton($host) === false) { // dirty check
533
            \PHPDaemon\Clients\DNS\Pool::getInstance()->resolve($host, function ($result) use ($host) {
534
                if ($result === false) {
535
                    Daemon::log(get_class($this) . '->connectRaw : unable to resolve hostname: ' . $host);
536
                    $this->onFailureEv();
537
                    return;
538
                }
539
                // @TODO stack of addrs
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
540
                if (is_array($result)) {
541
                    srand(Daemon::$process->getPid());
542
                    $real = $result[rand(0, sizeof($result) - 1)];
543
                    srand();
544
                } else {
545
                    $real = $result;
546
                }
547
                $this->connectRaw($real);
548
            });
549
            return true;
550
        }
551
        $this->hostReal = $host;
552
        if ($this->host === null) {
553
            $this->host = $this->hostReal;
554
        }
555
        $this->addr = $this->hostReal . ':raw';
556
        $fd = socket_create(\EventUtil::AF_INET, \EventUtil::SOCK_RAW, 1);
557
        if (!$fd) {
558
            return false;
559
        }
560
        socket_set_nonblock($fd);
561
        @socket_connect($fd, $host, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
562
        $this->setFd($fd);
563
        if (!$this->bev) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) $this->bev;.
Loading history...
564
            return false;
565
        }
566
        return true;
567
    }
568
569
    /**
570
     * Establish UDP connection
571
     * @param  string $host Hostname
572
     * @param  integer $port Port
573
     * @return boolean       Success
574
     */
575
    public function connectUdp($host, $port)
576
    {
577
        $this->type = 'udp';
578
        $pton = @inet_pton($host);
579 View Code Duplication
        if ($pton === false) { // dirty check
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...
580
            \PHPDaemon\Clients\DNS\Pool::getInstance()->resolve($host, function ($result) use ($host, $port) {
581
                if (!$result) {
582
                    Daemon::log(get_class($this) . '->connectUdp : unable to resolve hostname: ' . $host);
583
                    $this->onStateEv($this->bev, \EventBufferEvent::ERROR);
584
                    return;
585
                }
586
                // @todo stack of addrs
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
587
                if (is_array($result)) {
588
                    srand(Daemon::$process->getPid());
589
                    $real = $result[rand(0, sizeof($result) - 1)];
590
                    srand();
591
                } else {
592
                    $real = $result;
593
                }
594
                $this->connectUdp($real, $port);
595
            });
596
            return true;
597
        }
598
        $this->hostReal = $host;
599
        if ($this->host === null) {
600
            $this->host = $this->hostReal;
601
        }
602
        $l = mb_orig_strlen($pton);
603
        if ($l === 4) {
604
            $this->addr = $host . ':' . $port;
605
            /* @TODO: use EventUtil::SOCK_DGRAM */
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
606
            $fd = socket_create(\EventUtil::AF_INET, SOCK_DGRAM, \EventUtil::SOL_UDP);
607
        } elseif ($l === 16) {
608
            $this->addr = '[' . $host . ']:' . $port;
609
            $fd = socket_create(\EventUtil::AF_INET6, SOCK_DGRAM, \EventUtil::SOL_UDP);
610
        } else {
611
            return false;
612
        }
613
        if (!$fd) {
614
            return false;
615
        }
616
        socket_set_nonblock($fd);
617
        @socket_connect($fd, $host, $port);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
618
        socket_getsockname($fd, $this->locAddr, $this->locPort);
619
        $this->setFd($fd);
620
        if (!$this->bev) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) $this->bev;.
Loading history...
621
            return false;
622
        }
623
        return true;
624
    }
625
626
    /**
627
     * Establish TCP connection
628
     * @param  string $host Hostname
629
     * @param  integer $port Port
630
     * @return boolean       Success
631
     */
632
    public function connectTcp($host, $port)
633
    {
634
        $this->type = 'tcp';
635
        $pton = @inet_pton($host);
636
        $fd = null;
637 View Code Duplication
        if ($pton === false) { // dirty check
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...
638
            \PHPDaemon\Clients\DNS\Pool::getInstance()->resolve($this->host, function ($result) use ($host, $port) {
639
                if (!$result) {
640
                    Daemon::log(get_class($this) . '->connectTcp : unable to resolve hostname: ' . $host);
641
                    $this->onStateEv($this->bev, \EventBufferEvent::ERROR);
642
                    return;
643
                }
644
                // @todo stack of addrs
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
645
                if (is_array($result)) {
646
                    if (!sizeof($result)) {
647
                        return;
648
                    }
649
                    srand(Daemon::$process->getPid());
650
                    $real = $result[rand(0, sizeof($result) - 1)];
651
                    srand();
652
                } else {
653
                    $real = $result;
654
                }
655
                $this->connectTcp($real, $port);
656
            });
657
            return true;
658
        }
659
        $this->hostReal = $host;
660
        if ($this->host === null) {
661
            $this->host = $this->hostReal;
662
        }
663
        // TCP
664
        $l = mb_orig_strlen($pton);
665
        if ($l === 4) {
666
            $this->addr = $host . ':' . $port;
667
            if (!$this->bevConnectEnabled) {
668
                $fd = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
669
            }
670
        } elseif ($l === 16) {
671
            $this->addr = '[' . $host . ']:' . $port;
672
            if (!$this->bevConnectEnabled) {
673
                $fd = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
674
            }
675
        } else {
676
            return false;
677
        }
678
        if (!$this->bevConnectEnabled && !$fd) {
679
            return false;
680
        }
681
        if (!$this->bevConnectEnabled) {
682
            socket_set_nonblock($fd);
683
        }
684
        if (!$this->bevConnectEnabled) {
685
            $this->fd = $fd;
686
            $this->setTimeouts(
687
                $this->timeoutRead !== null ? $this->timeoutRead : $this->timeout,
688
                $this->timeoutWrite !== null ? $this->timeoutWrite : $this->timeout
689
            );
690
            socket_connect($fd, $host, $port);
691
            socket_getsockname($fd, $this->locAddr, $this->locPort);
692
        } else {
693
            $this->bevConnect = true;
694
        }
695
        $this->setFd($fd);
0 ignored issues
show
Bug introduced by
It seems like $fd defined by null on line 636 can also be of type null; however, PHPDaemon\Network\IOStream::setFd() does only seem to accept resource, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
696
        if (!$this->bev) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) $this->bev;.
Loading history...
697
            return false;
698
        }
699
        return true;
700
    }
701
702
    /**
703
     * Set keepalive
704
     * @param  boolean $bool
705
     * @return void
706
     */
707
    public function setKeepalive($bool)
708
    {
709
        $this->keepalive = (bool)$bool;
710
        $this->setOption(\EventUtil::SOL_SOCKET, \EventUtil::SO_KEEPALIVE, $this->keepalive ? true : false);
711
    }
712
713
    /**
714
     * Close the connection
715
     * @return void
716
     */
717
    public function close()
718
    {
719
        parent::close();
720
        if (is_resource($this->fd)) {
721
            socket_close($this->fd);
722
        }
723
    }
724
725
    /**
726
     * Set timeouts
727
     * @param  integer $read Read timeout in seconds
728
     * @param  integer $write Write timeout in seconds
729
     * @return void
730
     */
731
    public function setTimeouts($read, $write)
732
    {
733
        parent::setTimeouts($read, $write);
734
        if ($this->fd !== null) {
735
            $this->setOption(
736
                \EventUtil::SOL_SOCKET,
737
                \EventUtil::SO_SNDTIMEO,
738
                ['sec' => $this->timeoutWrite, 'usec' => 0]
739
            );
740
            $this->setOption(
741
                \EventUtil::SOL_SOCKET,
742
                \EventUtil::SO_RCVTIMEO,
743
                ['sec' => $this->timeoutRead, 'usec' => 0]
744
            );
745
        }
746
    }
747
748
    /**
749
     * Set socket option
750
     * @param  integer $level Level
751
     * @param  integer $optname Option
752
     * @param  mixed $val Value
753
     * @return void
754
     */
755
    public function setOption($level, $optname, $val)
756
    {
757
        if (is_resource($this->fd)) {
758
            socket_set_option($this->fd, $level, $optname, $val);
759
        } else {
760
            \EventUtil::setSocketOption($this->fd, $level, $optname, $val);
761
        }
762
    }
763
764
    /**
765
     * Called when connection finishes
766
     * @return void
767
     */
768
    public function onFinish()
769
    {
770
        if (!$this->connected) {
771
            if ($this->onConnected) {
772
                $this->onConnected->executeAll($this);
773
                $this->onConnected = null;
774
            }
775
        }
776
        parent::onFinish();
777
    }
778
}
779