autoload
[m6w6/atick] / lib / atick / Proc.php
1 <?php
2
3 namespace atick;
4
5 class Proc implements Able
6 {
7 /**
8 * Command string
9 * @var string
10 */
11 protected $command;
12
13 /**
14 * Process handle
15 * @var resource
16 */
17 protected $proc;
18
19 /**
20 * Proc's pipes
21 * @var array
22 */
23 protected $pipes;
24
25 protected $read;
26 protected $error;
27
28 /**
29 * @param string $command
30 * @param string $cwd
31 * @param array $env
32 * @throws \RuntimeException
33 */
34 function __construct($command, $cwd = null, array $env = null) {
35 $this->command = $command;
36 $this->proc = proc_open($command, [["pipe","r"],["pipe","w"],["pipe","w"]], $this->pipes, $cwd, $env);
37
38 if (!is_resource($this->proc) || !($status = proc_get_status($this->proc))) {
39 throw new \RuntimeException("Could not open proc '$command': " . error_get_last()["message"]);
40 }
41
42 stream_set_blocking($this->pipes[1], false);
43 stream_set_blocking($this->pipes[2], false);
44 }
45
46 /**
47 * Returns the command string
48 * @return string
49 */
50 function __toString() {
51 return (string) $this->command;
52 }
53
54 /**
55 * Cleanup pipes and proc handle
56 */
57 function __destruct() {
58 $this->close();
59 }
60
61 /**
62 * @inheritdoc
63 * @implements \aticker\Able
64 * @param \atick\Ticker $ticker
65 * @param callable $verify
66 */
67 function with(Ticker $ticker, callable $verify = null) {
68 if (is_callable($this->read)) {
69 $ticker->read($this->pipes[1], $this->read, $verify ?: array($this, "stat"));
70 } elseif (is_resource($this->read)) {
71 $ticker->read($this->pipes[1], function($fd) {
72 if (strlen($data = fread($fd, 8192))) {
73 fwrite($this->read, $data);
74 }
75 }, $verify ?: array($this, "stat"));
76 } else {
77 $ticker->read($this->pipes[1], function($fd) {
78 /* nirvana */
79 fread($fd, 8192);
80 }, $verify ?: array($this, "stat"));
81 }
82
83 if (is_callable($this->error)) {
84 $ticker->read($this->pipes[2], $this->error, $verify ?: array($this, "stat"));
85 } elseif (is_resource($this->error)) {
86 $ticker->read($this->pipes[2], function($fd) {
87 if (strlen($data = fread($fd, 8192))) {
88 fwrite($this->error, $data);
89 }
90 }, $verify ?: array($this, "stat"));
91 } else {
92 $ticker->read($this->pipes[2], function($fd) {
93 /* nirvana */
94 fread($fd, 8192);
95 }, $verify ?: array($this, "stat"));
96 }
97 }
98
99 function stat() {
100 echo "STAT $this\n";
101 if ($this->proc && proc_get_status($this->proc)["running"]) {
102 if ((isset($this->pipes[1]) && is_resource($this->pipes[1]) && !feof($this->pipes[1]))
103 && (isset($this->pipes[2]) && is_resource($this->pipes[2]) && !feof($this->pipes[2]))
104 ) {
105 if (isset($this->pipes[0]) && is_resource($this->pipes[0]) && !feof($this->pipes[0])) {
106 return self::READABLE | self::WRITABLE;
107 }
108 return self::READABLE;
109 }
110 }
111 $this->close();
112 return self::CLOSED;
113 }
114
115 function close($what = self::CLOSED) {
116 echo "PROC KILL $this $what\n";
117
118 if (!$what || ($what & self::WRITABLE)) {
119 if (is_resource($this->pipes[0])) {
120 fclose($this->pipes[0]);
121 }
122 $this->pipes[0] = null;
123 }
124
125 if (!$what || ($what & self::READABLE)) {
126 if (is_resource($this->pipes[1])) {
127 fclose($this->pipes[1]);
128 }
129 $this->pipes[1] = null;
130 if (is_resource($this->read)) {
131 fclose($this->read);
132 }
133 if (is_resource($this->pipes[2])) {
134 fclose($this->pipes[2]);
135 }
136 $this->pipes[2] = null;
137 if (is_resource($this->error)) {
138 fclose($this->error);
139 }
140 }
141
142 if (!$what && is_resource($this->proc)) {
143 proc_close($this->proc);
144 $this->proc = null;
145 }
146 }
147
148 /**
149 * @inheritdoc
150 * @implements \aticker\Able
151 * @param string $data
152 * @return int
153 */
154 function write($data) {
155 return fwrite($this->pipes[0], $data);
156 }
157
158 /**
159 * Where to read STDOUT into
160 * @param resource|callable $into
161 * @return \atick\Proc
162 * @throws \InvalidArgumentException
163 */
164 function read($into) {
165 if (is_resource($into) || is_callable($into)) {
166 $this->read = $into;
167 } else {
168 throw new \InvalidArgumentException("Not a valid resource or callback");
169 }
170 return $this;
171 }
172
173 /**
174 * Where to pass STDERR into
175 * @param resource|callable $into
176 * @return \atick\Proc
177 * @throws \InvalidArgumentException
178 */
179 function error($into) {
180 if (is_resource($into) || is_callable($into)) {
181 $this->error = $into;
182 } else {
183 throw new \InvalidArgumentException("Not a valid resource or callback");
184 }
185 return $this;
186 }
187 }