1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Soluble\Japha\Bridge\Socket; |
6
|
|
|
|
7
|
|
|
use Soluble\Japha\Bridge\Exception\ConnectionException; |
8
|
|
|
use Soluble\Japha\Bridge\Exception\InvalidArgumentException; |
9
|
|
|
|
10
|
|
|
class StreamSocket implements StreamSocketInterface |
11
|
|
|
{ |
12
|
|
|
public const TRANSPORT_SSL = 'ssl'; |
13
|
|
|
public const TRANSPORT_TCP = 'tcp'; |
14
|
|
|
|
15
|
|
|
public const REGISTERED_TRANSPORTS = [ |
16
|
|
|
self::TRANSPORT_SSL, |
17
|
|
|
self::TRANSPORT_TCP |
18
|
|
|
]; |
19
|
|
|
|
20
|
|
|
public const DEFAULT_CONTEXT = [ |
21
|
|
|
'http' => [ |
22
|
|
|
'protocol_version' => '1.1', |
23
|
|
|
] |
24
|
|
|
]; |
25
|
|
|
|
26
|
|
|
public const DEFAULT_CONNECT_TIMEOUTS = [ |
27
|
|
|
'HOST_127.0.0.1' => 5.0, |
28
|
|
|
'HOST_localhost' => 5.0, |
29
|
|
|
'DEFAULT' => 20.0 |
30
|
|
|
]; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var resource |
34
|
|
|
*/ |
35
|
|
|
protected $socket; |
36
|
|
|
|
37
|
|
|
protected $persistent = false; |
38
|
|
|
protected $address; |
39
|
|
|
protected $connectTimeout; |
40
|
|
|
protected $streamContext; |
41
|
|
|
protected $transport; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param string $transport tcp, ssl... see self::TRANSPORT |
45
|
|
|
* @param string $address ip:port |
46
|
32 |
|
* @param float $connectTimeout connection timeout in seconds (double: i.e: 5.0) |
47
|
|
|
* @param mixed[] $streamContext see stream_context_create() |
48
|
|
|
* @param bool $persistent whether to use persistent connections |
49
|
|
|
*/ |
50
|
|
|
public function __construct( |
51
|
|
|
string $transport, |
52
|
|
|
string $address, |
53
|
32 |
|
float $connectTimeout = null, |
54
|
31 |
|
array $streamContext = self::DEFAULT_CONTEXT, |
55
|
31 |
|
bool $persistent = false |
56
|
31 |
|
) { |
57
|
31 |
|
$this->setTransport($transport); |
58
|
31 |
|
$this->address = $address; |
59
|
28 |
|
$this->setConnectTimeout($connectTimeout); |
60
|
|
|
$this->streamContext = $streamContext; |
61
|
|
|
$this->persistent = $persistent; |
62
|
|
|
$this->createSocket(); |
63
|
|
|
} |
64
|
32 |
|
|
65
|
|
|
protected function setConnectTimeout(float $timeout = null): void |
66
|
32 |
|
{ |
67
|
1 |
|
if ($timeout === null) { |
68
|
1 |
|
list($host) = explode(':', $this->address); |
69
|
1 |
|
if (array_key_exists("HOST_$host", self::DEFAULT_CONNECT_TIMEOUTS)) { |
70
|
1 |
|
$timeout = self::DEFAULT_CONNECT_TIMEOUTS["HOST_$host"]; |
71
|
|
|
} else { |
72
|
|
|
$timeout = self::DEFAULT_CONNECT_TIMEOUTS['DEFAULT']; |
73
|
31 |
|
} |
74
|
31 |
|
} |
75
|
|
|
$this->connectTimeout = $timeout; |
76
|
31 |
|
} |
77
|
|
|
|
78
|
31 |
|
/** |
79
|
|
|
* @throws InvalidArgumentException when getting an unsupported transport |
80
|
|
|
*/ |
81
|
|
|
protected function setTransport(string $transport): void |
82
|
|
|
{ |
83
|
|
|
if (!in_array($transport, self::REGISTERED_TRANSPORTS, true)) { |
84
|
26 |
|
throw new InvalidArgumentException(sprintf( |
85
|
|
|
'Unsupported transport "%s" given (supported: %s)', |
86
|
26 |
|
$transport, |
87
|
|
|
implode(',', array_keys(self::REGISTERED_TRANSPORTS)) |
88
|
|
|
)); |
89
|
31 |
|
} |
90
|
|
|
$this->transport = $transport; |
91
|
31 |
|
} |
92
|
|
|
|
93
|
|
|
public function getTransport(): string |
94
|
|
|
{ |
95
|
|
|
return $this->transport; |
96
|
|
|
} |
97
|
31 |
|
|
98
|
|
|
public function getConnectTimeout(): float |
99
|
31 |
|
{ |
100
|
31 |
|
return $this->connectTimeout; |
101
|
31 |
|
} |
102
|
31 |
|
|
103
|
31 |
|
/** |
104
|
31 |
|
* @return resource php socket |
105
|
31 |
|
*/ |
106
|
|
|
public function getSocket() |
107
|
31 |
|
{ |
108
|
28 |
|
return $this->socket; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
public function getStreamAddress(): string |
112
|
|
|
{ |
113
|
|
|
return sprintf('%s://%s', $this->getTransport(), $this->address); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
31 |
|
* Whether the connection is persistent or not. |
118
|
|
|
* |
119
|
31 |
|
* @return bool |
120
|
3 |
|
*/ |
121
|
3 |
|
public function isPersistent(): bool |
122
|
3 |
|
{ |
123
|
3 |
|
return $this->persistent; |
124
|
3 |
|
} |
125
|
|
|
|
126
|
3 |
|
/** |
127
|
|
|
* @throws ConnectionException |
128
|
28 |
|
*/ |
129
|
|
|
protected function createSocket(): void |
130
|
31 |
|
{ |
131
|
|
|
$this->socket = @stream_socket_client( |
|
|
|
|
132
|
31 |
|
$this->getStreamAddress(), |
133
|
|
|
$errno, |
134
|
|
|
$errstr, |
135
|
|
|
$this->connectTimeout, |
136
|
|
|
$this->getStreamFlags(), |
137
|
|
|
$this->getStreamContext() |
138
|
31 |
|
); |
139
|
|
|
$this->checkSocket($this->socket, $errno, $errstr); |
140
|
31 |
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @param resource|false $socket |
144
|
|
|
* @param int|null $errno |
145
|
|
|
* @param string|null $errstr |
146
|
|
|
* |
147
|
|
|
* @throws ConnectionException |
148
|
|
|
*/ |
149
|
|
|
protected function checkSocket($socket, int $errno = null, string $errstr = null): void |
150
|
|
|
{ |
151
|
|
|
if ($socket === false || !is_resource($socket)) { |
152
|
|
|
$msg = sprintf( |
153
|
|
|
"Could not connect to the php-java-bridge server '%s'. Please start it. (err: %s, %s)", |
154
|
|
|
$this->address, |
155
|
|
|
$errno ?? 0, |
156
|
|
|
$errstr ?? 'Empty errstr returned' |
157
|
|
|
); |
158
|
|
|
throw new ConnectionException($msg); |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
protected function getStreamFlags(): int |
163
|
|
|
{ |
164
|
|
|
return $this->persistent ? STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT : STREAM_CLIENT_CONNECT; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @return resource |
169
|
|
|
*/ |
170
|
|
|
protected function getStreamContext() |
171
|
|
|
{ |
172
|
|
|
return stream_context_create($this->streamContext); |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
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 theid
property of an instance of theAccount
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.