58 public const TOKEN_DELIMITER =
":";
59 public const TOKEN_HASH_ALGO =
"xxh3";
61 private \PrefixedLogger $logger;
66 private int $commandTokenSeed;
68 public function __construct(
71 $this->logger = new \PrefixedLogger($logger,
"Console Reader Daemon");
72 $this->commandTokenSeed = mt_rand();
73 $this->prepareSubprocess();
76 private function prepareSubprocess() :
void{
79 $sub = Utils::assumeNotFalse(proc_open(
82 '-dopcache.enable_cli=0',
84 sprintf(
'require base64_decode("%s", true);', base64_encode(Path::join(__DIR__,
'ConsoleReaderChildProcess.php'))),
85 (
string) $this->commandTokenSeed
89 2 => fopen(
"php://stderr",
"w"),
92 ),
"Something has gone horribly wrong");
94 $this->subprocess = $sub;
95 $this->socket = $pipes[1];
98 private function shutdownSubprocess() :
void{
102 proc_terminate($this->subprocess);
103 proc_close($this->subprocess);
106 public function readLine() : ?
string{
107 $r = [$this->socket];
110 if(stream_select($r, $w, $e, 0, 0) === 1){
111 $line = fgets($this->socket);
113 $this->logger->debug(
"Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
114 $this->shutdownSubprocess();
115 $this->prepareSubprocess();
118 $line = rtrim($line,
"\n");
126 if($command ===
null){
129 $this->logger->warning(
"Unexpected output from child process: $line");
133 $command = preg_replace(
"#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#",
"", trim($command)) ??
throw new AssumptionFailedError(
"This regex is assumed to be valid");
134 $command = preg_replace(
'/[[:cntrl:]]/',
'', $command) ??
throw new AssumptionFailedError(
"This regex is assumed to be valid");
136 return $command !==
"" ? $command :
null;
142 public function quit() :
void{
143 $this->shutdownSubprocess();