Files
nih-php-command-builder/src/Child.php
2025-08-17 17:16:56 +02:00

173 lines
4.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Nih\CommandBuilder;
/**
* Representation of a running or exited child process.
*/
final class Child
{
/**
* The child's process handle.
*
* @var resource
*/
private readonly mixed $process;
/**
* The child's process identifier.
*/
private readonly int $id;
/**
* The child's exit status.
*/
private ?ExitStatus $status = null;
/**
* The handle for writing to the childs standard input (stdin), if it has
* been captured.
*/
public readonly ?ChildStdin $stdin;
/**
* The handle for reading from the childs standard output (stdout), if it
* has been captured.
*/
public readonly ?ChildStdout $stdout;
/**
* The handle for reading from the childs standard error (stderr), if it
* has been captured.
*/
public readonly ?ChildStderr $stderr;
/**
* @param resource $process The child's process handle.
* @param array<int, resource> $pipes File pointers.
*/
public function __construct(mixed $process, array $pipes)
{
$this->process = $process;
$status = proc_get_status($this->process);
$this->id = $status['pid'];
$this->stdin = array_key_exists(0, $pipes)
? new ChildStdin($pipes[0])
: null;
$this->stdout = array_key_exists(1, $pipes)
? new ChildStdout($pipes[1])
: null;
$this->stderr = array_key_exists(2, $pipes)
? new ChildStderr($pipes[2])
: null;
}
/**
* Returns the OS-assigned process identifier associated with this child.
*/
public function id(): int
{
return $this->id;
}
/**
* Waits for the child to exit completely, returning the status that it
* exited with. This function will continue to have the same return value
* after it has been called at least once.
*
* The stdin handle to the child process, if any, will be closed before
* waiting. This helps avoid deadlock: it ensures that the child does not
* block waiting for input from the parent, while the parent waits for the
* child to exit.
*
* @throws ChildException If the resource was already closed
*/
public function wait(): ExitStatus
{
if ($this->status) {
return $this->status;
}
if (!is_resource($this->process)) {
throw new ChildException('Resource was already closed');
}
// Avoid possible deadlock before waiting.
$this->stdin?->close();
$status = proc_get_status($this->process);
while ($status['running']) {
// Suboptimal, but it is what it is...
usleep(50);
$status = proc_get_status($this->process);
};
proc_close($this->process);
return $this->status = new ExitStatus(
$status['exitcode'],
$status['signaled'] ? $status['termsig'] : null,
$status['stopped'] ? $status['stopsig'] : null,
);
}
/**
* Simultaneously waits for the child to exit and collect all remaining
* output on the stdout/stderr handles, returning an {@see Output} instance.
*
* The stdin handle to the child process, if any, will be closed before
* waiting. This helps avoid deadlock: it ensures that the child does not
* block waiting for input from the parent, while the parent waits for the
* child to exit.
*
* By default, stdin, stdout and stderr are inherited from the parent. In
* order to capture the output it is necessary to create new pipes between
* parent and child. Use the `stdout` and `stderr` functions of {@see
* Command}, respectively.
*
* @throws ChildException If the resource was already closed
*/
public function waitWithOutput(): Output
{
if (!is_resource($this->process)) {
throw new ChildException('Resource was already closed');
}
// Avoid possible deadlock before waiting.
$this->stdin?->close();
$stdout = $this->stdout?->getContents();
$stderr = $this->stderr?->getContents();
$status = $this->wait();
return new Output($stdout, $stderr, $status);
}
/**
* Forces the child process to exit.
*
* This is equivalent to sending a SIGKILL.
*/
public function kill(): bool
{
if (!is_resource($this->process)) {
return true;
}
return proc_terminate($this->process, 9);
}
public function __destruct()
{
if (is_resource($this->process)) {
proc_close($this->process);
}
}
}