22
33namespace Clue \React \Stdio ;
44
5- use React \Stream \ReadableStream ;
65use React \Stream \Stream ;
76use React \EventLoop \LoopInterface ;
87
@@ -13,40 +12,77 @@ class Stdin extends Stream
1312
1413 public function __construct (LoopInterface $ loop )
1514 {
15+ // STDIN not defined ("php -a") or already closed (`fclose(STDIN)`)
16+ if (!defined ('STDIN ' ) || !is_resource (STDIN )) {
17+ parent ::__construct (fopen ('php://memory ' , 'r ' ), $ loop );
18+ return $ this ->close ();
19+ }
20+
1621 parent ::__construct (STDIN , $ loop );
17- }
1822
19- public function resume ()
20- {
21- if ($ this ->oldMode === null ) {
23+ // support starting program with closed STDIN ("example.php 0<&-")
24+ // the stream is a valid resource and is not EOF, but fstat fails
25+ if (fstat (STDIN ) === false ) {
26+ return $ this ->close ();
27+ }
28+
29+ if ($ this ->isTty ()) {
2230 $ this ->oldMode = shell_exec ('stty -g ' );
2331
2432 // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
2533 shell_exec ('stty -icanon -echo ' );
26-
27- parent ::resume ();
2834 }
2935 }
3036
31- public function pause ()
37+ public function close ()
38+ {
39+ $ this ->restore ();
40+ parent ::close ();
41+ }
42+
43+ public function __destruct ()
3244 {
33- if ($ this ->oldMode !== null ) {
45+ $ this ->restore ();
46+ }
47+
48+ private function restore ()
49+ {
50+ if ($ this ->oldMode !== null && $ this ->isTty ()) {
3451 // Reset stty so it behaves normally again
3552 shell_exec (sprintf ('stty %s ' , $ this ->oldMode ));
36-
3753 $ this ->oldMode = null ;
38- parent ::pause ();
3954 }
4055 }
4156
42- public function close ()
57+ /**
58+ * @return bool
59+ * @codeCoverageIgnore
60+ */
61+ private function isTty ()
4362 {
44- $ this ->pause ();
45- parent ::close ();
46- }
63+ if (PHP_VERSION_ID >= 70200 ) {
64+ // Prefer `stream_isatty()` (available as of PHP 7.2 only)
65+ return stream_isatty (STDIN );
66+ } elseif (function_exists ('posix_isatty ' )) {
67+ // Otherwise use `posix_isatty` if available (requires `ext-posix`)
68+ return posix_isatty (STDIN );
69+ }
4770
48- public function __destruct ()
49- {
50- $ this ->pause ();
71+ // otherwise try to guess based on stat file mode and device major number
72+ // Must be special character device: ($mode & S_IFMT) === S_IFCHR
73+ // And device major number must be allocated to TTYs (2-5 and 128-143)
74+ // For what it's worth, checking for device gid 5 (tty) is less reliable.
75+ // @link http://man7.org/linux/man-pages/man7/inode.7.html
76+ // @link https://www.kernel.org/doc/html/v4.11/admin-guide/devices.html#terminal-devices
77+ if (is_resource (STDIN )) {
78+ $ stat = fstat (STDIN );
79+ $ mode = isset ($ stat ['mode ' ]) ? ($ stat ['mode ' ] & 0170000 ) : 0 ;
80+ $ major = isset ($ stat ['dev ' ]) ? (($ stat ['dev ' ] >> 8 ) & 0xff ) : 0 ;
81+
82+ if ($ mode === 0020000 && $ major >= 2 && $ major <= 143 && ($ major <=5 || $ major >= 128 )) {
83+ return true ;
84+ }
85+ }
86+ return false ;
5187 }
5288}
0 commit comments