Port   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 2
dl 0
loc 245
rs 9.68
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A for() 0 4 1
A __construct() 0 4 1
A sanitizeDomain() 0 8 2
A getDomain() 0 4 1
A getDefaultPorts() 0 4 1
A getProtocols() 0 4 1
A addResult() 0 8 1
A check() 0 18 2
A getUri() 0 4 1
A checkPort() 0 16 3
A checkNonUdpPort() 0 13 2
A checkUdpPort() 0 34 4
A useTcp() 0 6 1
A useTls() 0 6 1
A useUdp() 0 6 1
A useSsl() 0 6 1
A setTimeout() 0 6 1
A setProtocol() 0 4 1
A addPortWithProtocol() 0 4 1
B resolvePorts() 0 25 7
1
<?php
2
3
namespace Gemz\Port;
4
5
use React\Promise\Deferred;
6
use React\Promise\PromiseInterface;
7
use React\Socket\Connector;
8
use React\EventLoop\Factory;
9
use React\EventLoop\LoopInterface;
10
use React\Socket\ConnectionInterface;
11
use Gemz\Port\Exceptions\InvalidArgument;
12
13
class Port
14
{
15
    const PROTOCOL_TCP = 'tcp';
16
    const PROTOCOL_UDP = 'udp';
17
    const PROTOCOL_SSL = 'ssl';
18
    const PROTOCOL_TLS = 'tls';
19
20
    /** @var string */
21
    protected $domain;
22
23
    /** @var string */
24
    protected $protocol = self::PROTOCOL_TCP;
25
26
    /** @var float */
27
    protected $timeout = 0.25;
28
29
    /** @var array */
30
    protected $protocols = ['tcp', 'tls', 'udp', 'ssl'];
31
32
    /**
33
     * @var array
34
     */
35
    protected $defaultPorts = [
36
        22 => 'tcp',
37
        80 => 'tcp',
38
        443 => 'tls',
39
        8080 => 'tcp',
40
        3306 => 'tcp',
41
    ];
42
43
    /** @var array */
44
    protected $result = [];
45
46
    /** @var array */
47
    protected $portsWithProtocol = [];
48
49
    public static function for(string $domain): self
50
    {
51
        return new self($domain);
52
    }
53
54
    public function __construct(string $domain)
55
    {
56
        $this->domain = $this->sanitizeDomain($domain);
57
    }
58
59
    protected function sanitizeDomain(string $domain): string
60
    {
61
        if (empty($domain)) {
62
            throw InvalidArgument::domainIsNotValid($domain);
63
        }
64
65
        return str_replace(['http://', 'https://'], '', $domain);
66
    }
67
68
    public function getDomain(): string
69
    {
70
        return $this->domain;
71
    }
72
73
    public function getDefaultPorts(): array
74
    {
75
        return $this->defaultPorts;
76
    }
77
78
    public function getProtocols(): array
79
    {
80
        return $this->protocols;
81
    }
82
83
    protected function addResult(int $port, string $protocol, bool $isOpen): void
84
    {
85
        array_push($this->result, [
86
           'port' => $port,
87
           'protocol' => $protocol,
88
           'open' => $isOpen
89
        ]);
90
    }
91
92
    /**
93
     * @param array|int ...$ports
94
     *
95
     * @return array
96
     */
97
    public function check(...$ports): array
98
    {
99
        $this->resolvePorts($ports);
100
101
        foreach ($this->portsWithProtocol as $port => $protocol) {
102
            $this->checkPort($port, $protocol)
103
                ->then(
104
                    function () use ($port, $protocol) {
105
                        $this->addResult($port, $protocol, true);
106
                    },
107
                    function () use ($port, $protocol) {
108
                        $this->addResult($port, $protocol, false);
109
                    }
110
                );
111
        }
112
113
        return $this->result;
114
    }
115
116
    protected function getUri(string $protocol): string
117
    {
118
        return "{$protocol}://{$this->domain}";
119
    }
120
121
    protected function checkPort(int $port, string $protocol): PromiseInterface
122
    {
123
        $deferred = new Deferred();
124
125
        $portIsOpen = ($protocol == self::PROTOCOL_UDP)
126
            ? $this->checkUdpPort($port)
127
            : $portIsOpen = $this->checkNonUdpPort($port, $protocol);
128
129
        if ($portIsOpen) {
130
            $deferred->resolve();
131
        } else {
132
            $deferred->reject();
133
        }
134
135
        return $deferred->promise();
136
    }
137
138
    protected function checkNonUdpPort(int $port, string $protocol): bool
139
    {
140
        $handler = @fsockopen(
141
            $this->getUri($protocol), $port, $errno, $errstr, $this->timeout
142
        );
143
144
        if (is_resource($handler)) {
145
            fclose($handler);
146
            return true;
147
        }
148
149
        return false;
150
    }
151
152
    protected function checkUdpPort($port): bool
153
    {
154
        $handler = @fsockopen(
155
            $this->getUri(self::PROTOCOL_UDP), $port, $errno, $errstr, $this->timeout
156
        );
157
158
        if (! $handler) {
159
            return false;
160
        }
161
162
        socket_set_timeout($handler, $this->timeout);
163
164
        $write = fwrite($handler, "x00");
165
166
        if (! $write) {
167
            return false;
168
        }
169
170
        $startTime = time();
171
172
        fread($handler, 1);
173
174
        $endTime = time();
175
176
        $timeDiff = $endTime - $startTime;
177
178
        fclose($handler);
179
180
        if ($timeDiff >= $this->timeout) {
181
            return true;
182
        }
183
184
        return false;
185
    }
186
187
    public function useTcp(): self
188
    {
189
        $this->setProtocol(self::PROTOCOL_TCP);
190
191
        return $this;
192
    }
193
194
    public function useTls(): self
195
    {
196
        $this->setProtocol(self::PROTOCOL_TLS);
197
198
        return $this;
199
    }
200
201
    public function useUdp(): self
202
    {
203
        $this->setProtocol(self::PROTOCOL_UDP);
204
205
        return $this;
206
    }
207
208
    public function useSsl(): self
209
    {
210
        $this->setProtocol(self::PROTOCOL_SSL);
211
212
        return $this;
213
    }
214
215
    public function setTimeout(float $seconds): self
216
    {
217
        $this->timeout = $seconds;
218
219
        return $this;
220
    }
221
222
    protected function setProtocol(string $protocol): void
223
    {
224
        $this->protocol = $protocol;
225
    }
226
227
    protected function addPortWithProtocol(int $port, string $protocol): void
228
    {
229
        $this->portsWithProtocol[$port] = $protocol;
230
    }
231
232
    protected function resolvePorts(array $ports = []): void
233
    {
234
        if (empty($ports)) {
235
            $ports = $this->defaultPorts;
236
        }
237
238
        $ports = is_array($ports[0] ?? null)
239
            ? $ports[0]
240
            : $ports;
241
242
        foreach ($ports as $port => $protocol) {
243
244
            if (in_array($protocol, $this->protocols) && is_int($port)) {
245
                $this->addPortWithProtocol($port, $protocol);
246
                continue;
247
            }
248
249
            if (is_int($protocol)) {
250
                $this->addPortWithProtocol($protocol, $this->protocol);
251
                continue;
252
            }
253
254
            throw InvalidArgument::portOrProtocolIsNotValid($port, $protocol);
255
        }
256
    }
257
}
258