1<?php 2 3// TODO: Move address/port info in here? 4class CliServerInfo { 5 public function __construct( 6 public string $docRoot, 7 ) {} 8} 9 10function php_cli_server_start( 11 ?string $code = 'echo "Hello world";', 12 ?string $router = 'index.php', 13 array $cmd_args = [] 14): CliServerInfo { 15 $php_executable = getenv('TEST_PHP_EXECUTABLE'); 16 $error = null; 17 18 // Create dedicated doc root to avoid index.php clashes between tests. 19 $doc_root = __DIR__ . '/' . basename($_SERVER['PHP_SELF'], '.php'); 20 @mkdir($doc_root); 21 22 if ($code) { 23 file_put_contents($doc_root . '/' . ($router ?: 'index.php'), '<?php ' . $code . ' ?>'); 24 } 25 26 $cmd = [$php_executable, '-t', $doc_root, '-n', ...$cmd_args, '-S', 'localhost:0']; 27 if (!is_null($router)) { 28 $cmd[] = $router; 29 } 30 31 $descriptorspec = array( 32 0 => STDIN, 33 1 => STDOUT, 34 2 => ['pipe', 'w'], 35 ); 36 $handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root, null, array("suppress_errors" => true)); 37 38 // First, wait for the dev server to declare itself ready. 39 $bound = null; 40 stream_set_blocking($pipes[2], false); 41 for ($i = 0; $i < 60; $i++) { 42 usleep(50000); // 50ms per try 43 $status = proc_get_status($handle); 44 if (empty($status['running'])) { 45 echo "Server is not running\n"; 46 proc_terminate($handle); 47 exit(1); 48 } 49 50 while (($line = fgets($pipes[2])) !== false) { 51 if (preg_match('@PHP \S* Development Server \(https?://(.*?:\d+)\) started@', $line, $matches)) { 52 $bound = $matches[1]; 53 // Now that we've identified the listen address, close STDERR. 54 // Otherwise the pipe may clog up with unread log messages. 55 fclose($pipes[2]); 56 break 2; 57 } 58 } 59 } 60 if ($bound === null) { 61 echo "Server did not output startup message"; 62 proc_terminate($handle); 63 exit(1); 64 } 65 66 // Now wait for a connection to succeed. 67 // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.' 68 // it might not be listening yet...need to wait until fsockopen() call returns 69 $error = "Unable to connect to server\n"; 70 for ($i=0; $i < 60; $i++) { 71 usleep(50000); // 50ms per try 72 $status = proc_get_status($handle); 73 $fp = @fsockopen("tcp://$bound"); 74 // Failure, the server is no longer running 75 if (!($status && $status['running'])) { 76 $error = "Server is not running\n"; 77 break; 78 } 79 // Success, Connected to servers 80 if ($fp) { 81 $error = ''; 82 break; 83 } 84 } 85 86 if ($error) { 87 echo $error; 88 proc_terminate($handle); 89 exit(1); 90 } 91 92 register_shutdown_function( 93 function($handle) use($router, $doc_root) { 94 proc_terminate($handle); 95 @unlink(__DIR__ . "/{$router}"); 96 @rmdir($doc_root); 97 }, 98 $handle 99 ); 100 101 // Define the same "constants" we previously did. 102 $port = (int) substr($bound, strrpos($bound, ':') + 1); 103 define("PHP_CLI_SERVER_HOSTNAME", "localhost"); 104 define("PHP_CLI_SERVER_PORT", $port); 105 define("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT); 106 107 return new CliServerInfo($doc_root); 108} 109 110function php_cli_server_connect() { 111 $timeout = 1.0; 112 $fp = fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT, $errno, $errstr, $timeout); 113 if (!$fp) { 114 die("connect failed"); 115 } 116 return $fp; 117} 118 119?> 120