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

  1. 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
  2. Fix the host/port in the target string; the parser expects exactly `socket://hostname:port` (two colon-separated parts after the scheme)
  3. In containerized or firewalled environments, connect to the address as seen from the test container, not the host's loopback
  4. 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

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


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/1bbe8d2f95a114ac. Report an issue: GitHub.