@@ -46,6 +46,15 @@ class Shell
46
46
const STATE_CLOSED = 'closed ' ;
47
47
const STATE_TERMINATED = 'terminated ' ;
48
48
49
+ const DEFAULT_STDIN_WIN = ['pipe ' , 'r ' ];
50
+ const DEFAULT_STDIN_NIX = ['pipe ' , 'r ' ];
51
+
52
+ const DEFAULT_STDOUT_WIN = ['pipe ' , 'w ' ];
53
+ const DEFAULT_STDOUT_NIX = ['pipe ' , 'w ' ];
54
+
55
+ const DEFAULT_STDERR_WIN = ['pipe ' , 'w ' ];
56
+ const DEFAULT_STDERR_NIX = ['pipe ' , 'w ' ];
57
+
49
58
/** @var bool Whether to wait for the process to finish or return instantly */
50
59
protected bool $ async = false ;
51
60
@@ -98,25 +107,40 @@ public function __construct(protected string $command, protected ?string $input
98
107
$ this ->input = $ input ;
99
108
}
100
109
101
- protected function getDescriptors ( ): array
110
+ protected function prepareDescriptors (? array $ stdin = null , ? array $ stdout = null , ? array $ stderr = null ): array
102
111
{
103
- $ out = $ this ->isWindows () ? ['file ' , 'NUL ' , 'w ' ] : ['pipe ' , 'w ' ];
104
-
112
+ $ win = $ this ->isWindows ();
113
+ if (!$ stdin ) {
114
+ $ stdin = $ win ? self ::DEFAULT_STDIN_WIN : self ::DEFAULT_STDIN_NIX ;
115
+ }
116
+ if (!$ stdout ) {
117
+ $ stdout = $ win ? self ::DEFAULT_STDOUT_WIN : self ::DEFAULT_STDOUT_NIX ;
118
+ }
119
+ if (!$ stderr ) {
120
+ $ stderr = $ win ? self ::DEFAULT_STDERR_WIN : self ::DEFAULT_STDERR_NIX ;
121
+ }
105
122
return [
106
- self ::STDIN_DESCRIPTOR_KEY => [ ' pipe ' , ' r ' ] ,
107
- self ::STDOUT_DESCRIPTOR_KEY => $ out ,
108
- self ::STDERR_DESCRIPTOR_KEY => $ out ,
123
+ self ::STDIN_DESCRIPTOR_KEY => $ stdin ,
124
+ self ::STDOUT_DESCRIPTOR_KEY => $ stdout ,
125
+ self ::STDERR_DESCRIPTOR_KEY => $ stderr ,
109
126
];
110
127
}
111
128
112
129
protected function isWindows (): bool
113
130
{
114
- return '\\' === DIRECTORY_SEPARATOR ;
131
+ // If PHP_OS is defined, use it - More reliable:
132
+ if (defined ('PHP_OS ' )) {
133
+ return 'WIN ' === strtoupper (substr (PHP_OS , 0 , 3 )); // May be 'WINNT' or 'WIN32' or 'Windows'
134
+ }
135
+ return '\\' === DIRECTORY_SEPARATOR ; // Fallback - Less reliable (Windows 7...)
115
136
}
116
137
117
138
protected function setInput (): void
118
139
{
119
- fwrite ($ this ->pipes [self ::STDIN_DESCRIPTOR_KEY ], $ this ->input ?? '' );
140
+ //Make sure the pipe is a stream resource before writing to it to avoid a warning
141
+ if (is_resource ($ this ->pipes [self ::STDIN_DESCRIPTOR_KEY ])) {
142
+ fwrite ($ this ->pipes [self ::STDIN_DESCRIPTOR_KEY ], $ this ->input ?? '' );
143
+ }
120
144
}
121
145
122
146
protected function updateProcessStatus (): void
@@ -132,9 +156,16 @@ protected function updateProcessStatus(): void
132
156
133
157
protected function closePipes (): void
134
158
{
135
- fclose ($ this ->pipes [self ::STDIN_DESCRIPTOR_KEY ]);
136
- fclose ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ]);
137
- fclose ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ]);
159
+ //Make sure the pipe are a stream resource before closing them to avoid a warning
160
+ if (is_resource ($ this ->pipes [self ::STDIN_DESCRIPTOR_KEY ])) {
161
+ fclose ($ this ->pipes [self ::STDIN_DESCRIPTOR_KEY ]);
162
+ }
163
+ if (is_resource ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ])) {
164
+ fclose ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ]);
165
+ }
166
+ if (is_resource ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ])) {
167
+ fclose ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ]);
168
+ }
138
169
}
139
170
140
171
protected function wait (): ?int
@@ -177,14 +208,25 @@ public function setOptions(
177
208
178
209
return $ this ;
179
210
}
180
-
181
- public function execute (bool $ async = false ): self
211
+
212
+ /**
213
+ * execute
214
+ * Execute the command with optional stdin, stdout and stderr which override the defaults
215
+ * If async is set to true, the process will be executed in the background
216
+ *
217
+ * @param bool $async - default false
218
+ * @param ?array $stdin - default null (loads default descriptor)
219
+ * @param ?array $stdout - default null (loads default descriptor)
220
+ * @param ?array $stderr - default null (loads default descriptor)
221
+ * @return self
222
+ */
223
+ public function execute (bool $ async = false , ?array $ stdin = null , ?array $ stdout = null , ?array $ stderr = null ): self
182
224
{
183
225
if ($ this ->isRunning ()) {
184
226
throw new RuntimeException ('Process is already running. ' );
185
227
}
186
228
187
- $ this ->descriptors = $ this ->getDescriptors ( );
229
+ $ this ->descriptors = $ this ->prepareDescriptors ( $ stdin , $ stdout , $ stderr );
188
230
$ this ->processStartTime = microtime (true );
189
231
190
232
$ this ->process = proc_open (
@@ -218,6 +260,10 @@ public function execute(bool $async = false): self
218
260
219
261
private function setOutputStreamNonBlocking (): bool
220
262
{
263
+ // Make sure the pipe is a stream resource before setting it to non-blocking to avoid a warning
264
+ if (!is_resource ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ])) {
265
+ return false ;
266
+ }
221
267
return stream_set_blocking ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ], false );
222
268
}
223
269
@@ -228,11 +274,18 @@ public function getState(): string
228
274
229
275
public function getOutput (): string
230
276
{
277
+ // Make sure the pipe is a stream resource before reading it to avoid a warning
278
+ if (!is_resource ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ])) {
279
+ return '' ;
280
+ }
231
281
return stream_get_contents ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ]);
232
282
}
233
283
234
284
public function getErrorOutput (): string
235
285
{
286
+ if (!is_resource ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ])) {
287
+ return '' ;
288
+ }
236
289
return stream_get_contents ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ]);
237
290
}
238
291
0 commit comments