Primer commit del sistema separado falta mejorar mucho
This commit is contained in:
176
vendor/react/child-process/CHANGELOG.md
vendored
Executable file
176
vendor/react/child-process/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,176 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.6 (2025-01-01)
|
||||
|
||||
This is a compatibility release that contains backported features from the `0.7.x` branch.
|
||||
Once v0.7 is released, it will be the way forward for this project.
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable types.
|
||||
(#114 by @clue)
|
||||
|
||||
* Improve test suite to run tests on latest PHP versions and report failed assertions.
|
||||
(#113 by @clue)
|
||||
|
||||
## 0.6.5 (2022-09-16)
|
||||
|
||||
* Feature: Full support for PHP 8.1 and PHP 8.2 release.
|
||||
(#91 by @SimonFrings and #99 by @WyriHaximus)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#94 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#92 by @SimonFrings and #95 by @nhedger)
|
||||
|
||||
* Improve test suite, skip failing tests on bugged versions and fix legacy HHVM build.
|
||||
(#96 and #98 by @clue and #93 by @SimonFrings)
|
||||
|
||||
## 0.6.4 (2021-10-12)
|
||||
|
||||
* Feature / Fix: Skip sigchild check if `phpinfo()` has been disabled.
|
||||
(#89 by @clue)
|
||||
|
||||
* Fix: Fix detecting closed socket pipes on PHP 8.
|
||||
(#90 by @clue)
|
||||
|
||||
## 0.6.3 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#87 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$process = new React\ChildProcess\Process($command);
|
||||
$process->start($loop);
|
||||
|
||||
// new (using default loop)
|
||||
$process = new React\ChildProcess\Process($command);
|
||||
$process->start();
|
||||
```
|
||||
|
||||
## 0.6.2 (2021-02-05)
|
||||
|
||||
* Feature: Support PHP 8 and add non-blocking I/O support on Windows with PHP 8.
|
||||
(#85 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#78 by @WyriHaximus and #80 by @gdejong)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Run tests on PHPUnit 9, switch to GitHub actions and clean up test suite.
|
||||
(#75 by @reedy, #81 by @gdejong, #82 by @SimonFrings and #84 by @clue)
|
||||
|
||||
## 0.6.1 (2019-02-15)
|
||||
|
||||
* Feature / Fix: Improve error reporting when spawning child process fails.
|
||||
(#73 by @clue)
|
||||
|
||||
## 0.6.0 (2019-01-14)
|
||||
|
||||
A major feature release with some minor API improvements!
|
||||
This project now has limited Windows support and supports passing custom pipes
|
||||
and file descriptors to the child process.
|
||||
|
||||
This update involves a few minor BC breaks. We've tried hard to avoid BC breaks
|
||||
where possible and minimize impact otherwise. We expect that most consumers of
|
||||
this package will actually not be affected by any BC breaks, see below for more
|
||||
details.
|
||||
|
||||
* Feature / BC break: Support passing custom pipes and file descriptors to child process,
|
||||
expose all standard I/O pipes in an array and remove unused Windows-only options.
|
||||
(#62, #64 and #65 by @clue)
|
||||
|
||||
> BC note: The optional `$options` parameter in the `Process` constructor
|
||||
has been removed and a new `$fds` parameter has been added instead. The
|
||||
previous `$options` parameter was Windows-only, available options were not
|
||||
documented or referenced anywhere else in this library, so its actual
|
||||
impact is expected to be relatively small. See the documentation and the
|
||||
following changelog entry if you're looking for Windows support.
|
||||
|
||||
* Feature: Support spawning child process on Windows without process I/O pipes.
|
||||
(#67 by @clue)
|
||||
|
||||
* Feature / BC break: Improve sigchild compatibility and support explicit configuration.
|
||||
(#63 by @clue)
|
||||
|
||||
```php
|
||||
// advanced: not recommended by default
|
||||
Process::setSigchildEnabled(true);
|
||||
```
|
||||
|
||||
> BC note: The old public sigchild methods have been removed, but its
|
||||
practical impact is believed to be relatively small due to the automatic detection.
|
||||
|
||||
* Improve performance by prefixing all global functions calls with \ to skip
|
||||
the look up and resolve process and go straight to the global function.
|
||||
(#68 by @WyriHaximus)
|
||||
|
||||
* Minor documentation improvements and docblock updates.
|
||||
(#59 by @iamluc and #69 by @CharlotteDunois)
|
||||
|
||||
* Improve test suite to test against PHP7.2 and PHP 7.3, improve HHVM compatibility,
|
||||
add forward compatibility with PHPUnit 7 and run tests on Windows via Travis CI.
|
||||
(#66 and #71 by @clue)
|
||||
|
||||
## 0.5.2 (2018-01-18)
|
||||
|
||||
* Feature: Detect "exit" immediately if last process pipe is closed
|
||||
(#58 by @clue)
|
||||
|
||||
This introduces a simple check to see if the program is already known to be
|
||||
closed when the last process pipe is closed instead of relying on a periodic
|
||||
timer. This simple change improves "exit" detection significantly for most
|
||||
programs and does not cause a noticeable penalty for more advanced use cases.
|
||||
|
||||
* Fix forward compatibility with upcoming EventLoop releases
|
||||
(#56 by @clue)
|
||||
|
||||
## 0.5.1 (2017-12-22)
|
||||
|
||||
* Fix: Update Stream dependency to work around SEGFAULT in legacy PHP < 5.4.28
|
||||
and PHP < 5.5.12
|
||||
(#50 and #52 by @clue)
|
||||
|
||||
* Improve test suite by simplifying test bootstrapping logic via Composer and
|
||||
adding forward compatibility with PHPUnit 6
|
||||
(#53, #54 and #55 by @clue)
|
||||
|
||||
## 0.5.0 (2017-08-15)
|
||||
|
||||
* Forward compatibility: react/event-loop 1.0 and 0.5, react/stream 0.7.2 and 1.0, and Événement 3.0
|
||||
(#38 and #44 by @WyriHaximus, and #46 by @clue)
|
||||
* Windows compatibility: Documentate that windows isn't supported in 0.5 unless used from within WSL
|
||||
(#41 and #47 by @WyriHaximus)
|
||||
* Documentation: Termination examples
|
||||
(#42 by @clue)
|
||||
* BC: Throw LogicException in Process instanciating when on Windows or when proc_open is missing (was `RuntimeException`)
|
||||
(#49 by @mdrost)
|
||||
|
||||
## 0.4.3 (2017-03-14)
|
||||
|
||||
* Ease getting started by improving documentation and adding examples
|
||||
(#33 and #34 by @clue)
|
||||
|
||||
* First class support for PHP 5.3 through PHP 7.1 and HHVM
|
||||
(#29 by @clue and #32 by @WyriHaximus)
|
||||
|
||||
## 0.4.2 (2017-03-10)
|
||||
|
||||
* Feature: Forward compatibility with Stream v0.5
|
||||
(#26 by @clue)
|
||||
|
||||
* Improve test suite by removing AppVeyor and adding PHPUnit to `require-dev`
|
||||
(#27 and #28 by @clue)
|
||||
|
||||
## 0.4.1 (2016-08-01)
|
||||
|
||||
* Standalone component
|
||||
* Test against PHP 7 and HHVM, report test coverage, AppVeyor tests
|
||||
* Wait for stdout and stderr to close before watching for process exit
|
||||
(#18 by @mbonneau)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* Feature: Added ChildProcess to run async child processes within the event loop (@jmikola)
|
||||
21
vendor/react/child-process/LICENSE
vendored
Executable file
21
vendor/react/child-process/LICENSE
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
619
vendor/react/child-process/README.md
vendored
Executable file
619
vendor/react/child-process/README.md
vendored
Executable file
@@ -0,0 +1,619 @@
|
||||
# ChildProcess
|
||||
|
||||
[](https://github.com/reactphp/child-process/actions)
|
||||
[](https://packagist.org/packages/react/child-process)
|
||||
|
||||
Event-driven library for executing child processes with
|
||||
[ReactPHP](https://reactphp.org/).
|
||||
|
||||
This library integrates [Program Execution](http://php.net/manual/en/book.exec.php)
|
||||
with the [EventLoop](https://github.com/reactphp/event-loop).
|
||||
Child processes launched may be signaled and will emit an
|
||||
`exit` event upon termination.
|
||||
Additionally, process I/O streams (i.e. STDIN, STDOUT, STDERR) are exposed
|
||||
as [Streams](https://github.com/reactphp/stream).
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Process](#process)
|
||||
* [Stream Properties](#stream-properties)
|
||||
* [Command](#command)
|
||||
* [Termination](#termination)
|
||||
* [Custom pipes](#custom-pipes)
|
||||
* [Sigchild Compatibility](#sigchild-compatibility)
|
||||
* [Windows Compatibility](#windows-compatibility)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Quickstart example
|
||||
|
||||
```php
|
||||
$process = new React\ChildProcess\Process('echo foo');
|
||||
$process->start();
|
||||
|
||||
$process->stdout->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
|
||||
$process->on('exit', function($exitCode, $termSignal) {
|
||||
echo 'Process exited with code ' . $exitCode . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Process
|
||||
|
||||
### Stream Properties
|
||||
|
||||
Once a process is started, its I/O streams will be constructed as instances of
|
||||
`React\Stream\ReadableStreamInterface` and `React\Stream\WritableStreamInterface`.
|
||||
Before `start()` is called, these properties are not set. Once a process terminates,
|
||||
the streams will become closed but not unset.
|
||||
|
||||
Following common Unix conventions, this library will start each child process
|
||||
with the three pipes matching the standard I/O streams as given below by default.
|
||||
You can use the named references for common use cases or access these as an
|
||||
array with all three pipes.
|
||||
|
||||
* `$stdin` or `$pipes[0]` is a `WritableStreamInterface`
|
||||
* `$stdout` or `$pipes[1]` is a `ReadableStreamInterface`
|
||||
* `$stderr` or `$pipes[2]` is a `ReadableStreamInterface`
|
||||
|
||||
Note that this default configuration may be overridden by explicitly passing
|
||||
[custom pipes](#custom-pipes), in which case they may not be set or be assigned
|
||||
different values. In particular, note that [Windows support](#windows-compatibility)
|
||||
is limited in that it doesn't support non-blocking STDIO pipes. The `$pipes`
|
||||
array will always contain references to all pipes as configured and the standard
|
||||
I/O references will always be set to reference the pipes matching the above
|
||||
conventions. See [custom pipes](#custom-pipes) for more details.
|
||||
|
||||
Because each of these implement the underlying
|
||||
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) or
|
||||
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface),
|
||||
you can use any of their events and methods as usual:
|
||||
|
||||
```php
|
||||
$process = new Process($command);
|
||||
$process->start();
|
||||
|
||||
$process->stdout->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
|
||||
$process->stdout->on('end', function () {
|
||||
echo 'ended';
|
||||
});
|
||||
|
||||
$process->stdout->on('error', function (Exception $e) {
|
||||
echo 'error: ' . $e->getMessage();
|
||||
});
|
||||
|
||||
$process->stdout->on('close', function () {
|
||||
echo 'closed';
|
||||
});
|
||||
|
||||
$process->stdin->write($data);
|
||||
$process->stdin->end($data = null);
|
||||
// …
|
||||
```
|
||||
|
||||
For more details, see the
|
||||
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) and
|
||||
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface).
|
||||
|
||||
### Command
|
||||
|
||||
The `Process` class allows you to pass any kind of command line string:
|
||||
|
||||
```php
|
||||
$process = new Process('echo test');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
The command line string usually consists of a whitespace-separated list with
|
||||
your main executable bin and any number of arguments. Special care should be
|
||||
taken to escape or quote any arguments, escpecially if you pass any user input
|
||||
along. Likewise, keep in mind that especially on Windows, it is rather common to
|
||||
have path names containing spaces and other special characters. If you want to
|
||||
run a binary like this, you will have to ensure this is quoted as a single
|
||||
argument using `escapeshellarg()` like this:
|
||||
|
||||
```php
|
||||
$bin = 'C:\\Program files (x86)\\PHP\\php.exe';
|
||||
$file = 'C:\\Users\\me\\Desktop\\Application\\main.php';
|
||||
|
||||
$process = new Process(escapeshellarg($bin) . ' ' . escapeshellarg($file));
|
||||
$process->start();
|
||||
```
|
||||
|
||||
By default, PHP will launch processes by wrapping the given command line string
|
||||
in a `sh` command on Unix, so that the first example will actually execute
|
||||
`sh -c echo test` under the hood on Unix. On Windows, it will not launch
|
||||
processes by wrapping them in a shell.
|
||||
|
||||
This is a very useful feature because it does not only allow you to pass single
|
||||
commands, but actually allows you to pass any kind of shell command line and
|
||||
launch multiple sub-commands using command chains (with `&&`, `||`, `;` and
|
||||
others) and allows you to redirect STDIO streams (with `2>&1` and family).
|
||||
This can be used to pass complete command lines and receive the resulting STDIO
|
||||
streams from the wrapping shell command like this:
|
||||
|
||||
```php
|
||||
$process = new Process('echo run && demo || echo failed');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
> Note that [Windows support](#windows-compatibility) is limited in that it
|
||||
doesn't support STDIO streams at all and also that processes will not be run
|
||||
in a wrapping shell by default. If you want to run a shell built-in function
|
||||
such as `echo hello` or `sleep 10`, you may have to prefix your command line
|
||||
with an explicit shell like `cmd /c echo hello`.
|
||||
|
||||
In other words, the underlying shell is responsible for managing this command
|
||||
line and launching the individual sub-commands and connecting their STDIO
|
||||
streams as appropriate.
|
||||
This implies that the `Process` class will only receive the resulting STDIO
|
||||
streams from the wrapping shell, which will thus contain the complete
|
||||
input/output with no way to discern the input/output of single sub-commands.
|
||||
|
||||
If you want to discern the output of single sub-commands, you may want to
|
||||
implement some higher-level protocol logic, such as printing an explicit
|
||||
boundary between each sub-command like this:
|
||||
|
||||
```php
|
||||
$process = new Process('cat first && echo --- && cat second');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
As an alternative, considering launching one process at a time and listening on
|
||||
its `exit` event to conditionally start the next process in the chain.
|
||||
This will give you an opportunity to configure the subsequent process I/O streams:
|
||||
|
||||
```php
|
||||
$first = new Process('cat first');
|
||||
$first->start();
|
||||
|
||||
$first->on('exit', function () {
|
||||
$second = new Process('cat second');
|
||||
$second->start();
|
||||
});
|
||||
```
|
||||
|
||||
Keep in mind that PHP uses the shell wrapper for ALL command lines on Unix.
|
||||
While this may seem reasonable for more complex command lines, this actually
|
||||
also applies to running the most simple single command:
|
||||
|
||||
```php
|
||||
$process = new Process('yes');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
This will actually spawn a command hierarchy similar to this on Unix:
|
||||
|
||||
```
|
||||
5480 … \_ php example.php
|
||||
5481 … \_ sh -c yes
|
||||
5482 … \_ yes
|
||||
```
|
||||
|
||||
This means that trying to get the underlying process PID or sending signals
|
||||
will actually target the wrapping shell, which may not be the desired result
|
||||
in many cases.
|
||||
|
||||
If you do not want this wrapping shell process to show up, you can simply
|
||||
prepend the command string with `exec` on Unix platforms, which will cause the
|
||||
wrapping shell process to be replaced by our process:
|
||||
|
||||
```php
|
||||
$process = new Process('exec yes');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
This will show a resulting command hierarchy similar to this:
|
||||
|
||||
```
|
||||
5480 … \_ php example.php
|
||||
5481 … \_ yes
|
||||
```
|
||||
|
||||
This means that trying to get the underlying process PID and sending signals
|
||||
will now target the actual command as expected.
|
||||
|
||||
Note that in this case, the command line will not be run in a wrapping shell.
|
||||
This implies that when using `exec`, there's no way to pass command lines such
|
||||
as those containing command chains or redirected STDIO streams.
|
||||
|
||||
As a rule of thumb, most commands will likely run just fine with the wrapping
|
||||
shell.
|
||||
If you pass a complete command line (or are unsure), you SHOULD most likely keep
|
||||
the wrapping shell.
|
||||
If you're running on Unix and you want to pass an invidual command only, you MAY
|
||||
want to consider prepending the command string with `exec` to avoid the wrapping shell.
|
||||
|
||||
### Termination
|
||||
|
||||
The `exit` event will be emitted whenever the process is no longer running.
|
||||
Event listeners will receive the exit code and termination signal as two
|
||||
arguments:
|
||||
|
||||
```php
|
||||
$process = new Process('sleep 10');
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($code, $term) {
|
||||
if ($term === null) {
|
||||
echo 'exit with code ' . $code . PHP_EOL;
|
||||
} else {
|
||||
echo 'terminated with signal ' . $term . PHP_EOL;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that `$code` is `null` if the process has terminated, but the exit
|
||||
code could not be determined (for example
|
||||
[sigchild compatibility](#sigchild-compatibility) was disabled).
|
||||
Similarly, `$term` is `null` unless the process has terminated in response to
|
||||
an uncaught signal sent to it.
|
||||
This is not a limitation of this project, but actual how exit codes and signals
|
||||
are exposed on POSIX systems, for more details see also
|
||||
[here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
|
||||
|
||||
It's also worth noting that process termination depends on all file descriptors
|
||||
being closed beforehand.
|
||||
This means that all [process pipes](#stream-properties) will emit a `close`
|
||||
event before the `exit` event and that no more `data` events will arrive after
|
||||
the `exit` event.
|
||||
Accordingly, if either of these pipes is in a paused state (`pause()` method
|
||||
or internally due to a `pipe()` call), this detection may not trigger.
|
||||
|
||||
The `terminate(?int $signal = null): bool` method can be used to send the
|
||||
process a signal (SIGTERM by default).
|
||||
Depending on which signal you send to the process and whether it has a signal
|
||||
handler registered, this can be used to either merely signal a process or even
|
||||
forcefully terminate it.
|
||||
|
||||
```php
|
||||
$process->terminate(SIGUSR1);
|
||||
```
|
||||
|
||||
Keep the above section in mind if you want to forcefully terminate a process.
|
||||
If your process spawn sub-processes or implicitly uses the
|
||||
[wrapping shell mentioned above](#command), its file descriptors may be
|
||||
inherited to child processes and terminating the main process may not
|
||||
necessarily terminate the whole process tree.
|
||||
It is highly suggested that you explicitly `close()` all process pipes
|
||||
accordingly when terminating a process:
|
||||
|
||||
```php
|
||||
$process = new Process('sleep 10');
|
||||
$process->start();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($process) {
|
||||
foreach ($process->pipes as $pipe) {
|
||||
$pipe->close();
|
||||
}
|
||||
$process->terminate();
|
||||
});
|
||||
```
|
||||
|
||||
For many simple programs these seamingly complicated steps can also be avoided
|
||||
by prefixing the command line with `exec` to avoid the wrapping shell and its
|
||||
inherited process pipes as [mentioned above](#command).
|
||||
|
||||
```php
|
||||
$process = new Process('exec sleep 10');
|
||||
$process->start();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($process) {
|
||||
$process->terminate();
|
||||
});
|
||||
```
|
||||
|
||||
Many command line programs also wait for data on `STDIN` and terminate cleanly
|
||||
when this pipe is closed.
|
||||
For example, the following can be used to "soft-close" a `cat` process:
|
||||
|
||||
```php
|
||||
$process = new Process('cat');
|
||||
$process->start();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($process) {
|
||||
$process->stdin->end();
|
||||
});
|
||||
```
|
||||
|
||||
While process pipes and termination may seem confusing to newcomers, the above
|
||||
properties actually allow some fine grained control over process termination,
|
||||
such as first trying a soft-close and then applying a force-close after a
|
||||
timeout.
|
||||
|
||||
### Custom pipes
|
||||
|
||||
Following common Unix conventions, this library will start each child process
|
||||
with the three pipes matching the standard I/O streams by default. For more
|
||||
advanced use cases it may be useful to pass in custom pipes, such as explicitly
|
||||
passing additional file descriptors (FDs) or overriding default process pipes.
|
||||
|
||||
Note that passing custom pipes is considered advanced usage and requires a
|
||||
more in-depth understanding of Unix file descriptors and how they are inherited
|
||||
to child processes and shared in multi-processing applications.
|
||||
|
||||
If you do not want to use the default standard I/O pipes, you can explicitly
|
||||
pass an array containing the file descriptor specification to the constructor
|
||||
like this:
|
||||
|
||||
```php
|
||||
$fds = array(
|
||||
// standard I/O pipes for stdin/stdout/stderr
|
||||
0 => array('pipe', 'r'),
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('pipe', 'w'),
|
||||
|
||||
// example FDs for files or open resources
|
||||
4 => array('file', '/dev/null', 'r'),
|
||||
6 => fopen('log.txt','a'),
|
||||
8 => STDERR,
|
||||
|
||||
// example FDs for sockets
|
||||
10 => fsockopen('localhost', 8080),
|
||||
12 => stream_socket_server('tcp://0.0.0.0:4711')
|
||||
);
|
||||
|
||||
$process = new Process($cmd, null, null, $fds);
|
||||
$process->start();
|
||||
```
|
||||
|
||||
Unless your use case has special requirements that demand otherwise, you're
|
||||
highly recommended to (at least) pass in the standard I/O pipes as given above.
|
||||
The file descriptor specification accepts arguments in the exact same format
|
||||
as the underlying [`proc_open()`](http://php.net/proc_open) function.
|
||||
|
||||
Once the process is started, the `$pipes` array will always contain references to
|
||||
all pipes as configured and the standard I/O references will always be set to
|
||||
reference the pipes matching common Unix conventions. This library supports any
|
||||
number of pipes and additional file descriptors, but many common applications
|
||||
being run as a child process will expect that the parent process properly
|
||||
assigns these file descriptors.
|
||||
|
||||
### Sigchild Compatibility
|
||||
|
||||
Internally, this project uses a work-around to improve compatibility when PHP
|
||||
has been compiled with the `--enable-sigchild` option. This should not affect most
|
||||
installations as this configure option is not used by default and many
|
||||
distributions (such as Debian and Ubuntu) are known to not use this by default.
|
||||
Some installations that use [Oracle OCI8](http://php.net/manual/en/book.oci8.php)
|
||||
may use this configure option to circumvent `defunct` processes.
|
||||
|
||||
When PHP has been compiled with the `--enable-sigchild` option, a child process'
|
||||
exit code cannot be reliably determined via `proc_close()` or `proc_get_status()`.
|
||||
To work around this, we execute the child process with an additional pipe and
|
||||
use that to retrieve its exit code.
|
||||
|
||||
This work-around incurs some overhead, so we only trigger this when necessary
|
||||
and when we detect that PHP has been compiled with the `--enable-sigchild` option.
|
||||
Because PHP does not provide a way to reliably detect this option, we try to
|
||||
inspect output of PHP's configure options from the `phpinfo()` function.
|
||||
|
||||
The static `setSigchildEnabled(bool $sigchild): void` method can be used to
|
||||
explicitly enable or disable this behavior like this:
|
||||
|
||||
```php
|
||||
// advanced: not recommended by default
|
||||
Process::setSigchildEnabled(true);
|
||||
```
|
||||
|
||||
Note that all processes instantiated after this method call will be affected.
|
||||
If this work-around is disabled on an affected PHP installation, the `exit`
|
||||
event may receive `null` instead of the actual exit code as described above.
|
||||
Similarly, some distributions are known to omit the configure options from
|
||||
`phpinfo()`, so automatic detection may fail to enable this work-around in some
|
||||
cases. You may then enable this explicitly as given above.
|
||||
|
||||
**Note:** The original functionality was taken from Symfony's
|
||||
[Process](https://github.com/symfony/process) compoment.
|
||||
|
||||
### Windows Compatibility
|
||||
|
||||
Due to platform constraints, this library provides only limited support for
|
||||
spawning child processes on Windows. In particular, PHP does not allow accessing
|
||||
standard I/O pipes on Windows without blocking. As such, this project will not
|
||||
allow constructing a child process with the default process pipes and will
|
||||
instead throw a `LogicException` on Windows by default:
|
||||
|
||||
```php
|
||||
// throws LogicException on Windows
|
||||
$process = new Process('ping example.com');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
There are a number of alternatives and workarounds as detailed below if you want
|
||||
to run a child process on Windows, each with its own set of pros and cons:
|
||||
|
||||
* As of PHP 8, you can start the child process with `socket` pair descriptors
|
||||
in place of normal standard I/O pipes like this:
|
||||
|
||||
```php
|
||||
$process = new Process(
|
||||
'ping example.com',
|
||||
null,
|
||||
null,
|
||||
[
|
||||
['socket'],
|
||||
['socket'],
|
||||
['socket']
|
||||
]
|
||||
);
|
||||
$process->start();
|
||||
|
||||
$process->stdout->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
```
|
||||
|
||||
These `socket` pairs support non-blocking process I/O on any platform,
|
||||
including Windows. However, not all programs accept stdio sockets.
|
||||
|
||||
* This package does work on
|
||||
[`Windows Subsystem for Linux`](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)
|
||||
(or WSL) without issues. When you are in control over how your application is
|
||||
deployed, we recommend [installing WSL](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide)
|
||||
when you want to run this package on Windows.
|
||||
|
||||
* If you only care about the exit code of a child process to check if its
|
||||
execution was successful, you can use [custom pipes](#custom-pipes) to omit
|
||||
any standard I/O pipes like this:
|
||||
|
||||
```php
|
||||
$process = new Process('ping example.com', null, null, array());
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($exitcode) {
|
||||
echo 'exit with ' . $exitcode . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, this is also useful if your child process communicates over
|
||||
sockets with remote servers or even your parent process using the
|
||||
[Socket component](https://github.com/reactphp/socket). This is usually
|
||||
considered the best alternative if you have control over how your child
|
||||
process communicates with the parent process.
|
||||
|
||||
* If you only care about command output after the child process has been
|
||||
executed, you can use [custom pipes](#custom-pipes) to configure file
|
||||
handles to be passed to the child process instead of pipes like this:
|
||||
|
||||
```php
|
||||
$process = new Process('ping example.com', null, null, array(
|
||||
array('file', 'nul', 'r'),
|
||||
$stdout = tmpfile(),
|
||||
array('file', 'nul', 'w')
|
||||
));
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($exitcode) use ($stdout) {
|
||||
echo 'exit with ' . $exitcode . PHP_EOL;
|
||||
|
||||
// rewind to start and then read full file (demo only, this is blocking).
|
||||
// reading from shared file is only safe if you have some synchronization in place
|
||||
// or after the child process has terminated.
|
||||
rewind($stdout);
|
||||
echo stream_get_contents($stdout);
|
||||
fclose($stdout);
|
||||
});
|
||||
```
|
||||
|
||||
Note that this example uses `tmpfile()`/`fopen()` for illustration purposes only.
|
||||
This should not be used in a truly async program because the filesystem is
|
||||
inherently blocking and each call could potentially take several seconds.
|
||||
See also the [Filesystem component](https://github.com/reactphp/filesystem) as an
|
||||
alternative.
|
||||
|
||||
* If you want to access command output as it happens in a streaming fashion,
|
||||
you can use redirection to spawn an additional process to forward your
|
||||
standard I/O streams to a socket and use [custom pipes](#custom-pipes) to
|
||||
omit any actual standard I/O pipes like this:
|
||||
|
||||
```php
|
||||
$server = new React\Socket\Server('127.0.0.1:0');
|
||||
$server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
$connection->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
|
||||
$command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress());
|
||||
$process = new Process($command, null, null, array());
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($exitcode) use ($server) {
|
||||
$server->close();
|
||||
echo 'exit with ' . $exitcode . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
Note how this will spawn another fictional `foobar` helper program to consume
|
||||
the standard output from the actual child process. This is in fact similar
|
||||
to the above recommendation of using socket connections in the child process,
|
||||
but in this case does not require modification of the actual child process.
|
||||
|
||||
In this example, the fictional `foobar` helper program can be implemented by
|
||||
simply consuming all data from standard input and forwarding it to a socket
|
||||
connection like this:
|
||||
|
||||
```php
|
||||
$socket = stream_socket_client($argv[1]);
|
||||
do {
|
||||
fwrite($socket, $data = fread(STDIN, 8192));
|
||||
} while (isset($data[0]));
|
||||
```
|
||||
|
||||
Accordingly, this example can also be run with plain PHP without having to
|
||||
rely on any external helper program like this:
|
||||
|
||||
```php
|
||||
$code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));';
|
||||
$command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress());
|
||||
$process = new Process($command, null, null, array());
|
||||
$process->start();
|
||||
```
|
||||
|
||||
See also [example #23](examples/23-forward-socket.php).
|
||||
|
||||
Note that this is for illustration purposes only and you may want to implement
|
||||
some proper error checks and/or socket verification in actual production use
|
||||
if you do not want to risk other processes connecting to the server socket.
|
||||
In this case, we suggest looking at the excellent
|
||||
[createprocess-windows](https://github.com/cubiclesoft/createprocess-windows).
|
||||
|
||||
Additionally, note that the [command](#command) given to the `Process` will be
|
||||
passed to the underlying Windows-API
|
||||
([`CreateProcess`](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa))
|
||||
as-is and the process will not be launched in a wrapping shell by default. In
|
||||
particular, this means that shell built-in functions such as `echo hello` or
|
||||
`sleep 10` may have to be prefixed with an explicit shell command like this:
|
||||
|
||||
```php
|
||||
$process = new Process('cmd /c echo hello', null, null, $pipes);
|
||||
$process->start();
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/child-process:^0.6.6
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
See above note for limited [Windows Compatibility](#windows-compatibility).
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
49
vendor/react/child-process/composer.json
vendored
Executable file
49
vendor/react/child-process/composer.json
vendored
Executable file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "react/child-process",
|
||||
"description": "Event-driven library for executing child processes with ReactPHP.",
|
||||
"keywords": ["process", "event-driven", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/socket": "^1.16",
|
||||
"sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\ChildProcess\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\ChildProcess\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
585
vendor/react/child-process/src/Process.php
vendored
Executable file
585
vendor/react/child-process/src/Process.php
vendored
Executable file
@@ -0,0 +1,585 @@
|
||||
<?php
|
||||
|
||||
namespace React\ChildProcess;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* Process component.
|
||||
*
|
||||
* This class borrows logic from Symfony's Process component for ensuring
|
||||
* compatibility when PHP is compiled with the --enable-sigchild option.
|
||||
*
|
||||
* This class also implements the `EventEmitterInterface`
|
||||
* which allows you to react to certain events:
|
||||
*
|
||||
* exit event:
|
||||
* The `exit` event will be emitted whenever the process is no longer running.
|
||||
* Event listeners will receive the exit code and termination signal as two
|
||||
* arguments:
|
||||
*
|
||||
* ```php
|
||||
* $process = new Process('sleep 10');
|
||||
* $process->start();
|
||||
*
|
||||
* $process->on('exit', function ($code, $term) {
|
||||
* if ($term === null) {
|
||||
* echo 'exit with code ' . $code . PHP_EOL;
|
||||
* } else {
|
||||
* echo 'terminated with signal ' . $term . PHP_EOL;
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that `$code` is `null` if the process has terminated, but the exit
|
||||
* code could not be determined (for example
|
||||
* [sigchild compatibility](#sigchild-compatibility) was disabled).
|
||||
* Similarly, `$term` is `null` unless the process has terminated in response to
|
||||
* an uncaught signal sent to it.
|
||||
* This is not a limitation of this project, but actual how exit codes and signals
|
||||
* are exposed on POSIX systems, for more details see also
|
||||
* [here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
|
||||
*
|
||||
* It's also worth noting that process termination depends on all file descriptors
|
||||
* being closed beforehand.
|
||||
* This means that all [process pipes](#stream-properties) will emit a `close`
|
||||
* event before the `exit` event and that no more `data` events will arrive after
|
||||
* the `exit` event.
|
||||
* Accordingly, if either of these pipes is in a paused state (`pause()` method
|
||||
* or internally due to a `pipe()` call), this detection may not trigger.
|
||||
*/
|
||||
class Process extends EventEmitter
|
||||
{
|
||||
/**
|
||||
* @var WritableStreamInterface|null|DuplexStreamInterface|ReadableStreamInterface
|
||||
*/
|
||||
public $stdin;
|
||||
|
||||
/**
|
||||
* @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
|
||||
*/
|
||||
public $stdout;
|
||||
|
||||
/**
|
||||
* @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
|
||||
*/
|
||||
public $stderr;
|
||||
|
||||
/**
|
||||
* Array with all process pipes (once started)
|
||||
*
|
||||
* Unless explicitly configured otherwise during construction, the following
|
||||
* standard I/O pipes will be assigned by default:
|
||||
* - 0: STDIN (`WritableStreamInterface`)
|
||||
* - 1: STDOUT (`ReadableStreamInterface`)
|
||||
* - 2: STDERR (`ReadableStreamInterface`)
|
||||
*
|
||||
* @var array<ReadableStreamInterface|WritableStreamInterface|DuplexStreamInterface>
|
||||
*/
|
||||
public $pipes = array();
|
||||
|
||||
private $cmd;
|
||||
private $cwd;
|
||||
private $env;
|
||||
private $fds;
|
||||
|
||||
private $enhanceSigchildCompatibility;
|
||||
private $sigchildPipe;
|
||||
|
||||
private $process;
|
||||
private $status;
|
||||
private $exitCode;
|
||||
private $fallbackExitCode;
|
||||
private $stopSignal;
|
||||
private $termSignal;
|
||||
|
||||
private static $sigchild;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $cmd Command line to run
|
||||
* @param null|string $cwd Current working directory or null to inherit
|
||||
* @param null|array $env Environment variables or null to inherit
|
||||
* @param null|array $fds File descriptors to allocate for this process (or null = default STDIO streams)
|
||||
* @throws \LogicException On windows or when proc_open() is not installed
|
||||
*/
|
||||
public function __construct($cmd, $cwd = null, $env = null, $fds = null)
|
||||
{
|
||||
if ($env !== null && !\is_array($env)) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($env) expected null|array');
|
||||
}
|
||||
if ($fds !== null && !\is_array($fds)) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #4 ($fds) expected null|array');
|
||||
}
|
||||
if (!\function_exists('proc_open')) {
|
||||
throw new \LogicException('The Process class relies on proc_open(), which is not available on your PHP installation.');
|
||||
}
|
||||
|
||||
$this->cmd = $cmd;
|
||||
$this->cwd = $cwd;
|
||||
|
||||
if (null !== $env) {
|
||||
$this->env = array();
|
||||
foreach ($env as $key => $value) {
|
||||
$this->env[(binary) $key] = (binary) $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fds === null) {
|
||||
$fds = array(
|
||||
array('pipe', 'r'), // stdin
|
||||
array('pipe', 'w'), // stdout
|
||||
array('pipe', 'w'), // stderr
|
||||
);
|
||||
}
|
||||
|
||||
if (\DIRECTORY_SEPARATOR === '\\') {
|
||||
foreach ($fds as $fd) {
|
||||
if (isset($fd[0]) && $fd[0] === 'pipe') {
|
||||
throw new \LogicException('Process pipes are not supported on Windows due to their blocking nature on Windows');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->fds = $fds;
|
||||
$this->enhanceSigchildCompatibility = self::isSigchildEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the process.
|
||||
*
|
||||
* After the process is started, the standard I/O streams will be constructed
|
||||
* and available via public properties.
|
||||
*
|
||||
* This method takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this process. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param ?LoopInterface $loop Loop interface for stream construction
|
||||
* @param float $interval Interval to periodically monitor process state (seconds)
|
||||
* @throws \RuntimeException If the process is already running or fails to start
|
||||
*/
|
||||
public function start($loop = null, $interval = 0.1)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
if ($this->isRunning()) {
|
||||
throw new \RuntimeException('Process is already running');
|
||||
}
|
||||
|
||||
$loop = $loop ?: Loop::get();
|
||||
$cmd = $this->cmd;
|
||||
$fdSpec = $this->fds;
|
||||
$sigchild = null;
|
||||
|
||||
// Read exit code through fourth pipe to work around --enable-sigchild
|
||||
if ($this->enhanceSigchildCompatibility) {
|
||||
$fdSpec[] = array('pipe', 'w');
|
||||
\end($fdSpec);
|
||||
$sigchild = \key($fdSpec);
|
||||
|
||||
// make sure this is fourth or higher (do not mess with STDIO)
|
||||
if ($sigchild < 3) {
|
||||
$fdSpec[3] = $fdSpec[$sigchild];
|
||||
unset($fdSpec[$sigchild]);
|
||||
$sigchild = 3;
|
||||
}
|
||||
|
||||
$cmd = \sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd);
|
||||
}
|
||||
|
||||
// on Windows, we do not launch the given command line in a shell (cmd.exe) by default and omit any error dialogs
|
||||
// the cmd.exe shell can explicitly be given as part of the command as detailed in both documentation and tests
|
||||
$options = array();
|
||||
if (\DIRECTORY_SEPARATOR === '\\') {
|
||||
$options['bypass_shell'] = true;
|
||||
$options['suppress_errors'] = true;
|
||||
}
|
||||
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// proc_open(/dev/does-not-exist): Failed to open stream: No such file or directory
|
||||
$errstr = $error;
|
||||
});
|
||||
|
||||
$pipes = array();
|
||||
$this->process = @\proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env, $options);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (!\is_resource($this->process)) {
|
||||
throw new \RuntimeException('Unable to launch a new process: ' . $errstr);
|
||||
}
|
||||
|
||||
// count open process pipes and await close event for each to drain buffers before detecting exit
|
||||
$that = $this;
|
||||
$closeCount = 0;
|
||||
$streamCloseHandler = function () use (&$closeCount, $loop, $interval, $that) {
|
||||
$closeCount--;
|
||||
|
||||
if ($closeCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// process already closed => report immediately
|
||||
if (!$that->isRunning()) {
|
||||
$that->close();
|
||||
$that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
|
||||
return;
|
||||
}
|
||||
|
||||
// close not detected immediately => check regularly
|
||||
$loop->addPeriodicTimer($interval, function ($timer) use ($that, $loop) {
|
||||
if (!$that->isRunning()) {
|
||||
$loop->cancelTimer($timer);
|
||||
$that->close();
|
||||
$that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if ($sigchild !== null) {
|
||||
$this->sigchildPipe = $pipes[$sigchild];
|
||||
unset($pipes[$sigchild]);
|
||||
}
|
||||
|
||||
foreach ($pipes as $n => $fd) {
|
||||
// use open mode from stream meta data or fall back to pipe open mode for legacy HHVM
|
||||
$meta = \stream_get_meta_data($fd);
|
||||
$mode = $meta['mode'] === '' ? ($this->fds[$n][1] === 'r' ? 'w' : 'r') : $meta['mode'];
|
||||
|
||||
if ($mode === 'r+') {
|
||||
$stream = new DuplexResourceStream($fd, $loop);
|
||||
$stream->on('close', $streamCloseHandler);
|
||||
$closeCount++;
|
||||
} elseif ($mode === 'w') {
|
||||
$stream = new WritableResourceStream($fd, $loop);
|
||||
} else {
|
||||
$stream = new ReadableResourceStream($fd, $loop);
|
||||
$stream->on('close', $streamCloseHandler);
|
||||
$closeCount++;
|
||||
}
|
||||
$this->pipes[$n] = $stream;
|
||||
}
|
||||
|
||||
$this->stdin = isset($this->pipes[0]) ? $this->pipes[0] : null;
|
||||
$this->stdout = isset($this->pipes[1]) ? $this->pipes[1] : null;
|
||||
$this->stderr = isset($this->pipes[2]) ? $this->pipes[2] : null;
|
||||
|
||||
// immediately start checking for process exit when started without any I/O pipes
|
||||
if (!$closeCount) {
|
||||
$streamCloseHandler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the process.
|
||||
*
|
||||
* This method should only be invoked via the periodic timer that monitors
|
||||
* the process state.
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->pipes as $pipe) {
|
||||
$pipe->close();
|
||||
}
|
||||
|
||||
if ($this->enhanceSigchildCompatibility) {
|
||||
$this->pollExitCodePipe();
|
||||
$this->closeExitCodePipe();
|
||||
}
|
||||
|
||||
$exitCode = \proc_close($this->process);
|
||||
$this->process = null;
|
||||
|
||||
if ($this->exitCode === null && $exitCode !== -1) {
|
||||
$this->exitCode = $exitCode;
|
||||
}
|
||||
|
||||
if ($this->exitCode === null && $this->status['exitcode'] !== -1) {
|
||||
$this->exitCode = $this->status['exitcode'];
|
||||
}
|
||||
|
||||
if ($this->exitCode === null && $this->fallbackExitCode !== null) {
|
||||
$this->exitCode = $this->fallbackExitCode;
|
||||
$this->fallbackExitCode = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate the process with an optional signal.
|
||||
*
|
||||
* @param int $signal Optional signal (default: SIGTERM)
|
||||
* @return bool Whether the signal was sent successfully
|
||||
*/
|
||||
public function terminate($signal = null)
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($signal !== null) {
|
||||
return \proc_terminate($this->process, $signal);
|
||||
}
|
||||
|
||||
return \proc_terminate($this->process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command string used to launch the process.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exit code returned by the process.
|
||||
*
|
||||
* This value is only meaningful if isRunning() has returned false. Null
|
||||
* will be returned if the process is still running.
|
||||
*
|
||||
* Null may also be returned if the process has terminated, but the exit
|
||||
* code could not be determined (e.g. sigchild compatibility was disabled).
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getExitCode()
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the process ID.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPid()
|
||||
{
|
||||
$status = $this->getCachedStatus();
|
||||
|
||||
return $status !== null ? $status['pid'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signal that caused the process to stop its execution.
|
||||
*
|
||||
* This value is only meaningful if isStopped() has returned true. Null will
|
||||
* be returned if the process was never stopped.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getStopSignal()
|
||||
{
|
||||
return $this->stopSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signal that caused the process to terminate its execution.
|
||||
*
|
||||
* This value is only meaningful if isTerminated() has returned true. Null
|
||||
* will be returned if the process was never terminated.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getTermSignal()
|
||||
{
|
||||
return $this->termSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the process is still running.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRunning()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = $this->getFreshStatus();
|
||||
|
||||
return $status !== null ? $status['running'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the process has been stopped by a signal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isStopped()
|
||||
{
|
||||
$status = $this->getFreshStatus();
|
||||
|
||||
return $status !== null ? $status['stopped'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the process has been terminated by an uncaught signal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTerminated()
|
||||
{
|
||||
$status = $this->getFreshStatus();
|
||||
|
||||
return $status !== null ? $status['signaled'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether PHP has been compiled with the '--enable-sigchild' option.
|
||||
*
|
||||
* @see \Symfony\Component\Process\Process::isSigchildEnabled()
|
||||
* @return bool
|
||||
*/
|
||||
public final static function isSigchildEnabled()
|
||||
{
|
||||
if (null !== self::$sigchild) {
|
||||
return self::$sigchild;
|
||||
}
|
||||
|
||||
if (!\function_exists('phpinfo')) {
|
||||
return self::$sigchild = false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
\ob_start();
|
||||
\phpinfo(INFO_GENERAL);
|
||||
|
||||
return self::$sigchild = false !== \strpos(\ob_get_clean(), '--enable-sigchild');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable sigchild compatibility mode.
|
||||
*
|
||||
* Sigchild compatibility mode is required to get the exit code and
|
||||
* determine the success of a process when PHP has been compiled with
|
||||
* the --enable-sigchild option.
|
||||
*
|
||||
* @param bool $sigchild
|
||||
* @return void
|
||||
*/
|
||||
public final static function setSigchildEnabled($sigchild)
|
||||
{
|
||||
self::$sigchild = (bool) $sigchild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the fourth pipe for an exit code.
|
||||
*
|
||||
* This should only be used if --enable-sigchild compatibility was enabled.
|
||||
*/
|
||||
private function pollExitCodePipe()
|
||||
{
|
||||
if ($this->sigchildPipe === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$r = array($this->sigchildPipe);
|
||||
$w = $e = null;
|
||||
|
||||
$n = @\stream_select($r, $w, $e, 0);
|
||||
|
||||
if (1 !== $n) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \fread($r[0], 8192);
|
||||
|
||||
if (\strlen($data) > 0) {
|
||||
$this->fallbackExitCode = (int) $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the fourth pipe used to relay an exit code.
|
||||
*
|
||||
* This should only be used if --enable-sigchild compatibility was enabled.
|
||||
*/
|
||||
private function closeExitCodePipe()
|
||||
{
|
||||
if ($this->sigchildPipe === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
\fclose($this->sigchildPipe);
|
||||
$this->sigchildPipe = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cached process status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCachedStatus()
|
||||
{
|
||||
if ($this->status === null) {
|
||||
$this->updateStatus();
|
||||
}
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the updated process status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getFreshStatus()
|
||||
{
|
||||
$this->updateStatus();
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the process status, stop/term signals, and exit code.
|
||||
*
|
||||
* Stop/term signals are only updated if the process is currently stopped or
|
||||
* signaled, respectively. Otherwise, signal values will remain as-is so the
|
||||
* corresponding getter methods may be used at a later point in time.
|
||||
*/
|
||||
private function updateStatus()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status = \proc_get_status($this->process);
|
||||
|
||||
if ($this->status === false) {
|
||||
throw new \UnexpectedValueException('proc_get_status() failed');
|
||||
}
|
||||
|
||||
if ($this->status['stopped']) {
|
||||
$this->stopSignal = $this->status['stopsig'];
|
||||
}
|
||||
|
||||
if ($this->status['signaled']) {
|
||||
$this->termSignal = $this->status['termsig'];
|
||||
}
|
||||
|
||||
if (!$this->status['running'] && -1 !== $this->status['exitcode']) {
|
||||
$this->exitCode = $this->status['exitcode'];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user