Passed
Pull Request — master (#988)
by
unknown
17:05
created

TWebServerAction::actionServe()   C

Complexity

Conditions 16
Paths 168

Size

Total Lines 79
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 50
c 1
b 0
f 0
nc 168
nop 1
dl 0
loc 79
rs 5

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * TWebServerAction class file
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Shell\Actions;
11
12
use Prado\Prado;
13
use Prado\Shell\TShellAction;
14
use Prado\Shell\TShellApplication;
15
use Prado\TPropertyValue;
16
use Prado\Util\Helpers\TProcessHelper;
17
18
/**
19
 * TWebServerAction class
20
 *
21
 * This class serves the application with the built-in PHP testing web server.
22
 *
23
 * When no network address is specified, the web server will listen on the default
24
 * 127.0.0.1 network interface and on port 8080.  The application is accessible on
25
 * the machine web browser at web address "http://127.0.0.1:8080/".
26
 *
27
 * The command option `--address=localhost` can specify the network address both
28
 * with and without a port.  A port can also be specified with the network address,
29
 * eg `--address=localhost:8777`.
30
 *
31
 * if the machine "hosts" file maps a domain name to 127.0.0.1/localhost, then that
32
 * domain name can be specified with `--address=testdomain.com` and is accessible
33
 * on the machine web browser at "http://testdomain.com/".  In this example, testdomain.com
34
 * maps to 127.0.0.1 in the system's "hosts" file.
35
 *
36
 * The network address can be changed to IPv6 with the command line option `--ipv6`.
37
 * To serve pages on all network addresses, including any internet IP address, include
38
 * the option `--all`.  These options only work when there is no address specified.
39
 *
40
 * The command line option `--port=8777` can be used to change the port; in this example
41
 * to port 8777.  Ports 1023 and below are typically reserved for application and
42
 * system use and cannot be specified without administration access.
43
 *
44
 * To have more than one worker (to handle multiple requests), specify the `--workers=8`
45
 * command option (with the number of page workers need for you).  In this example,
46
 * eight concurrent workers are created.
47
 *
48
 * @author Brad Anderson <[email protected]>
49
 * @since 4.2.3
50
 */
51
class TWebServerAction extends TShellAction
52
{
53
	public const DEV_WEBSERVER_ENV = 'PRADO_DEV_WEBSERVER';
54
	public const WORKERS_ENV = 'PHP_CLI_SERVER_WORKERS';
55
56
	public const DEV_WEBSERVER_PARAM = 'Prado:PhpWebServer';
57
58
	protected $action = 'http';
59
	protected $methods = ['serve'];
60
	protected $parameters = [[]];
61
	protected $optional = [['router-filepath']];
62
	protected $description = [
63
		'Provides a PHP Web Server to serve the application.',
64
		'Runs a PHP Web Server after Initializing the Application.'];
65
66
	/** @var bool Listen on all network addresses assigned to the computer, when one is not provided. */
67
	private bool $_all = false;
68
69
	/** @var ?string The specific address to listen on. */
70
	private ?string $_address = null;
71
72
	/** @var int The port to listen on, default 8080 */
73
	private int $_port = 8080;
74
75
	/** @var bool Use a direct ip v6 address, when one is not provided. */
76
	private bool $_ipv6 = false;
77
78
	/** @var int the number of workers for the Web Server */
79
	private int $_workers = 1;
80
81
82
83
	/**
84
	 * This option is only used when no network interface is specified.
85
	 * @return bool Respond on all network addresses.
86
	 */
87
	public function getAll(): bool
88
	{
89
		return $this->_all;
90
	}
91
92
	/**
93
	 * This option is only used when no network interface is specified.
94
	 * @param mixed $value
95
	 * @return bool Respond on all network addresses.
96
	 * @return static The current object.
97
	 */
98
	public function setAll($value): static
99
	{
100
		if (!$value) {
101
			$this->_all = true;
102
		} else {
103
			$this->_all = TPropertyValue::ensureBoolean($value);
104
		}
105
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Prado\Shell\Actions\TWebServerAction which is incompatible with the documented return type boolean.
Loading history...
106
	}
107
108
	/**
109
	 * Gets the network address to serve pages from.  When no network address is specified
110
	 * then this will return the proper network address based upon {@see self::getIpv6()}
111
	 * and {@see self::getAll()}.
112
	 * @return string The network address to serve pages.
113
	 */
114
	public function getAddress(): string
115
	{
116
		if(!$this->_address) {
117
			if ($this->getIpv6()) {
118
				if ($this->getAll()) {
119
					return '[::0]';
120
				} else {
121
					return 'localhost';
122
				}
123
			} else {
124
				if ($this->getAll()) {
125
					return '0.0.0.0';
126
				} else {
127
					return '127.0.0.1';
128
				}
129
			}
130
		}
131
		return $this->_address;
132
	}
133
134
	/**
135
	 * @param ?string $address The network address to serve pages from.
136
	 * @return static The current object.
137
	 */
138
	public function setAddress($address): static
139
	{
140
		if ($address) {
141
			$address = TPropertyValue::ensureString($address);
142
			$port = null;
143
144
			if (($address[0] ?? '') === '[') {
145
				if ($pos = strrpos($address, ']')) {
146
					if ($pos = strrpos($address, ':', $pos)) {
147
						$port = substr($address, $pos + 1);
148
					}
149
				}
150
			} else {
151
				if (($pos = strrpos($address, ':')) !== false) {
152
					$port = substr($address, $pos + 1);
153
				}
154
			}
155
156
			if (is_numeric($port)) {
157
				$this->_port = (int) $port;
158
				$address = substr($address, 0, $pos);
159
			}
160
			$this->_address = $address;
161
		} else {
162
			$this->_address = null;
163
		}
164
165
		return $this;
166
	}
167
168
	/**
169
	 * @return int The port to serve pages.
170
	 */
171
	public function getPort(): int
172
	{
173
		return $this->_port;
174
	}
175
176
	/**
177
	 * @param null|int|string $address The port to serve pages, default 8080.
178
	 * @return static The current object.
179
	 */
180
	public function setPort($address): static
181
	{
182
		$this->_port = TPropertyValue::ensureInteger($address);
183
184
		return $this;
185
	}
186
187
	/**
188
	 * @return bool Use an IPv6 network address.
189
	 */
190
	public function getIpv6(): bool
191
	{
192
		return $this->_ipv6;
193
	}
194
195
	/**
196
	 * @param null|bool|string $ipv6
197
	 * @return static The current object.
198
	 */
199
	public function setIpv6($ipv6): static
200
	{
201
		if ($ipv6 === null || $ipv6 === '') {
202
			$ipv6 = true;
203
		}
204
		$this->_ipv6 = TPropertyValue::ensureBoolean($ipv6);
205
206
		return $this;
207
	}
208
209
	/**
210
	 * @return int The number of web server requests workers.
211
	 */
212
	public function getWorkers(): int
213
	{
214
		return $this->_workers;
215
	}
216
217
	/**
218
	 * @param null|int|string $value The number of web server requests workers.
219
	 * @return static The current object.
220
	 */
221
	public function setWorkers($value): static
222
	{
223
		$this->_workers = max(1, TPropertyValue::ensureInteger($value));
224
225
		return $this;
226
	}
227
228
	/**
229
	 * Properties for the action set by parameter.
230
	 * @param string $methodID the action being executed
231
	 * @return array properties for the $actionID
232
	 */
233
	public function options($methodID): array
234
	{
235
		if ($methodID === 'serve') {
236
			return ['address', 'port', 'workers', 'ipv6', 'all'];
237
		}
238
		return [];
239
	}
240
241
	/**
242
	 * Aliases for the properties to be set by parameter
243
	 * @return array<string, string> alias => property for the $actionID
244
	 */
245
	public function optionAliases(): array
246
	{
247
		return ['a' => 'address', 'p' => 'port', 'w' => 'workers', '6' => 'ipv6', 'i' => 'all'];
248
	}
249
250
251
	/**
252
	 * This runs the PHP Development Web Server.
253
	 * @param array $args parameters
254
	 * @return bool
255
	 */
256
	public function actionServe($args)
257
	{
258
		array_shift($args);
259
260
		$env = getenv();
261
		$env[static::DEV_WEBSERVER_ENV] = '1';
262
263
		if (($workers = $this->getWorkers()) > 1) {
264
			$env[static::WORKERS_ENV] = $workers;
265
		}
266
267
		$address = $this->getAddress();
268
		$port = $this->getPort();
269
		$documentRoot = dirname($_SERVER['SCRIPT_FILENAME']);
270
271
		if ($router = array_shift($args)) {
272
			if ($r = realpath($router)) {
273
				$router = $r;
274
			}
275
		}
276
277
		$app = Prado::getApplication();
278
		$quiet = ($app instanceof TShellApplication) ? $app->getQuietMode() : 0;
279
280
		$writer = $this->getWriter();
281
282
		if (!is_dir($documentRoot)) {
283
			if ($quiet !== 3) {
284
				$writer->writeError("Document root \"$documentRoot\" does not exist.");
285
				$writer->flush();
286
			}
287
			return true;
288
		}
289
290
		if ($this->isAddressTaken($address, $port)) {
291
			if ($quiet !== 3) {
292
				$writer->writeError("http://$address is taken by another process.");
293
				$writer->flush();
294
			}
295
			return true;
296
		}
297
298
		if ($router !== null && !file_exists($router)) {
299
			if ($quiet !== 3) {
300
				$writer->writeError("Routing file \"$router\" does not exist.");
301
				$writer->flush();
302
			}
303
			return true;
304
		}
305
306
		$nullFile = null;
307
		if ($quiet >= 2) {
308
			$nullFile = TProcessHelper::isSystemWindows() ? 'NUL' : '/dev/null';
309
			$descriptors = [STDIN, ['file', $nullFile, 'w'], ['file', $nullFile, 'w']];
310
		} else {
311
			$writer->writeline();
312
			$writer->write("Document root is \"{$documentRoot}\"\n");
313
			if ($router) {
314
				$writer->write("Routing file is \"$router\"\n");
315
			}
316
			$writer->writeline();
317
			$writer->write("To quit press CTRL-C or COMMAND-C.\n");
318
			$writer->flush();
319
320
			$descriptors = [STDIN, STDOUT, STDERR];
321
		}
322
323
		$command = $this->generateCommand($address . ':' . $port, $documentRoot, $router);
324
		$cwd = null;
325
326
		$process = proc_open($command, $descriptors, $pipes, $cwd, $env);
327
		proc_close($process);
328
329
		if ($nullFile) {
330
			fclose($descriptors[1]);
331
			fclose($descriptors[2]);
332
		}
333
334
		return true;
335
	}
336
337
	/**
338
	 * @param string $address The web server address and port.
339
	 * @param string $documentRoot The path of the application.
340
	 * @param ?string $router The router file
341
	 * @return array
342
	 */
343
	public function generateCommand(string $address, string $documentRoot, $router): array
344
	{
345
		return TProcessHelper::filterCommand(array_merge(['@php', '-S', $address, '-t', $documentRoot], $router ? [$router] : []));
346
	}
347
348
349
	/**
350
	 * This checks if a specific hostname and port are currently being used in the system
351
	 * @param string $hostname server address
352
	 * @param int $port server port
353
	 * @return bool if address is already in use
354
	 */
355
	protected function isAddressTaken($hostname, $port)
356
	{
357
		$fp = @fsockopen($hostname, $port, $errno, $errstr, 3);
358
		if ($fp === false) {
359
			return false;
360
		}
361
		fclose($fp);
362
		return true;
363
	}
364
}
365