Connection::getSocketName()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
nc 2
nop 2
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 (\Throwable $e) {
0 ignored issues
show
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...
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 . '\'');
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
        
383
        $params = [
384
            \EventSslContext::OPT_VERIFY_PEER => $this->verifypeer,
385
            \EventSslContext::OPT_ALLOW_SELF_SIGNED => $this->allowselfsigned,
386
        ];
387
        if ($this->certfile !== null) {
388
            $params[\EventSslContext::OPT_LOCAL_CERT] = $this->certfile;
389
        }
390
        if ($this->pkfile !== null) {
391
            $params[\EventSslContext::OPT_LOCAL_PK] = $this->pkfile;
392
        }
393
        if ($this->passphrase !== null) {
394
            $params[\EventSslContext::OPT_PASSPHRASE] = $this->passphrase;
395
        }
396
        $hash = igbinary_serialize($params);
397
        if (!self::$contextCache) {
398
            self::$contextCache = new CappedStorageHits(self::$contextCacheSize);
399
        } elseif ($ctx = self::$contextCache->getValue($hash)) {
400
            return $ctx;
401
        }
402
        $ctx = new \EventSslContext(\EventSslContext::TLS_CLIENT_METHOD, $params);
403
        self::$contextCache->put($hash, $ctx);
404
        return $ctx;
405
    }
406
407
    /**
408
     * Get URL
409
     * @return string
410
     */
411
    public function getUrl()
412
    {
413
        return $this->url;
414
    }
415
416
    /**
417
     * Get host
418
     * @return string
419
     */
420
    public function getHost()
421
    {
422
        return $this->host;
423
    }
424
425
    /**
426
     * Get port
427
     * @return integer
428
     */
429
    public function getPort()
430
    {
431
        return $this->port;
432
    }
433
434
    /**
435
     * Connects to URL
436
     * @param  string $url URL
437
     * @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...
438
     * @param  \Closure $beforeConnect Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $beforeConnect not be null|\Closure?

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...
439
     * @return boolean       Success
440
     */
441
    public function connect($url, $cb = null, \Closure $beforeConnect = null)
442
    {
443
        $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...
444
        $u =& $this->uri;
445
        if (!$u) {
446
            return false;
447
        }
448
        if ($beforeConnect !== null) {
449
            $beforeConnect->call($this);
450
        }
451
        $this->importParams();
452
        if (!isset($u['port'])) {
453
            if ($this->ssl) {
454 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...
455
                    $u['port'] = $this->pool->config->sslport->value;
456
                }
457 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...
458
                if (isset($this->pool->config->port->value)) {
459
                    $u['port'] = $this->pool->config->port->value;
460
                }
461
            }
462
        }
463
        if (isset($u['user'])) {
464
            $this->user = $u['user'];
465
        }
466
467
        if ($this->ssl) {
468
            $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...
469
        }
470
471
        $this->url = $url;
472
        $this->scheme = strtolower($u['scheme']);
473
        $this->host = isset($u['host']) ? $u['host'] : null;
474
        $this->port = isset($u['port']) ? $u['port'] : 0;
475
476
        if (isset($u['pass'])) {
477
            $this->password = $u['pass'];
478
        }
479
480
        if (isset($u['path'])) {
481
            $this->path = ltrim($u['path'], '/');
482
        }
483
484
        if ($cb !== null) {
485
            $this->onConnected($cb);
486
        }
487
488
        if ($this->scheme === 'unix') {
489
            return $this->connectUnix($u['path']);
490
        }
491
        if ($this->scheme === 'raw') {
492
            return $this->connectRaw($u['host']);
493
        }
494
        if ($this->scheme === 'udp') {
495
            return $this->connectUdp($this->host, $this->port);
496
        }
497
        if ($this->scheme === 'tcp') {
498
            return $this->connectTcp($this->host, $this->port);
499
        }
500
        Daemon::log(get_class($this) . ': connect(): unrecoginized scheme \'' . $this->scheme . '\' (not unix/raw/udp/tcp) in URL: ' . $url);
501
        return false;
502
    }
503
504
    /**
505
     * Establish UNIX socket connection
506
     * @param  string $path Path
507
     * @return boolean       Success
508
     */
509
    public function connectUnix($path)
510
    {
511
        $this->type = 'unix';
512
513
        if (!$this->bevConnectEnabled) {
514
            $fd = socket_create(AF_UNIX, SOCK_STREAM, 0);
515
            if (!$fd) {
516
                return false;
517
            }
518
            socket_set_nonblock($fd);
519
            @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...
520
            $this->setFd($fd);
521
            return true;
522
        }
523
        $this->bevConnect = true;
524
        $this->addr = 'unix:' . $path;
525
        $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...
526
        return true;
527
    }
528
529
    /**
530
     * Establish raw socket connection
531
     * @param  string $host Hostname
532
     * @return boolean       Success
533
     */
534
    public function connectRaw($host)
535
    {
536
        $this->type = 'raw';
537
        if (@inet_pton($host) === false) { // dirty check
538
            \PHPDaemon\Clients\DNS\Pool::getInstance()->resolve($host, function ($result) use ($host) {
539
                if ($result === false) {
540
                    Daemon::log(get_class($this) . '->connectRaw : unable to resolve hostname: ' . $host);
541
                    $this->onFailureEv();
542
                    return;
543
                }
544
                // @TODO stack of addrs
545
                if (is_array($result)) {
546
                    srand(Daemon::$process->getPid());
547
                    $real = $result[rand(0, sizeof($result) - 1)];
548
                    srand();
549
                } else {
550
                    $real = $result;
551
                }
552
                $this->connectRaw($real);
553
            });
554
            return true;
555
        }
556
        $this->hostReal = $host;
557
        if ($this->host === null) {
558
            $this->host = $this->hostReal;
559
        }
560
        $this->addr = $this->hostReal . ':raw';
561
        $fd = socket_create(\EventUtil::AF_INET, \EventUtil::SOCK_RAW, 1);
562
        if (!$fd) {
563
            return false;
564
        }
565
        socket_set_nonblock($fd);
566
        @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...
567
        $this->setFd($fd);
568
        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...
569
            return false;
570
        }
571
        return true;
572
    }
573
574
    /**
575
     * Establish UDP connection
576
     * @param  string $host Hostname
577
     * @param  integer $port Port
578
     * @return boolean       Success
579
     */
580
    public function connectUdp($host, $port)
581
    {
582
        $this->type = 'udp';
583
        $pton = @inet_pton($host);
584 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...
585
            \PHPDaemon\Clients\DNS\Pool::getInstance()->resolve($host, function ($result) use ($host, $port) {
586
                if (!$result) {
587
                    Daemon::log(get_class($this) . '->connectUdp : unable to resolve hostname: ' . $host);
588
                    $this->onStateEv($this->bev, \EventBufferEvent::ERROR);
589
                    return;
590
                }
591
                // @todo stack of addrs
592
                if (is_array($result)) {
593
                    srand(Daemon::$process->getPid());
594
                    $real = $result[rand(0, sizeof($result) - 1)];
595
                    srand();
596
                } else {
597
                    $real = $result;
598
                }
599
                $this->connectUdp($real, $port);
600
            });
601
            return true;
602
        }
603
        $this->hostReal = $host;
604
        if ($this->host === null) {
605
            $this->host = $this->hostReal;
606
        }
607
        $l = mb_orig_strlen($pton);
608
        if ($l === 4) {
609
            $this->addr = $host . ':' . $port;
610
            /* @TODO: use EventUtil::SOCK_DGRAM */
611
            $fd = socket_create(\EventUtil::AF_INET, SOCK_DGRAM, \EventUtil::SOL_UDP);
612
        } elseif ($l === 16) {
613
            $this->addr = '[' . $host . ']:' . $port;
614
            $fd = socket_create(\EventUtil::AF_INET6, SOCK_DGRAM, \EventUtil::SOL_UDP);
615
        } else {
616
            return false;
617
        }
618
        if (!$fd) {
619
            return false;
620
        }
621
        socket_set_nonblock($fd);
622
        @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...
623
        socket_getsockname($fd, $this->locAddr, $this->locPort);
624
        $this->setFd($fd);
625
        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...
626
            return false;
627
        }
628
        return true;
629
    }
630
631
    /**
632
     * Establish TCP connection
633
     * @param  string $host Hostname
634
     * @param  integer $port Port
635
     * @return boolean       Success
636
     */
637
    public function connectTcp($host, $port)
