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') ?: PHP_BINARY; 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 $output_file = tempnam(sys_get_temp_dir(), 'cli_server_output'); 32 $output_file_fd = fopen($output_file, 'ab'); 33 if ($output_file_fd === false) { 34 die(sprintf("Failed opening output file %s\n", $output_file)); 35 } 36 37 $descriptorspec = array( 38 0 => STDIN, 39 1 => $output_file_fd, 40 2 => $output_file_fd, 41 ); 42 $handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root, null, array("suppress_errors" => true)); 43 44 // First, wait for the dev server to declare itself ready. 45 $bound = null; 46 for ($i = 0; $i < 60; $i++) { 47 usleep(50000); // 50ms per try 48 $status = proc_get_status($handle); 49 if (empty($status['running'])) { 50 echo "Server failed to start\n"; 51 printf("Server output:\n%s\n", file_get_contents($output_file)); 52 proc_terminate($handle); 53 exit(1); 54 } 55 56 $output = file_get_contents($output_file); 57 if (preg_match('@PHP \S* Development Server \(https?://(.*?:\d+)\) started@', $output, $matches)) { 58 $bound = $matches[1]; 59 break; 60 } 61 } 62 if ($bound === null) { 63 echo "Server did not output startup message"; 64 printf("Server output:\n%s\n", file_get_contents($output_file)); 65 proc_terminate($handle); 66 exit(1); 67 } 68 69 // Now wait for a connection to succeed. 70 // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.' 71 // it might not be listening yet...need to wait until fsockopen() call returns 72 $error = "Unable to connect to server\n"; 73 for ($i=0; $i < 60; $i++) { 74 usleep(50000); // 50ms per try 75 $status = proc_get_status($handle); 76 $fp = @fsockopen("tcp://$bound"); 77 // Failure, the server is no longer running 78 if (!($status && $status['running'])) { 79 $error = sprintf("Server stopped\nServer output:\n%s\n", file_get_contents($output_file)); 80 break; 81 } 82 // Success, Connected to servers 83 if ($fp) { 84 $error = ''; 85 break; 86 } 87 } 88 89 if ($error) { 90 echo $error; 91 proc_terminate($handle); 92 exit(1); 93 } 94 95 register_shutdown_function( 96 function($handle) use($router, $doc_root, $output_file) { 97 proc_terminate($handle); 98 $status = proc_get_status($handle); 99 if ($status['exitcode'] !== -1 && $status['exitcode'] !== 0 100 && !($status['exitcode'] === 255 && PHP_OS_FAMILY == 'Windows')) { 101 printf("Server exited with non-zero status: %d\n", $status['exitcode']); 102 printf("Server output:\n%s\n", file_get_contents($output_file)); 103 } 104 @unlink(__DIR__ . "/{$router}"); 105 @rmdir($doc_root); 106 }, 107 $handle 108 ); 109 110 // Define the same "constants" we previously did. 111 $port = (int) substr($bound, strrpos($bound, ':') + 1); 112 define("PHP_CLI_SERVER_HOSTNAME", "localhost"); 113 define("PHP_CLI_SERVER_PORT", $port); 114 define("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT); 115 116 return new CliServerInfo($doc_root); 117} 118 119function php_cli_server_connect() { 120 $timeout = 1.0; 121 $fp = fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT, $errno, $errstr, $timeout); 122 if (!$fp) { 123 die("connect failed"); 124 } 125 return $fp; 126} 127 128?> 129