@@ -46,6 +46,15 @@ class Shell
4646 const STATE_CLOSED = 'closed ' ;
4747 const STATE_TERMINATED = 'terminated ' ;
4848
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+
4958 /** @var bool Whether to wait for the process to finish or return instantly */
5059 protected bool $ async = false ;
5160
@@ -98,25 +107,40 @@ public function __construct(protected string $command, protected ?string $input
98107 $ this ->input = $ input ;
99108 }
100109
101- protected function getDescriptors ( ): array
110+ protected function prepareDescriptors (? array $ stdin = null , ? array $ stdout = null , ? array $ stderr = null ): array
102111 {
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+ }
105122 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 ,
109126 ];
110127 }
111128
112129 protected function isWindows (): bool
113130 {
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...)
115136 }
116137
117138 protected function setInput (): void
118139 {
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+ }
120144 }
121145
122146 protected function updateProcessStatus (): void
@@ -132,9 +156,16 @@ protected function updateProcessStatus(): void
132156
133157 protected function closePipes (): void
134158 {
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+ }
138169 }
139170
140171 protected function wait (): ?int
@@ -177,14 +208,25 @@ public function setOptions(
177208
178209 return $ this ;
179210 }
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
182224 {
183225 if ($ this ->isRunning ()) {
184226 throw new RuntimeException ('Process is already running. ' );
185227 }
186228
187- $ this ->descriptors = $ this ->getDescriptors ( );
229+ $ this ->descriptors = $ this ->prepareDescriptors ( $ stdin , $ stdout , $ stderr );
188230 $ this ->processStartTime = microtime (true );
189231
190232 $ this ->process = proc_open (
@@ -218,6 +260,10 @@ public function execute(bool $async = false): self
218260
219261 private function setOutputStreamNonBlocking (): bool
220262 {
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+ }
221267 return stream_set_blocking ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ], false );
222268 }
223269
@@ -228,11 +274,18 @@ public function getState(): string
228274
229275 public function getOutput (): string
230276 {
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+ }
231281 return stream_get_contents ($ this ->pipes [self ::STDOUT_DESCRIPTOR_KEY ]);
232282 }
233283
234284 public function getErrorOutput (): string
235285 {
286+ if (!is_resource ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ])) {
287+ return '' ;
288+ }
236289 return stream_get_contents ($ this ->pipes [self ::STDERR_DESCRIPTOR_KEY ]);
237290 }
238291
0 commit comments