@@ -67,10 +67,11 @@ array with all three pipes.
6767
6868Note that this default configuration may be overridden by explicitly passing
6969[ custom pipes] ( #custom-pipes ) , in which case they may not be set or be assigned
70- different values. The ` $pipes ` array will always contain references to all pipes
71- as configured and the standard I/O references will always be set to reference
72- the pipes matching the above conventions. See [ custom pipes] ( #custom-pipes ) for
73- more details.
70+ different values. In particular, note that [ Windows support] ( #windows-compatibility )
71+ is limited in that it doesn't support non-blocking STDIO pipes. The ` $pipes `
72+ array will always contain references to all pipes as configured and the standard
73+ I/O references will always be set to reference the pipes matching the above
74+ conventions. See [ custom pipes] ( #custom-pipes ) for more details.
7475
7576Because each of these implement the underlying
7677[ ` ReadableStreamInterface ` ] ( https://github.com/reactphp/stream#readablestreaminterface ) or
@@ -116,8 +117,9 @@ $process->start($loop);
116117```
117118
118119By default, PHP will launch processes by wrapping the given command line string
119- in a ` sh ` command, so that the above example will actually execute
120- ` sh -c echo test ` under the hood.
120+ in a ` sh ` command on Unix, so that the above example will actually execute
121+ ` sh -c echo test ` under the hood on Unix. On Windows, it will launch processes
122+ by wrapping it in a ` cmd ` shell like ` cmd /C echo test ` .
121123
122124This is a very useful feature because it does not only allow you to pass single
123125commands, but actually allows you to pass any kind of shell command line and
@@ -183,8 +185,8 @@ will actually target the wrapping shell, which may not be the desired result
183185in many cases.
184186
185187If you do not want this wrapping shell process to show up, you can simply
186- prepend the command string with ` exec ` , which will cause the wrapping shell
187- process to be replaced by our process:
188+ prepend the command string with ` exec ` on Unix platforms , which will cause the
189+ wrapping shell process to be replaced by our process:
188190
189191``` php
190192$process = new Process('exec yes');
@@ -209,8 +211,8 @@ As a rule of thumb, most commands will likely run just fine with the wrapping
209211shell.
210212If you pass a complete command line (or are unsure), you SHOULD most likely keep
211213the wrapping shell.
212- If you want to pass an invidual command only, you MAY want to consider
213- prepending the command string with ` exec ` to avoid the wrapping shell.
214+ If you're running on Unix and you want to pass an invidual command only, you MAY
215+ want to consider prepending the command string with ` exec ` to avoid the wrapping shell.
214216
215217### Termination
216218
@@ -396,12 +398,132 @@ cases. You may then enable this explicitly as given above.
396398
397399### Windows Compatibility
398400
399- Due to the blocking nature of ` STDIN ` /` STDOUT ` /` STDERR ` pipes on Windows we can
400- not guarantee this package works as expected on Windows directly. As such when
401- instantiating ` Process ` it throws an exception when on native Windows.
402- However this package does work on [ ` Windows Subsystem for Linux ` ] ( https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux )
403- (or WSL) without issues. We suggest [ installing WSL] ( https://msdn.microsoft.com/en-us/commandline/wsl/install_guide )
404- when you want to run this package on Windows.
401+ Due to platform constraints, this library provides only limited support for
402+ spawning child processes on Windows. In particular, PHP does not allow accessing
403+ standard I/O pipes without blocking. As such, this project will not allow
404+ constructing a child process with the default process pipes and will instead
405+ throw a ` LogicException ` on Windows by default:
406+
407+ ``` php
408+ // throws LogicException on Windows
409+ $process = new Process('ping example.com');
410+ $process->start($loop);
411+ ```
412+
413+ There are a number of alternatives and workarounds as detailed below if you want
414+ to run a child process on Windows, each with its own set of pros and cons:
415+
416+ * This package does work on
417+ [ ` Windows Subsystem for Linux ` ] ( https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux )
418+ (or WSL) without issues. When you are in control over how your application is
419+ deployed, we recommend [ installing WSL] ( https://msdn.microsoft.com/en-us/commandline/wsl/install_guide )
420+ when you want to run this package on Windows.
421+
422+ * If you only care about the exit code of a child process to check if its
423+ execution was successful, you can use [ custom pipes] ( #custom-pipes ) to omit
424+ any standard I/O pipes like this:
425+
426+ ``` php
427+ $process = new Process('ping example.com', null, null, array());
428+ $process->start($loop);
429+
430+ $process->on('exit', function ($exitcode) {
431+ echo 'exit with ' . $exitcode . PHP_EOL;
432+ });
433+ ```
434+
435+ Similarly, this is also useful if your child process communicates over
436+ sockets with remote servers or even your parent process using the
437+ [Socket component](https://github.com/reactphp/socket). This is usually
438+ considered the best alternative if you have control over how your child
439+ process communicates with the parent process.
440+
441+ * If you only care about command output after the child process has been
442+ executed, you can use [custom pipes](#custom-pipes) to configure file
443+ handles to be passed to the child process instead of pipes like this:
444+
445+ ```php
446+ $process = new Process('ping example.com', null, null, array(
447+ array('file', 'nul', 'r'),
448+ $stdout = tmpfile(),
449+ array('file', 'nul', 'w')
450+ ));
451+ $process->start($loop);
452+
453+ $process->on('exit', function ($exitcode) use ($stdout) {
454+ echo 'exit with ' . $exitcode . PHP_EOL;
455+
456+ // rewind to start and then read full file (demo only, this is blocking).
457+ // reading from shared file is only safe if you have some synchronization in place
458+ // or after the child process has terminated.
459+ rewind($stdout);
460+ echo stream_get_contents($stdout);
461+ fclose($stdout);
462+ });
463+ ```
464+
465+ Note that this example uses `tmpfile()`/`fopen()` for illustration purposes only.
466+ This should not be used in a truly async program because the filesystem is
467+ inherently blocking and each call could potentially take several seconds.
468+ See also the [Filesystem component](https://github.com/reactphp/filesystem) as an
469+ alternative.
470+
471+ * If you want to access command output as it happens in a streaming fashion,
472+ you can use redirection to spawn an additional process to forward your
473+ standard I/O streams to a socket and use [custom pipes](#custom-pipes) to
474+ omit any actual standard I/O pipes like this:
475+
476+ ```php
477+ $server = new React\Socket\Server('127.0.0.1:0', $loop);
478+ $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
479+ $connection->on('data', function ($chunk) {
480+ echo $chunk;
481+ });
482+ });
483+
484+ $command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress());
485+ $process = new Process($command, null, null, array());
486+ $process->start($loop);
487+
488+ $process->on('exit', function ($exitcode) use ($server) {
489+ $server->close();
490+ echo 'exit with ' . $exitcode . PHP_EOL;
491+ });
492+ ```
493+
494+ Note how this will spawn another fictional `foobar` helper program to consume
495+ the standard output from the actual child process. This is in fact similar
496+ to the above recommendation of using socket connections in the child process,
497+ but in this case does not require modification of the actual child process.
498+
499+ In this example, the fictional `foobar` helper program can be implemented by
500+ simply consuming all data from standard input and forwarding it to a socket
501+ connection like this:
502+
503+ ```php
504+ $socket = stream_socket_client($argv[1]);
505+ do {
506+ fwrite($socket, $data = fread(STDIN, 8192));
507+ } while (isset($data[0]));
508+ ```
509+
510+ Accordingly, this example can also be run with plain PHP without having to
511+ rely on any external helper program like this:
512+
513+ ```php
514+ $code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));';
515+ $command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress());
516+ $process = new Process($command, null, null, array());
517+ $process->start($loop);
518+ ```
519+
520+ See also [example #23](examples/23-forward-socket.php).
521+
522+ Note that this is for illustration purposes only and you may want to implement
523+ some proper error checks and/or socket verification in actual production use
524+ if you do not want to risk other processes connecting to the server socket.
525+ In this case, we suggest looking at the excellent
526+ [createprocess-windows](https://github.com/cubiclesoft/createprocess-windows).
405527
406528## Install
407529
0 commit comments