638
    {
639
        $this->type = 'tcp';
640
        $pton = @inet_pton($host);
641
        $fd = null;
642 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...
643
            \PHPDaemon\Clients\DNS\Pool::getInstance()->resolve($this->host, function ($result) use ($host, $port) {
644
                if (!$result) {
645
                    Daemon::log(get_class($this) . '->connectTcp : unable to resolve hostname: ' . $host);
646
                    $this->onStateEv($this->bev, \EventBufferEvent::ERROR);
647
                    return;
648
                }
649
                // @todo stack of addrs
650
                if (is_array($result)) {
651
                    if (!sizeof($result)) {
652
                        return;
653
                    }
654
                    srand(Daemon::$process->getPid());
655
                    $real = $result[rand(0, sizeof($result) - 1)];
656
                    srand();
657
                } else {
658
                    $real = $result;
659
                }
660
                $this->connectTcp($real, $port);
661
            });
662
            return true;
663
        }
664
        $this->hostReal = $host;
665
        if ($this->host === null) {
666
            $this->host = $this->hostReal;
667
        }
668
        // TCP
669
        $l = mb_orig_strlen($pton);
670
        if ($l === 4) {
671
            $this->addr = $host . ':' . $port;
672
            if (!$this->bevConnectEnabled) {
673
                $fd = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
674
            }
675
        } elseif ($l === 16) {
676
            $this->addr = '[' . $host . ']:' . $port;
677
            if (!$this->bevConnectEnabled) {
678
                $fd = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
679
            }
680
        } else {
681
            return false;
682
        }
683
        if (!$this->bevConnectEnabled && !$fd) {
684
            return false;
685
        }
686
        if (!$this->bevConnectEnabled) {
687
            socket_set_nonblock($fd);
688
        }
689
        if (!$this->bevConnectEnabled) {
690
            $this->fd = $fd;
691
            $this->setTimeouts(
692
                $this->timeoutRead !== null ? $this->timeoutRead : $this->timeout,
693
                $this->timeoutWrite !== null ? $this->timeoutWrite : $this->timeout
694
            );
695
            socket_connect($fd, $host, $port);
696
            socket_getsockname($fd, $this->locAddr, $this->locPort);
697
        } else {
698
            $this->bevConnect = true;
699
        }
700
        $this->setFd($fd);
0 ignored issues
show
Bug introduced by
It seems like $fd defined by null on line 641 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...
701
        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...
702
            return false;
703
        }
704
        return true;
705
    }
706
707
    /**
708
     * Set keepalive
709
     * @param  boolean $bool
710
     * @return void
711
     */
712
    public function setKeepalive($bool)
713
    {
714
        $this->keepalive = (bool)$bool;
715
        $this->setOption(\EventUtil::SOL_SOCKET, \EventUtil::SO_KEEPALIVE, $this->keepalive ? true : false);
716
    }
717
718
    /**
719
     * Close the connection
720
     * @return void
721
     */
722
    public function close()
723
    {
724
        parent::close();
725
        if (is_resource($this->fd)) {
726
            socket_close($this->fd);
727
        }
728
    }
729
730
    /**
731
     * Set timeouts
732
     * @param  integer $read Read timeout in seconds
733
     * @param  integer $write Write timeout in seconds
734
     * @return void
735
     */
736
    public function setTimeouts($read, $write)
737
    {
738
        parent::setTimeouts($read, $write);
739
        if ($this->fd !== null) {
740
            $this->setOption(
741
                \EventUtil::SOL_SOCKET,
742
                \EventUtil::SO_SNDTIMEO,
743
                ['sec' => $this->timeoutWrite, 'usec' => 0]
744
            );
745
            $this->setOption(
746
                \EventUtil::SOL_SOCKET,
747
                \EventUtil::SO_RCVTIMEO,
748
                ['sec' => $this->timeoutRead, 'usec' => 0]
749
            );
750
        }
751
    }
752
753
    /**
754
     * Set socket option
755
     * @param  integer $level Level
756
     * @param  integer $optname Option
757
     * @param  mixed $val Value
758
     * @return void
759
     */
760
    public function setOption($level, $optname, $val)
761
    {
762
        if (is_resource($this->fd)) {
763
            socket_set_option($this->fd, $level, $optname, $val);
764
        } else {
765
            \EventUtil::setSocketOption($this->fd, $level, $optname, $val);
766
        }
767
    }
768
769
    /**
770
     * Called when connection finishes
771
     * @return void
772
     */
773
    public function onFinish()
774
    {
775
        if (!$this->connected) {
776
            if ($this->onConnected) {
777
                $this->onConnected->executeAll($this);
778
                $this->onConnected = null;
779
            }
780
        }
781
        parent::onFinish();
782
    }
783
}
784