Completed
Pull Request — master (#3)
by thomas
17:44
created

IPC::readStdOut()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\PinEntry\Process;
6
7
class IPC
8
{
9
    const STDIN = 0;
10
    const STDOUT = 1;
11
    const STDERR = 2;
12
13
    /**
14
     * Contains a default file descriptor definitions
15
     * for the STDIN, STDOUT, STDERR streams.
16
     * @var array
17
     */
18
    protected static $defaultDescriptor = [
19
        // Define the STDIN pipe the child will read from
20
        self::STDIN => ["pipe", "r"],
21
22
        // Define the STDOUT pipe the child will write to
23
        self::STDOUT => ["pipe", "w"],
24
25
        // Define the STDERR pipe the child will write to. Can also
26
        // specify a file instead of writing to this process.
27
        self::STDERR => ["pipe", "w"],
28
    ];
29
30
    /**
31
     * @var resource[]
32
     */
33
    private $fhList = [];
34
35
    public function __construct(array $fileHandles)
36
    {
37
        $missingKeys = array_diff_key(self::$defaultDescriptor, $fileHandles);
38
        if (count($missingKeys) > 0) {
39
            throw new \InvalidArgumentException("Missing required file handle ({$missingKeys[0]})");
40
        }
41
42
        foreach ($fileHandles as $fhKey => $fh) {
43
            if (!(is_resource($fh) && get_resource_type($fh) === "stream")) {
44
                throw new \InvalidArgumentException("Invalid file handle ({$fhKey})");
45
            }
46
        }
47
48
        stream_set_blocking($fileHandles[self::STDOUT], false);
49
        stream_set_blocking($fileHandles[self::STDERR], false);
50
51
        $this->fhList = $fileHandles;
52
    }
53
54
    public function close()
55
    {
56
        fclose($this->fhList[self::STDIN]);
57
        fclose($this->fhList[self::STDOUT]);
58
        fclose($this->fhList[self::STDERR]);
59
    }
60
61
    /**
62
     * @param string $data
63
     * @return int
64
     */
65
    public function send(string $data): int
66
    {
67
        $write = fwrite($this->fhList[self::STDIN], $data);
68
        if ($write === false) {
69
            throw new \RuntimeException("Failed to write to process stdin");
70
        }
71
        return $write;
72
    }
73
74
    public function readStdOut(): string
75
    {
76
        return $this->blockingRead($this->fhList[self::STDOUT]);
77
    }
78
79
    /**
80
     * @param resource $fh
81
     * @return string
82
     */
83
    private function blockingRead($fh)
84
    {
85
        $rx = [$fh];
86
        $wx = [];
87
        $ex = [];
88
        // This will pause execution until the stream changes state,
89
        // most likely indicating it is ready to be read.
90
        if (false === stream_select($rx, $wx, $ex, null, 0)) {
91
            throw new \RuntimeException("stream_select failed");
92
        }
93
94
        // maybe we should inspect $rx to see what the new status is before reading
95
        $buffer = stream_get_contents($fh);
96
        if ($buffer === false) {
97
            throw new \RuntimeException("Reading from stream failed");
98
        }
99
100
        assert($buffer !== "");
101
102
        return $buffer;
103
    }
104
105
    /**
106
     * Return the default descriptors values, overloaded with
107
     * the value in $overrideDescriptors if the same key is set there.
108
     *
109
     * @param array[] $overrideDescriptors
110
     * @return array[]
111
     */
112
    public static function buildDescriptors(array $overrideDescriptors = []): array
113
    {
114
        $descriptor = static::$defaultDescriptor;
115
        foreach (array_intersect_key($descriptor, $overrideDescriptors) as $key => $value) {
116
            $descriptor[$key] = $overrideDescriptors[$key];
117
        }
118
        return $descriptor;
119
    }
120
}
121