sebastianbergmann/phpunit · error · PHPUnit\TextUI\CannotOpenSocketException
Cannot open socket %s:%d
Error message
Cannot open socket %s:%d
What it means
DefaultPrinter is PHPUnit's output sink for progress and test-result output. When its constructor receives a target like `socket://host:port`, it strips the scheme, splits host and port, and calls @fsockopen(). If the TCP connection cannot be established, it throws CannotOpenSocketException('Cannot open socket host:port'). PHPUnit aborts at startup because it cannot deliver output to the configured destination.
Source
Thrown at src/TextUI/Output/Printer/DefaultPrinter.php:90
* @throws CannotOpenSocketException
* @throws DirectoryDoesNotExistException
* @throws InvalidSocketException
*/
private function __construct(string $out)
{
$this->isPhpStream = str_starts_with($out, 'php://');
if (str_starts_with($out, 'socket://')) {
$tmp = explode(':', str_replace('socket://', '', $out));
if (count($tmp) !== 2) {
throw new InvalidSocketException($out);
}
$stream = @fsockopen($tmp[0], (int) $tmp[1]);
if ($stream === false) {
throw new CannotOpenSocketException($tmp[0], (int) $tmp[1]);
}
$this->stream = $stream;
$this->isOpen = true;
return;
}
if (!$this->isPhpStream && !Filesystem::createDirectory(dirname($out))) {
throw new DirectoryDoesNotExistException(dirname($out));
}
$stream = fopen($out, 'wb');
assert($stream !== false);
$this->stream = $stream;
$this->isOpen = true;View on GitHub (pinned to f123cdb2a2)
Solutions
- Verify something is listening and reachable from the test runner: `nc -vz host port` or `ss -ltn` / `docker port`, then start the receiver before running PHPUnit
- Fix the host/port in the target string; the parser expects exactly `socket://hostname:port` (two colon-separated parts after the scheme)
- In containerized or firewalled environments, connect to the address as seen from the test container, not the host's loopback
- If you never intended a socket, remove the `socket://` prefix — any non-`php://` target without that scheme is opened as a regular file
Example fix
// before
$printer = \PHPUnit\TextUI\Output\Printer\DefaultPrinter::from('socket://127.0.0.1:9988');
// Throws CannotOpenSocketException: nothing listens on 9988
// after: start the receiver first, or target a file
$ ls: $ nc -l 127.0.0.1 9988 &
$printer = \PHPUnit\TextUI\Output\Printer\DefaultPrinter::from('socket://127.0.0.1:9988');
// or simply write to a file:
$printer = \PHPUnit\TextUI\Output\Printer\DefaultPrinter::from('build/logs/output.txt'); Defensive patterns
Strategy: try-catch
Validate before calling
$target = 'socket://127.0.0.1:9988';
if (str_starts_with($target, 'socket://')) {
[$host, $port] = explode(':', substr($target, 9));
$probe = @fsockopen($host, (int) $port, $errno, $errstr, 3);
if ($probe === false) {
throw new RuntimeException("Output sink unavailable: $errstr ($errno)");
}
fclose($probe);
} Try / catch
use PHPUnit\TextUI\Output\Printer\CannotOpenSocketException;
try {
$printer = DefaultPrinter::from($target);
} catch (CannotOpenSocketException $e) {
// degrade gracefully instead of aborting the run
$printer = DefaultPrinter::standardOutput();
error_log('Socket output unavailable, falling back to stdout: ' . $e->getMessage());
} Prevention
- Start and health-check the socket receiver before invoking PHPUnit (a 1-line nc/timeout probe in the pipeline)
- Pin the target to exactly `socket://host:port` — three or more colon-separated parts raise InvalidSocketException instead
- In containers, always address the collector by the service name/IP reachable from the test container, never 127.0.0.1 by habit
When it happens
Trigger: Calling DefaultPrinter::from('socket://127.0.0.1:9000') (or configuring an equivalent output target) when no server is listening on that host/port, the port is mistyped, the hostname does not resolve, or a firewall/network barrier drops the connection. The string must be exactly `socket://hostname:port`; anything else is treated as a file path (php:// streams excepted).
Common situations: CI pipelines that ship test output to a log-collector socket (Logstash, Datadog agent, custom TCP sink) that has not been started yet; Docker setups where 'localhost' resolves inside the wrong container; port typos after changing the collector's configuration.
Related errors
- "%s" does not match "socket://hostname:port" format
- Directory "%s" does not exist and could not be created
- Subscriber "%s" does not implement any known interface - did
- Unknown event type "%s"
- Subscriber "%s" does not implement a known interface
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/1bbe8d2f95a114ac.
Report an issue: GitHub.