@@ -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
@@ -115,9 +116,26 @@ $process = new Process('echo test');
115116$process->start($loop);
116117```
117118
119+ The command line string usually consists of a whitespace-separated list with
120+ your main executable bin and any number of arguments. Special care should be
121+ taken to escape or quote any arguments, escpecially if you pass any user input
122+ along. Likewise, keep in mind that especially on Windows, it is rather common to
123+ have path names containing spaces and other special characters. If you want to
124+ run a binary like this, you will have to ensure this is quoted as a single
125+ argument using ` escapeshellarg() ` like this:
126+
127+ ``` php
128+ $bin = 'C:\\Program files (x86)\\PHP\\php.exe';
129+ $file = 'C:\\Users\\me\\Desktop\\Application\\main.php';
130+
131+ $process = new Process(escapeshellarg($bin) . ' ' . escapeshellarg($file));
132+ $process->start($loop);
133+ ```
134+
118135By 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.
136+ in a ` sh ` command on Unix, so that the first example will actually execute
137+ ` sh -c echo test ` under the hood on Unix. On Windows, it will not launch
138+ processes by wrapping them in a shell.
121139
122140This is a very useful feature because it does not only allow you to pass single
123141commands, but actually allows you to pass any kind of shell command line and
@@ -131,6 +149,12 @@ $process = new Process('echo run && demo || echo failed');
131149$process->start($loop);
132150```
133151
152+ > Note that [ Windows support] ( #windows-compatibility ) is limited in that it
153+ doesn't support STDIO streams at all and also that processes will not be run
154+ in a wrapping shell by default. If you want to run a shell built-in function
155+ such as ` echo hello ` or ` sleep 10 ` , you may have to prefix your command line
156+ with an explicit shell like ` cmd /c echo hello ` .
157+
134158In other words, the underlying shell is responsible for managing this command
135159line and launching the individual sub-commands and connecting their STDIO
136160streams as appropriate.
@@ -161,7 +185,7 @@ $first->on('exit', function () use ($loop) {
161185});
162186```
163187
164- Keep in mind that PHP uses the shell wrapper for ALL command lines.
188+ Keep in mind that PHP uses the shell wrapper for ALL command lines on Unix .
165189While this may seem reasonable for more complex command lines, this actually
166190also applies to running the most simple single command:
167191
@@ -170,7 +194,7 @@ $process = new Process('yes');
170194$process->start($loop);
171195```
172196
173- This will actually spawn a command hierarchy similar to this:
197+ This will actually spawn a command hierarchy similar to this on Unix :
174198
175199```
1762005480 … \_ php example.php
@@ -183,8 +207,8 @@ will actually target the wrapping shell, which may not be the desired result
183207in many cases.
184208
185209If 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:
210+ prepend the command string with ` exec ` on Unix platforms , which will cause the
211+ wrapping shell process to be replaced by our process:
188212
189213``` php
190214$process = new Process('exec yes');
@@ -209,8 +233,8 @@ As a rule of thumb, most commands will likely run just fine with the wrapping
209233shell.
210234If you pass a complete command line (or are unsure), you SHOULD most likely keep
211235the 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.
236+ If you're running on Unix and you want to pass an invidual command only, you MAY
237+ want to consider prepending the command string with ` exec ` to avoid the wrapping shell.
214238
215239### Termination
216240
@@ -396,12 +420,144 @@ cases. You may then enable this explicitly as given above.
396420
397421### Windows Compatibility
398422
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.
423+ Due to platform constraints, this library provides only limited support for
424+ spawning child processes on Windows. In particular, PHP does not allow accessing
425+ standard I/O pipes without blocking. As such, this project will not allow
426+ constructing a child process with the default process pipes and will instead
427+ throw a ` LogicException ` on Windows by default:
428+
429+ ``` php
430+ // throws LogicException on Windows
431+ $process = new Process('ping example.com');
432+ $process->start($loop);
433+ ```
434+
435+ There are a number of alternatives and workarounds as detailed below if you want
436+ to run a child process on Windows, each with its own set of pros and cons:
437+
438+ * This package does work on
439+ [ ` Windows Subsystem for Linux ` ] ( https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux )
440+ (or WSL) without issues. When you are in control over how your application is
441+ deployed, we recommend [ installing WSL] ( https://msdn.microsoft.com/en-us/commandline/wsl/install_guide )
442+ when you want to run this package on Windows.
443+
444+ * If you only care about the exit code of a child process to check if its
445+ execution was successful, you can use [ custom pipes] ( #custom-pipes ) to omit
446+ any standard I/O pipes like this:
447+
448+ ``` php
449+ $process = new Process('ping example.com', null, null, array());
450+ $process->start($loop);
451+
452+ $process->on('exit', function ($exitcode) {
453+ echo 'exit with ' . $exitcode . PHP_EOL;
454+ });
455+ ```
456+
457+ Similarly, this is also useful if your child process communicates over
458+ sockets with remote servers or even your parent process using the
459+ [Socket component](https://github.com/reactphp/socket). This is usually
460+ considered the best alternative if you have control over how your child
461+ process communicates with the parent process.
462+
463+ * If you only care about command output after the child process has been
464+ executed, you can use [custom pipes](#custom-pipes) to configure file
465+ handles to be passed to the child process instead of pipes like this:
466+
467+ ```php
468+ $process = new Process('ping example.com', null, null, array(
469+ array('file', 'nul', 'r'),
470+ $stdout = tmpfile(),
471+ array('file', 'nul', 'w')
472+ ));
473+ $process->start($loop);
474+
475+ $process->on('exit', function ($exitcode) use ($stdout) {
476+ echo 'exit with ' . $exitcode . PHP_EOL;
477+
478+ // rewind to start and then read full file (demo only, this is blocking).
479+ // reading from shared file is only safe if you have some synchronization in place
480+ // or after the child process has terminated.
481+ rewind($stdout);
482+ echo stream_get_contents($stdout);
483+ fclose($stdout);
484+ });
485+ ```
486+
487+ Note that this example uses `tmpfile()`/`fopen()` for illustration purposes only.
488+ This should not be used in a truly async program because the filesystem is
489+ inherently blocking and each call could potentially take several seconds.
490+ See also the [Filesystem component](https://github.com/reactphp/filesystem) as an
491+ alternative.
492+
493+ * If you want to access command output as it happens in a streaming fashion,
494+ you can use redirection to spawn an additional process to forward your
495+ standard I/O streams to a socket and use [custom pipes](#custom-pipes) to
496+ omit any actual standard I/O pipes like this:
497+
498+ ```php
499+ $server = new React\Socket\Server('127.0.0.1:0', $loop);
500+ $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
501+ $connection->on('data', function ($chunk) {
502+ echo $chunk;
503+ });
504+ });
505+
506+ $command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress());
507+ $process = new Process($command, null, null, array());
508+ $process->start($loop);
509+
510+ $process->on('exit', function ($exitcode) use ($server) {
511+ $server->close();
512+ echo 'exit with ' . $exitcode . PHP_EOL;
513+ });
514+ ```
515+
516+ Note how this will spawn another fictional `foobar` helper program to consume
517+ the standard output from the actual child process. This is in fact similar
518+ to the above recommendation of using socket connections in the child process,
519+ but in this case does not require modification of the actual child process.
520+
521+ In this example, the fictional `foobar` helper program can be implemented by
522+ simply consuming all data from standard input and forwarding it to a socket
523+ connection like this:
524+
525+ ```php
526+ $socket = stream_socket_client($argv[1]);
527+ do {
528+ fwrite($socket, $data = fread(STDIN, 8192));
529+ } while (isset($data[0]));
530+ ```
531+
532+ Accordingly, this example can also be run with plain PHP without having to
533+ rely on any external helper program like this:
534+
535+ ```php
536+ $code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));';
537+ $command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress());
538+ $process = new Process($command, null, null, array());
539+ $process->start($loop);
540+ ```
541+
542+ See also [example #23](examples/23-forward-socket.php).
543+
544+ Note that this is for illustration purposes only and you may want to implement
545+ some proper error checks and/or socket verification in actual production use
546+ if you do not want to risk other processes connecting to the server socket.
547+ In this case, we suggest looking at the excellent
548+ [createprocess-windows](https://github.com/cubiclesoft/createprocess-windows).
549+
550+ Additionally, note that the [command](#command) given to the `Process` will be
551+ passed to the underlying Windows-API
552+ ([`CreateProcess`](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa))
553+ as-is and the process will not be launched in a wrapping shell by default. In
554+ particular, this means that shell built-in functions such as `echo hello` or
555+ `sleep 10` may have to be prefixed with an explicit shell command like this:
556+
557+ ```php
558+ $process = new Process('cmd /c echo hello', null, null, $pipes);
559+ $process->start($loop);
560+ ```
405561
406562## Install
407563
0 commit comments