Passed
Pull Request — master (#988)
by
unknown
04:53
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
 * The TWebServerAction is only available when the application is in "Debug" mode.
49
 * In other Application modes, this action can be enabled with the Prado Application
50
 * Parameter "Prado:PhpWebServer" set to "true". eg. Within the Application configuration:
51
 *
52
 * ```xml
53
 * <parameters>
54
 *     <parameter id="Prado:PhpWebServer" value="true" />
55
 * </parameters>
56
 * ```
57
 *
58
 * @author Brad Anderson <[email protected]>
59
 * @since 4.2.3
60
 */
61
class TWebServerAction extends TShellAction
62
{
63
	public const DEV_WEBSERVER_ENV = 'PRADO_DEV_WEBSERVER';
64
	public const WORKERS_ENV = 'PHP_CLI_SERVER_WORKERS';
65
66
	public const DEV_WEBSERVER_PARAM = 'Prado:PhpWebServer';
67
68
	protected $action = 'http';
69
	protected $methods = ['serve'];
70
	protected $parameters = [[]];
71
	protected $optional = [['router-filepath']];
72
	protected $description = [
73
		'Provides a PHP Web Server to serve the application.',
74
		'Runs a PHP Web Server after Initializing the Application.'];
75
76
	/** @var bool Listen on all network addresses assigned to the computer, when one is not provided. */
77
	private bool $_all = false;
78
79
	/** @var ?string The specific address to listen on. */
80
	private ?string $_address = null;
81
82
	/** @var int The port to listen on, default 8080 */
83
	private int $_port = 8080;
84
85
	/** @var bool Use a direct ip v6 address, when one is not provided. */
86
	private bool $_ipv6 = false;
87
88
	/** @var int the number of workers for the Web Server */
89
	private int $_workers = 1;
90
91
92
93
	/**
94
	 * This option is only used when no network interface is specified.
95
	 * @return bool Respond on all network addresses.
96
	 */
97
	public function getAll(): bool
98
	{
99
		return $this->_all;
100
	}
101
102
	/**
103
	 * This option is only used when no network interface is specified.
104
	 * @param mixed $value
105
	 * @return bool Respond on all network addresses.
106
	 * @return static The current object.
107
	 */
108
	public function setAll($value): static
109
	{
110
		if (!$value) {
111
			$this->_all = true;
112
		} else {
113
			$this->_all = TPropertyValue::ensureBoolean($value);
114
		}
115
		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...
116
	}
117
118
	/**
119
	 * Gets the network address to serve pages from.  When no network address is specified
120
	 * then this will return the proper network address based upon {@see self::getIpv6()}
121
	 * and {@see self::getAll()}.
122
	 * @return string The network address to serve pages.
123
	 */
124
	public function getAddress(): string
125
	{
126
		if(!$this->_address) {
127
			if ($this->getIpv6()) {
128
				if ($this->getAll()) {
129
					return '[::0]';
130
				} else {
131
					return 'localhost';
132
				}
133
			} else {
134
				if ($this->getAll()) {
135
					return '0.0.0.0';
136
				} else {
137
					return '127.0.0.1';
138
				}
139
			}
140
		}
141
		return $this->_address;
142
	}
143
144
	/**
145
	 * @param ?string $address The network address to serve pages from.
146
	 * @return static The current object.
147
	 */
148
	public function setAddress($address): static
149
	{
150
		if ($address) {
151
			$address = TPropertyValue::ensureString($address);
152
			$port = null;
153
154
			if (($address[0] ?? '') === '[') {
155
				if ($pos = strrpos($address, ']')) {
156
					if ($pos = strrpos($address, ':', $pos)) {
157
						$port = substr($address, $pos + 1);
158
					}
159
				}
160
			} else {
161
				if (($pos = strrpos($address, ':')) !== false) {
162
					$port = substr($address, $pos + 1);
163
				}
164
			}
165
166
			if (is_numeric($port)) {
167
				$this->_port = (int) $port;
168
				$address = substr($address, 0, $pos);
169
			}
170
			$this->_address = $address;
171
		} else {
172
			$this->_address = null;
173
		}
174
175
		return $this;
176
	}
177
178
	/**
179
	 * @return int The port to serve pages.
180
	 */
181
	public function getPort(): int
182
	{
183
		return $this->_port;
184
	}
185
186
	/**
187
	 * @param null|int|string $address The port to serve pages, default 8080.
188
	 * @return static The current object.
189
	 */
190
	public function setPort($address): static
191
	{
192
		$this->_port = TPropertyValue::ensureInteger($address);
193
194
		return $this;
195
	}
196
197
	/**
198
	 * @return bool Use an IPv6 network address.
199
	 */
200
	public function getIpv6(): bool
201
	{
202
		return $this->_ipv6;
203
	}
204
205
	/**
206
	 * @param null|bool|string $ipv6
207
	 * @return static The current object.
208
	 */
209
	public function setIpv6($ipv6): static
210
	{
211
		if ($ipv6 === null || $ipv6 === '') {
212
			$ipv6 = true;
213
		}
214
		$this->_ipv6 = TPropertyValue::ensureBoolean($ipv6);
215
216
		return $this;
217
	}
218
219
	/**
220
	 * @return int The number of web server requests workers.
221
	 */
222
	public function getWorkers(): int
223
	{
224
		return $this->_workers;
225
	}
226
227
	/**
228
	 * @param null|int|string $value The number of web server requests workers.
229
	 * @return static The current object.
230
	 */
231
	public function setWorkers($value): static
232
	{
233
		$this->_workers = max(1, TPropertyValue::ensureInteger($value));
234
235
		return $this;
236
	}
237
238
	/**
239
	 * Properties for the action set by parameter.
240
	 * @param string $methodID the action being executed
241
	 * @return array properties for the $actionID
242
	 */
243
	public function options($methodID): array
244
	{
245
		if ($methodID === 'serve') {
246
			return ['address', 'port', 'workers', 'ipv6', 'all'];
247
		}
248
		return [];
249
	}
250
251
	/**
252
	 * Aliases for the properties to be set by parameter
253
	 * @return array<string, string> alias => property for the $actionID
254
	 */
255
	public function optionAliases(): array
256
	{
257
		return ['a' => 'address', 'p' => 'port', 'w' => 'workers', '6' => 'ipv6', 'i' => 'all'];
258
	}
259
260
261
	/**
262
	 * This runs the PHP Development Web Server.
263
	 * @param array $args parameters
264
	 * @return bool
265
	 */
266
	public function actionServe($args)
267
	{
268
		array_shift($args);
269
270
		$env = getenv();
271
		$env[static::DEV_WEBSERVER_ENV] = '1';
272
273
		if (($workers = $this->getWorkers()) > 1) {
274
			$env[static::WORKERS_ENV] = $workers;
275
		}
276
277
		$address = $this->getAddress();
278
		$port = $this->getPort();
279
		$documentRoot = dirname($_SERVER['SCRIPT_FILENAME']);
280
281
		if ($router = array_shift($args)) {
282
			if ($r = realpath($router)) {
283
				$router = $r;
284
			}
285
		}
286
287
		$app = Prado::getApplication();
288
		$quiet = ($app instanceof TShellApplication) ? $app->getQuietMode() : 0;
289
290
		$writer = $this->getWriter();
291
292
		if (!is_dir($documentRoot)) {
293
			if ($quiet !== 3) {
294
				$writer->writeError("Document root \"$documentRoot\" does not exist.");
295
				$writer->flush();
296
			}
297
			return true;
298
		}
299
300
		if ($this->isAddressTaken($address, $port)) {
301
			if ($quiet !== 3) {
302
				$writer->writeError("http://$address is taken by another process.");
303
				$writer->flush();
304
			}
305
			return true;
306
		}
307
308
		if ($router !== null && !file_exists($router)) {
309
			if ($quiet !== 3) {
310
				$writer->writeError("Routing file \"$router\" does not exist.");
311
				$writer->flush();
312
			}
313
			return true;
314
		}
315
316
		$nullFile = null;
317
		if ($quiet >= 2) {
318
			$nullFile = TProcessHelper::isSystemWindows() ? 'NUL' : '/dev/null';
319
			$descriptors = [STDIN, ['file', $nullFile, 'w'], ['file', $nullFile, 'w']];
320
		} else {
321
			$writer->writeline();
322
			$writer->write("Document root is \"{$documentRoot}\"\n");
323
			if ($router) {
324
				$writer->write("Routing file is \"$router\"\n");
325
			}
326
			$writer->writeline();
327
			$writer->write("To quit press CTRL-C or COMMAND-C.\n");
328
			$writer->flush();
329
330
			$descriptors = [STDIN, STDOUT, STDERR];
331
		}
332
333
		$command = $this->generateCommand($address . ':' . $port, $documentRoot, $router);
334
		$cwd = null;
335
336
		$process = proc_open($command, $descriptors, $pipes, $cwd, $env);
337
		proc_close($process);
338
339
		if ($nullFile) {
340
			fclose($descriptors[1]);
341
			fclose($descriptors[2]);
342
		}
343
344
		return true;
345
	}
346
347
	/**
348
	 * @param string $address The web server address and port.
349
	 * @param string $documentRoot The path of the application.
350
	 * @param ?string $router The router file
351
	 * @return array
352
	 */
353
	public function generateCommand(string $address, string $documentRoot, $router): array
354
	{
355
		return TProcessHelper::filterCommand(array_merge(['@php', '-S', $address, '-t', $documentRoot], $router ? [$router] : []));
356
	}
357
358
359
	/**
360
	 * This checks if a specific hostname and port are currently being used in the system
361
	 * @param string $hostname server address
362
	 * @param int $port server port
363
	 * @return bool if address is already in use
364
	 */
365
	protected function isAddressTaken($hostname, $port)
366
	{
367
		$fp = @fsockopen($hostname, $port, $errno, $errstr, 3);
368
		if ($fp === false) {
369
			return false;
370
		}
371
		fclose($fp);
372
		return true;
373
	}
374
}
375