Complex classes like WebSocketServer 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 WebSocketServer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class WebSocketServer implements WebSocketServerContract, CommonsContract |
||
19 | { |
||
20 | |||
21 | private $clients = []; |
||
22 | // set any template You need ex.: GET /subscription/messenger/token |
||
23 | private $pathParams = []; |
||
24 | private $config; |
||
25 | private $handshakes = []; |
||
26 | private $headersUpgrade = []; |
||
27 | private $totalClients = 0; |
||
28 | private $maxClients = 1; |
||
29 | private $handler; |
||
30 | private $cureentConn; |
||
31 | |||
32 | // for the very 1st time must be true |
||
33 | private $stepRecursion = true; |
||
34 | |||
35 | const MAX_BYTES_READ = 8192; |
||
36 | const HEADER_BYTES_READ = 1024; |
||
37 | |||
38 | // stream non-blocking |
||
39 | const NON_BLOCK = 0; |
||
40 | const PROC_TITLE = 'php-wss'; |
||
41 | |||
42 | /** |
||
43 | * WebSocketServer constructor. |
||
44 | * |
||
45 | * @param WebSocket $handler |
||
46 | * @param ServerConfig $config |
||
47 | */ |
||
48 | public function __construct( |
||
57 | |||
58 | /** |
||
59 | * Runs main process - Anscestor with server socket on TCP |
||
60 | * |
||
61 | * @throws WebSocketException |
||
62 | */ |
||
63 | public function run() |
||
77 | |||
78 | /** |
||
79 | * Recursive event loop that input intu recusion by remainder = 0 - thus when N users, |
||
80 | * and when forks equals true which prevents it from infinite recursive iterations |
||
81 | * |
||
82 | * @param resource $server server connection |
||
83 | * @param bool $fork flag to fork or run event loop |
||
84 | */ |
||
85 | private function eventLoop($server, bool $fork = false) |
||
98 | |||
99 | /** |
||
100 | * @param resource $server |
||
101 | */ |
||
102 | private function looping($server) |
||
103 | { |
||
104 | while (true) { |
||
105 | $this->totalClients = count($this->clients) + 1; |
||
106 | |||
107 | // maxClients prevents process fork on count down |
||
108 | if ($this->totalClients > $this->maxClients) { |
||
109 | $this->maxClients = $this->totalClients; |
||
110 | } |
||
111 | |||
112 | if ($this->config->isForking() === true |
||
113 | && $this->totalClients !== 0 // avoid 0 process creation |
||
114 | && true === $this->stepRecursion // only once |
||
115 | && $this->maxClients === $this->totalClients // only if stack grows |
||
116 | && $this->totalClients % $this->config->getClientsPerFork() === 0 // only when N is there |
||
117 | ) { |
||
118 | $this->stepRecursion = false; |
||
119 | $this->eventLoop($server, true); |
||
120 | } |
||
121 | |||
122 | if ($this->totalClients !== 0 && $this->maxClients > $this->totalClients |
||
123 | && $this->totalClients % $this->config->getClientsPerFork() === 0) { // there is less connection for amount of processes at this moment |
||
124 | exit(1); |
||
125 | } |
||
126 | |||
127 | //prepare readable sockets |
||
128 | $readSocks = $this->clients; |
||
129 | $readSocks[] = $server; |
||
130 | |||
131 | // clear socket resources that were closed, thus avoiding (stream_select(): supplied resource is not a valid stream resource) |
||
132 | foreach ($readSocks as $k => $sock) { |
||
133 | if (!is_resource($sock)) { |
||
134 | unset($readSocks[$k]); |
||
135 | } |
||
136 | } |
||
137 | |||
138 | //start reading and use a large timeout |
||
139 | if (!stream_select($readSocks, $write, $except, $this->config->getStreamSelectTimeout())) { |
||
140 | die('something went wrong while selecting'); |
||
141 | } |
||
142 | |||
143 | //new client |
||
144 | if (in_array($server, $readSocks, false)) { |
||
145 | $this->acceptNewClient($server, $readSocks); |
||
146 | } |
||
147 | |||
148 | //message from existing client |
||
149 | $this->messagesWorker($readSocks); |
||
150 | } |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * @param resource $server |
||
155 | * @param array $readSocks |
||
156 | */ |
||
157 | private function acceptNewClient($server, array &$readSocks) |
||
158 | { |
||
159 | $newClient = stream_socket_accept($server, 0); // must be 0 to non-block |
||
160 | if ($newClient) { |
||
161 | |||
162 | // important to read from headers here coz later client will change and there will be only msgs on pipe |
||
163 | $headers = fread($newClient, self::HEADER_BYTES_READ); |
||
164 | if (empty($this->handler->pathParams[0]) === false) { |
||
165 | $this->setPathParams($headers); |
||
166 | } |
||
167 | |||
168 | $this->clients[] = $newClient; |
||
169 | $this->stepRecursion = true; // set on new client - remainder % is always 0 |
||
170 | |||
171 | // trigger OPEN event |
||
172 | $this->handler->onOpen(new Connection($newClient, $this->clients)); |
||
173 | $this->handshake($newClient, $headers); |
||
174 | } |
||
175 | |||
176 | //delete the server socket from the read sockets |
||
177 | unset($readSocks[array_search($server, $readSocks, false)]); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * @uses onMessage |
||
182 | * @uses onPing |
||
183 | * @uses onPong |
||
184 | * @param array $readSocks |
||
185 | */ |
||
186 | private function messagesWorker(array $readSocks) |
||
218 | |||
219 | /** |
||
220 | * Message frames decoder |
||
221 | * |
||
222 | * @param string $data |
||
223 | * @return mixed null on empty data|false on improper data|array - on success |
||
224 | */ |
||
225 | private function decode(string $data) |
||
313 | |||
314 | /** |
||
315 | * Handshakes/upgrade and key parse |
||
316 | * |
||
317 | * @param resource $client Source client socket to write |
||
318 | * @param string $headers Headers that client has been sent |
||
319 | * @return string socket handshake key (Sec-WebSocket-Key)| false on parse error |
||
320 | */ |
||
321 | private function handshake($client, string $headers): string |
||
341 | |||
342 | /** |
||
343 | * Sets an array of headers needed to upgrade server/client connection |
||
344 | * |
||
345 | * @param string $secWebSocketAccept base64 encoded Sec-WebSocket-Accept header |
||
346 | */ |
||
347 | private function setHeadersUpgrade($secWebSocketAccept) |
||
356 | |||
357 | /** |
||
358 | * Retreives headers from an array of headers to upgrade server/client connection |
||
359 | * |
||
360 | * @return string Headers to Upgrade communication connection |
||
361 | */ |
||
362 | private function getHeadersUpgrade(): string |
||
378 | |||
379 | /** |
||
380 | * Parses parameters from GET on web-socket client connection before handshake |
||
381 | * |
||
382 | * @param string $headers |
||
383 | */ |
||
384 | private function setPathParams(string $headers) |
||
407 | } |
||
408 |
If you suppress an error, we recommend checking for the error condition explicitly: