url('/echo')); * $srv->stop(); */ class MockServer { public int $port; /** @var resource */ private $proc; private array $pipes = []; private function __construct() {} /** * サーバーを起動する * * @param string $script このディレクトリのスクリプト名 * @param array $args スクリプトに渡す追加の引数 * @return self|null 起動出来なかった場合はnull(proc_openが無効等) */ public static function start(string $script, array $args = []): ?self { if (!function_exists('proc_open')) return null; // 空いているポートを探す $probe = @stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr); if (!$probe) return null; $port = (int)substr(strrchr(stream_socket_get_name($probe, false), ':'), 1); fclose($probe); $cmd = array_merge([PHP_BINARY, __DIR__.'/'.$script, (string)$port], array_map('strval', $args)); $spec = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $proc = @proc_open($cmd, $spec, $pipes); if (!is_resource($proc)) return null; // 「ready」が出力されるまで待つ(起動に失敗したらfalse) $line = fgets($pipes[1]); if (trim((string)$line) !== 'ready') { proc_terminate($proc); proc_close($proc); return null; } $srv = new self(); $srv->port = $port; $srv->proc = $proc; $srv->pipes = $pipes; return $srv; } /** * このサーバーのURL * * @param string $path パス * @param string $host 127.0.0.1又はlocalhost(別のオリジンとして扱う為) * @return string */ public function url(string $path = '/', string $host = '127.0.0.1'): string { return "http://{$host}:{$this->port}{$path}"; } /** * サーバーを止める * * @return void */ public function stop(): void { if (!is_resource($this->proc)) return; // 終了のリクエストを送る(Windowsではproc_terminateで子プロセスが残る事がある) $c = @stream_socket_client("tcp://127.0.0.1:{$this->port}", $errno, $errstr, 1); if ($c) { fwrite($c, "QUIT /__quit HTTP/1.0\r\n\r\n"); fclose($c); } foreach ($this->pipes as $p) if (is_resource($p)) fclose($p); proc_terminate($this->proc); proc_close($this->proc); } public function __destruct() { $this->stop(); } }