[PHP]stream_get_transports完全ガイド|利用可能なソケットトランスポート一覧を取得してネットワーク接続を安全に構築する

PHP

はじめに

PHPでソケット通信やネットワーク接続を扱う際、「このサーバー環境では SSL/TLS が使えるか?」「unix:// ドメインソケットは利用可能か?」といった確認が必要になる場面があります。

stream_get_transports() は、現在の PHP 環境で利用可能なソケットトランスポートの名前を配列で一括取得する関数です。fsockopen()stream_socket_client() で接続する前にトランスポートの存在確認を行うことで、環境依存による接続エラーを未然に防げます。


関数の基本情報

項目内容
関数名stream_get_transports()
対応バージョンPHP 5.0.0 以降
返り値string[](トランスポート名の配列)
カテゴリストリーム関数

構文

stream_get_transports(): array

パラメータ

なし。

返り値

利用可能なトランスポート名の文字列配列。順序は不定です。


基本的な使い方

<?php

$transports = stream_get_transports();
print_r($transports);

// 出力例(環境により異なる):
// Array
// (
//     [0] => tcp
//     [1] => udp
//     [2] => unix
//     [3] => udg
//     [4] => ssl
//     [5] => tls
//     [6] => tlsv1.0
//     [7] => tlsv1.1
//     [8] => tlsv1.2
//     [9] => tlsv1.3
// )

// 特定トランスポートの存在確認
$hasSsl = in_array('ssl', stream_get_transports(), true);
var_dump($hasSsl); // bool(true) or bool(false)

主なトランスポート一覧

トランスポート名説明要件
tcpTCP ソケット接続標準(常に利用可能)
udpUDP ソケット接続標準(常に利用可能)
unixUnix ドメインソケット(SOCK_STREAM)Unix 系 OS のみ
udgUnix ドメインソケット(SOCK_DGRAM)Unix 系 OS のみ
sslSSL(OpenSSL の汎用エイリアス)OpenSSL 拡張が必要
tlsTLS(OpenSSL の汎用エイリアス)OpenSSL 拡張が必要
tlsv1.0TLS 1.0OpenSSL 拡張が必要
tlsv1.1TLS 1.1OpenSSL 拡張が必要
tlsv1.2TLS 1.2OpenSSL 拡張が必要
tlsv1.3TLS 1.3OpenSSL 1.1.1 以降 + PHP 7.4 以降

stream_get_transports() と stream_get_wrappers() / stream_get_filters() の違い

関数返す内容用途例
stream_get_transports()ソケット接続方式の一覧ssl://tcp://unix:// でのソケット接続
stream_get_wrappers()ストリームラッパーの一覧fopen('http://...')fopen('php://memory')
stream_get_filters()ストリームフィルターの一覧stream_filter_append() でのデータ変換
// トランスポート → fsockopen() / stream_socket_client() で使うプレフィックス
$sock = stream_socket_client('ssl://example.com:443');

// ラッパー → fopen() の URL スキームで使う
$fp = fopen('https://example.com/', 'r');

実践的なクラスベースの活用例


例1:トランスポートレジストリクラス(TransportRegistry)

利用可能なトランスポートをカテゴリ別に分類・管理し、存在確認や SSL/TLS サポートの有無を一括チェックするレジストリクラスです。

<?php

class TransportRegistry
{
    private array $available;

    /** SSL/TLS 系トランスポート名 */
    private const SSL_TRANSPORTS = ['ssl', 'tls', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'];

    /** Unix ドメインソケット系 */
    private const UNIX_TRANSPORTS = ['unix', 'udg'];

    public function __construct()
    {
        $this->available = stream_get_transports();
    }

    /** トランスポートが利用可能か確認する */
    public function has(string $transport): bool
    {
        return in_array(strtolower($transport), $this->available, true);
    }

    /** SSL/TLS が少なくとも1つ利用可能か確認する */
    public function hasSsl(): bool
    {
        return !empty(array_intersect(self::SSL_TRANSPORTS, $this->available));
    }

    /** 利用可能な SSL/TLS バージョンを返す */
    public function getSslVersions(): array
    {
        return array_values(array_intersect(self::SSL_TRANSPORTS, $this->available));
    }

    /** 最新の推奨 TLS バージョンを返す(tlsv1.3 > tlsv1.2 > tls の優先順) */
    public function getBestTls(): ?string
    {
        foreach (['tlsv1.3', 'tlsv1.2', 'tls', 'ssl'] as $candidate) {
            if ($this->has($candidate)) return $candidate;
        }
        return null;
    }

    /** Unix ドメインソケットが利用可能か確認する */
    public function hasUnixSocket(): bool
    {
        return $this->has('unix');
    }

    /** カテゴリ別に分類して返す */
    public function grouped(): array
    {
        $groups = [
            'tcp_udp' => [],
            'ssl_tls' => [],
            'unix'    => [],
            'other'   => [],
        ];

        foreach ($this->available as $t) {
            if (in_array($t, ['tcp', 'udp'], true)) {
                $groups['tcp_udp'][] = $t;
            } elseif (in_array($t, self::SSL_TRANSPORTS, true)) {
                $groups['ssl_tls'][] = $t;
            } elseif (in_array($t, self::UNIX_TRANSPORTS, true)) {
                $groups['unix'][] = $t;
            } else {
                $groups['other'][] = $t;
            }
        }

        return array_filter($groups);
    }

    public function all(): array   { return $this->available; }
    public function count(): int   { return count($this->available); }
}

// 使用例
$registry = new TransportRegistry();

echo "総トランスポート数: " . $registry->count() . PHP_EOL;
echo "SSL利用可能: "        . ($registry->hasSsl()        ? 'YES' : 'NO') . PHP_EOL;
echo "Unixソケット利用可能: " . ($registry->hasUnixSocket() ? 'YES' : 'NO') . PHP_EOL;
echo "推奨TLS: "             . ($registry->getBestTls()   ?? '利用不可') . PHP_EOL;
echo "SSL/TLSバージョン: "   . implode(', ', $registry->getSslVersions()) . PHP_EOL;

echo PHP_EOL . "カテゴリ別:" . PHP_EOL;
foreach ($registry->grouped() as $category => $transports) {
    echo "  [{$category}] " . implode(', ', $transports) . PHP_EOL;
}

例2:安全な接続ファクトリクラス(SecureSocketFactory)

stream_get_transports() で TLS の利用可否を確認し、最適なトランスポートを自動選択してソケット接続を生成するファクトリクラスです。

<?php

class SocketConnectionException extends \RuntimeException {}

class SecureSocketFactory
{
    private TransportRegistry $registry;

    /** デフォルトのソケットコンテキストオプション */
    private array $defaultContextOptions = [
        'ssl' => [
            'verify_peer'       => true,
            'verify_peer_name'  => true,
            'allow_self_signed' => false,
        ],
    ];

    public function __construct()
    {
        $this->registry = new TransportRegistry();
    }

    /**
     * TLS 接続を生成する(利用可能な最新バージョンを自動選択)
     *
     * @throws SocketConnectionException TLS 非対応または接続失敗時
     */
    public function createTls(
        string $host,
        int    $port    = 443,
        int    $timeout = 10,
        array  $contextOptions = []
    ): mixed {
        $transport = $this->registry->getBestTls();
        if ($transport === null) {
            throw new SocketConnectionException(
                'SSL/TLS トランスポートが利用できません。OpenSSL 拡張を確認してください。'
            );
        }

        $options = array_merge_recursive($this->defaultContextOptions, $contextOptions);
        $context = stream_context_create($options);

        $address = "{$transport}://{$host}:{$port}";
        $errno   = 0;
        $errstr  = '';

        $socket = @stream_socket_client(
            $address,
            $errno,
            $errstr,
            $timeout,
            STREAM_CLIENT_CONNECT,
            $context
        );

        if ($socket === false) {
            throw new SocketConnectionException(
                "TLS 接続に失敗しました [{$address}]: ({$errno}) {$errstr}"
            );
        }

        stream_set_timeout($socket, $timeout);
        return $socket;
    }

    /**
     * TCP 接続を生成する
     *
     * @throws SocketConnectionException 接続失敗時
     */
    public function createTcp(string $host, int $port, int $timeout = 10): mixed
    {
        if (!$this->registry->has('tcp')) {
            throw new SocketConnectionException('TCP トランスポートが利用できません。');
        }

        $errno  = 0;
        $errstr = '';
        $socket = @stream_socket_client(
            "tcp://{$host}:{$port}",
            $errno,
            $errstr,
            $timeout
        );

        if ($socket === false) {
            throw new SocketConnectionException(
                "TCP 接続に失敗しました [tcp://{$host}:{$port}]: ({$errno}) {$errstr}"
            );
        }

        stream_set_timeout($socket, $timeout);
        return $socket;
    }

    /**
     * Unix ドメインソケット接続を生成する
     *
     * @throws SocketConnectionException Unix ソケット非対応または接続失敗時
     */
    public function createUnix(string $socketPath, int $timeout = 5): mixed
    {
        if (!$this->registry->has('unix')) {
            throw new SocketConnectionException(
                'Unix ドメインソケットはこの環境では利用できません。'
            );
        }

        if (!file_exists($socketPath)) {
            throw new SocketConnectionException(
                "ソケットファイルが見つかりません: {$socketPath}"
            );
        }

        $errno  = 0;
        $errstr = '';
        $socket = @stream_socket_client(
            "unix://{$socketPath}",
            $errno,
            $errstr,
            $timeout
        );

        if ($socket === false) {
            throw new SocketConnectionException(
                "Unix ソケット接続に失敗しました [{$socketPath}]: ({$errno}) {$errstr}"
            );
        }

        return $socket;
    }
}

// 使用例
$factory = new SecureSocketFactory();

try {
    // TLS 接続(実環境では実際のホストを指定)
    // $socket = $factory->createTls('smtp.gmail.com', 465);
    // fwrite($socket, "EHLO localhost\r\n");
    // echo fread($socket, 1024);
    // fclose($socket);
    echo "SecureSocketFactory: TLS接続の準備完了" . PHP_EOL;
} catch (SocketConnectionException $e) {
    echo "接続エラー: " . $e->getMessage() . PHP_EOL;
}

例3:環境診断レポートクラス(TransportDiagnostics)

現在の PHP 環境におけるトランスポートの対応状況を診断し、ネットワーク機能の利用可否を一覧レポートするクラスです。

<?php

class TransportDiagnostics
{
    /** アプリケーションが必要とするトランスポート定義 */
    private array $requirements;

    public function __construct(array $requirements = [])
    {
        $this->requirements = $requirements;
    }

    public function run(): array
    {
        $available = stream_get_transports();

        return [
            'available'    => $available,
            'total'        => count($available),
            'ssl_versions' => array_values(array_filter($available, fn($t) =>
                in_array($t, ['ssl', 'tls', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'], true)
            )),
            'has_tcp'      => in_array('tcp',  $available, true),
            'has_udp'      => in_array('udp',  $available, true),
            'has_unix'     => in_array('unix', $available, true),
            'has_ssl'      => !empty(array_intersect(['ssl', 'tls'], $available)),
            'requirements' => $this->checkRequirements($available),
            'openssl'      => extension_loaded('openssl'),
            'openssl_ver'  => defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N/A',
        ];
    }

    private function checkRequirements(array $available): array
    {
        $results = [];
        foreach ($this->requirements as $transport) {
            $results[$transport] = in_array($transport, $available, true) ? 'OK' : 'MISSING';
        }
        return $results;
    }

    public function printReport(): void
    {
        $r = $this->run();

        echo "=== Transport Diagnostics ===" . PHP_EOL;
        echo "利用可能トランスポート数: {$r['total']}" . PHP_EOL;
        echo "全一覧: " . implode(', ', $r['available']) . PHP_EOL;

        echo PHP_EOL . "--- 基本プロトコル ---" . PHP_EOL;
        echo "  TCP  : " . ($r['has_tcp']  ? 'OK' : 'UNAVAILABLE') . PHP_EOL;
        echo "  UDP  : " . ($r['has_udp']  ? 'OK' : 'UNAVAILABLE') . PHP_EOL;
        echo "  Unix : " . ($r['has_unix'] ? 'OK' : 'UNAVAILABLE(Windows環境など)') . PHP_EOL;

        echo PHP_EOL . "--- SSL/TLS ---" . PHP_EOL;
        echo "  OpenSSL拡張: " . ($r['openssl'] ? 'OK' : 'NOT LOADED') . PHP_EOL;
        echo "  バージョン : " . $r['openssl_ver'] . PHP_EOL;
        if (!empty($r['ssl_versions'])) {
            foreach ($r['ssl_versions'] as $v) {
                echo "  [{$v}] OK" . PHP_EOL;
            }
        } else {
            echo "  SSL/TLS 非対応" . PHP_EOL;
        }

        if (!empty($r['requirements'])) {
            echo PHP_EOL . "--- 必須トランスポート確認 ---" . PHP_EOL;
            foreach ($r['requirements'] as $t => $status) {
                echo "  [{$status}] {$t}" . PHP_EOL;
            }
        }
    }
}

// 使用例
$diag = new TransportDiagnostics(
    requirements: ['tcp', 'tlsv1.2', 'tlsv1.3', 'unix']
);
$diag->printReport();

// 出力例:
// === Transport Diagnostics ===
// 利用可能トランスポート数: 10
// 全一覧: tcp, udp, unix, udg, ssl, tls, tlsv1.0, tlsv1.1, tlsv1.2, tlsv1.3
//
// --- 基本プロトコル ---
//   TCP  : OK
//   UDP  : OK
//   Unix : OK
//
// --- SSL/TLS ---
//   OpenSSL拡張: OK
//   バージョン : OpenSSL 3.0.2 15 Mar 2022
//   [ssl] OK
//   [tls] OK
//   [tlsv1.2] OK
//   [tlsv1.3] OK
//
// --- 必須トランスポート確認 ---
//   [OK] tcp
//   [OK] tlsv1.2
//   [OK] tlsv1.3
//   [OK] unix

例4:接続戦略セレクタークラス(ConnectionStrategySelector)

stream_get_transports() で環境を確認し、「Unix ドメインソケット優先→TLS→TCP」のような優先順位に従って接続方法を自動選択するクラスです。

<?php

class ConnectionStrategy
{
    public function __construct(
        public readonly string $transport,
        public readonly string $address,
        public readonly string $description,
        public readonly int    $priority,
    ) {}

    public function __toString(): string
    {
        return "[{$this->transport}] {$this->address} - {$this->description}";
    }
}

class ConnectionStrategySelector
{
    private array $available;

    public function __construct()
    {
        $this->available = stream_get_transports();
    }

    /**
     * Redis への接続戦略を優先順に返す
     */
    public function forRedis(
        string $host        = '127.0.0.1',
        int    $port        = 6379,
        string $unixSocket  = '/var/run/redis/redis.sock'
    ): array {
        $strategies = [];

        // Unix ドメインソケットが最速(同一ホスト時)
        if (in_array('unix', $this->available, true) && file_exists($unixSocket)) {
            $strategies[] = new ConnectionStrategy(
                transport:   'unix',
                address:     "unix://{$unixSocket}",
                description: 'Unix ドメインソケット(最速)',
                priority:    1,
            );
        }

        // TLS 暗号化 TCP
        foreach (['tlsv1.3', 'tlsv1.2', 'tls'] as $tls) {
            if (in_array($tls, $this->available, true)) {
                $strategies[] = new ConnectionStrategy(
                    transport:   $tls,
                    address:     "{$tls}://{$host}:{$port}",
                    description: 'TLS暗号化TCP接続',
                    priority:    2,
                );
                break;
            }
        }

        // 平文 TCP(最後の手段)
        if (in_array('tcp', $this->available, true)) {
            $strategies[] = new ConnectionStrategy(
                transport:   'tcp',
                address:     "tcp://{$host}:{$port}",
                description: '平文TCP(非推奨・開発環境のみ)',
                priority:    3,
            );
        }

        usort($strategies, fn($a, $b) => $a->priority <=> $b->priority);
        return $strategies;
    }

    /**
     * メール送信(SMTP)の接続戦略を優先順に返す
     */
    public function forSmtp(string $host, int $port = 587): array
    {
        $strategies = [];

        if (in_array('tlsv1.3', $this->available, true)) {
            $strategies[] = new ConnectionStrategy('tlsv1.3', "tlsv1.3://{$host}:{$port}", 'SMTPS TLS1.3', 1);
        }
        if (in_array('tlsv1.2', $this->available, true)) {
            $strategies[] = new ConnectionStrategy('tlsv1.2', "tlsv1.2://{$host}:465",    'SMTPS TLS1.2', 2);
        }
        if (in_array('tcp', $this->available, true)) {
            $strategies[] = new ConnectionStrategy('tcp',     "tcp://{$host}:587",          'STARTTLS(平文で開始)', 3);
        }

        return $strategies;
    }
}

// 使用例
$selector = new ConnectionStrategySelector();

echo "=== Redis 接続戦略 ===" . PHP_EOL;
foreach ($selector->forRedis() as $strategy) {
    echo "  {$strategy}" . PHP_EOL;
}

echo PHP_EOL . "=== SMTP 接続戦略 ===" . PHP_EOL;
foreach ($selector->forSmtp('smtp.example.com') as $strategy) {
    echo "  {$strategy}" . PHP_EOL;
}

// 出力例(Unix ソケットファイルが存在する場合):
// === Redis 接続戦略 ===
//   [unix] unix:///var/run/redis/redis.sock - Unix ドメインソケット(最速)
//   [tlsv1.3] tlsv1.3://127.0.0.1:6379 - TLS暗号化TCP接続
//   [tcp] tcp://127.0.0.1:6379 - 平文TCP(非推奨・開発環境のみ)
//
// === SMTP 接続戦略 ===
//   [tlsv1.3] tlsv1.3://smtp.example.com:587 - SMTPS TLS1.3
//   [tlsv1.2] tlsv1.2://smtp.example.com:465 - SMTPS TLS1.2
//   [tcp] tcp://smtp.example.com:587 - STARTTLS(平文で開始)

例5:トランスポート自動フォールバッククライアント(FallbackSocketClient)

接続試行時にトランスポートを順番に試し、成功するまで自動フォールバックするクライアントクラスです。

<?php

class FallbackSocketClient
{
    private array  $available;
    private ?mixed $socket      = null;
    private string $usedTransport = '';
    private array  $attemptLog  = [];

    public function __construct(
        private int $timeout = 10
    ) {
        $this->available = stream_get_transports();
    }

    /**
     * ホスト・ポートに対してトランスポート候補を順番に試して接続する
     *
     * @param  string[] $transportPriority 試行するトランスポートの優先順リスト
     * @return bool 接続成功なら true
     */
    public function connect(string $host, int $port, array $transportPriority): bool
    {
        $this->socket        = null;
        $this->usedTransport = '';
        $this->attemptLog    = [];

        foreach ($transportPriority as $transport) {
            // 環境で利用不可なら即スキップ
            if (!in_array($transport, $this->available, true)) {
                $this->attemptLog[] = ['transport' => $transport, 'result' => 'SKIPPED(利用不可)'];
                continue;
            }

            $context = $this->buildContext($transport);
            $address = "{$transport}://{$host}:{$port}";
            $errno   = 0;
            $errstr  = '';

            $sock = @stream_socket_client(
                $address,
                $errno,
                $errstr,
                $this->timeout,
                STREAM_CLIENT_CONNECT,
                $context
            );

            if ($sock !== false) {
                $this->socket        = $sock;
                $this->usedTransport = $transport;
                $this->attemptLog[]  = ['transport' => $transport, 'result' => 'SUCCESS'];
                stream_set_timeout($sock, $this->timeout);
                return true;
            }

            $this->attemptLog[] = [
                'transport' => $transport,
                'result'    => "FAILED: ({$errno}) {$errstr}",
            ];
        }

        return false;
    }

    private function buildContext(string $transport): mixed
    {
        $isSsl = in_array($transport, ['ssl', 'tls', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'], true);
        if (!$isSsl) return stream_context_create();

        return stream_context_create([
            'ssl' => [
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ],
        ]);
    }

    public function write(string $data): int|false
    {
        return $this->socket ? fwrite($this->socket, $data) : false;
    }

    public function read(int $length = 4096): string|false
    {
        return $this->socket ? fread($this->socket, $length) : false;
    }

    public function close(): void
    {
        if ($this->socket) {
            fclose($this->socket);
            $this->socket = null;
        }
    }

    public function getUsedTransport(): string { return $this->usedTransport; }
    public function isConnected(): bool        { return $this->socket !== null; }

    public function printLog(): void
    {
        echo "=== 接続試行ログ ===" . PHP_EOL;
        foreach ($this->attemptLog as $log) {
            echo "  [{$log['transport']}] {$log['result']}" . PHP_EOL;
        }
    }
}

// 使用例
$client = new FallbackSocketClient(timeout: 5);

$connected = $client->connect('example.com', 443, [
    'tlsv1.3',  // 最優先
    'tlsv1.2',  // フォールバック1
    'tls',      // フォールバック2
    'ssl',      // フォールバック3
    'tcp',      // 最終フォールバック(非推奨)
]);

$client->printLog();

if ($connected) {
    echo "接続成功: " . $client->getUsedTransport() . " トランスポートを使用" . PHP_EOL;
    $client->close();
} else {
    echo "全トランスポートで接続失敗" . PHP_EOL;
}

例6:Unix ドメインソケットサーバー・クライアントクラス(UnixSocketChannel)

unix トランスポートの利用可否を確認した上で、プロセス間通信用の Unix ドメインソケットサーバーとクライアントを構築するクラスです。

<?php

class UnixSocketNotAvailableException extends \RuntimeException {}

class UnixSocketChannel
{
    private string $socketPath;

    /**
     * @throws UnixSocketNotAvailableException Unix ソケット非対応環境の場合
     */
    public function __construct(string $socketPath)
    {
        if (!in_array('unix', stream_get_transports(), true)) {
            throw new UnixSocketNotAvailableException(
                'Unix ドメインソケット(unix://)はこの環境では利用できません。' . PHP_EOL .
                '利用可能なトランスポート: ' . implode(', ', stream_get_transports())
            );
        }
        $this->socketPath = $socketPath;
    }

    /**
     * サーバーソケットを作成して返す
     */
    public function createServer(int $backlog = 5): mixed
    {
        // 既存のソケットファイルを削除
        if (file_exists($this->socketPath)) {
            unlink($this->socketPath);
        }

        $errno  = 0;
        $errstr = '';
        $server = stream_socket_server(
            "unix://{$this->socketPath}",
            $errno,
            $errstr,
            STREAM_SERVER_BIND | STREAM_SERVER_LISTEN
        );

        if ($server === false) {
            throw new \RuntimeException("サーバー作成失敗: ({$errno}) {$errstr}");
        }

        chmod($this->socketPath, 0600);
        return $server;
    }

    /**
     * クライアント接続を確立して返す
     */
    public function createClient(int $timeout = 5): mixed
    {
        $errno  = 0;
        $errstr = '';
        $client = stream_socket_client(
            "unix://{$this->socketPath}",
            $errno,
            $errstr,
            $timeout
        );

        if ($client === false) {
            throw new \RuntimeException(
                "クライアント接続失敗 [{$this->socketPath}]: ({$errno}) {$errstr}"
            );
        }

        stream_set_timeout($client, $timeout);
        return $client;
    }

    public function getSocketPath(): string { return $this->socketPath; }
}

// 使用例(Unix ソケットの動作確認)
try {
    $channel = new UnixSocketChannel('/tmp/php_ipc.sock');

    // サーバー側(本来は別プロセス)
    $server = $channel->createServer();
    echo "サーバー起動: {$channel->getSocketPath()}" . PHP_EOL;

    // クライアント接続
    $client = $channel->createClient();
    fwrite($client, "HELLO FROM CLIENT\n");

    // サーバーが接続を受け付けて読む
    $conn = stream_socket_accept($server, 1);
    if ($conn) {
        $msg = fread($conn, 256);
        echo "サーバー受信: " . trim($msg) . PHP_EOL;
        fwrite($conn, "ACK\n");
        fclose($conn);
    }

    // クライアントが応答を読む
    echo "クライアント受信: " . trim(fread($client, 256)) . PHP_EOL;

    fclose($client);
    fclose($server);
    unlink('/tmp/php_ipc.sock');

} catch (UnixSocketNotAvailableException $e) {
    echo "Unix ソケット非対応: " . $e->getMessage() . PHP_EOL;
}

// 出力:
// サーバー起動: /tmp/php_ipc.sock
// サーバー受信: HELLO FROM CLIENT
// クライアント受信: ACK

例7:トランスポート対応マトリクス生成クラス(TransportCompatibilityMatrix)

複数のサーバー環境(本番・ステージング・開発)で stream_get_transports() の結果を比較し、環境間の差異を可視化するマトリクスを生成するクラスです。

<?php

class TransportCompatibilityMatrix
{
    /** @var array<string, string[]> 環境名 => トランスポート配列 */
    private array $environments = [];

    /**
     * 環境を追加する
     *
     * @param string[] $transports stream_get_transports() の結果
     */
    public function addEnvironment(string $name, array $transports): void
    {
        $this->environments[$name] = $transports;
    }

    /**
     * 現在の実行環境を自動追加する
     */
    public function addCurrent(string $name = 'current'): void
    {
        $this->addEnvironment($name, stream_get_transports());
    }

    /**
     * 全環境で共通して使えるトランスポートを返す
     */
    public function getCommon(): array
    {
        if (empty($this->environments)) return [];

        return array_values(array_intersect(...array_values($this->environments)));
    }

    /**
     * いずれかの環境にしか存在しないトランスポートを返す
     *
     * @return array<string, string[]> トランスポート名 => 利用可能な環境名の配列
     */
    public function getUnique(): array
    {
        $all    = array_unique(array_merge(...array_values($this->environments)));
        $unique = [];

        foreach ($all as $transport) {
            $available = [];
            foreach ($this->environments as $envName => $transports) {
                if (in_array($transport, $transports, true)) {
                    $available[] = $envName;
                }
            }
            if (count($available) < count($this->environments)) {
                $unique[$transport] = $available;
            }
        }

        return $unique;
    }

    /**
     * 互換性マトリクスをテーブル形式で出力する
     */
    public function printMatrix(): void
    {
        $all      = array_unique(array_merge(...array_values($this->environments)));
        $envNames = array_keys($this->environments);
        sort($all);

        // ヘッダー
        $header = sprintf("%-15s", 'Transport');
        foreach ($envNames as $env) {
            $header .= sprintf(" %-12s", $env);
        }
        echo $header . PHP_EOL;
        echo str_repeat('-', strlen($header)) . PHP_EOL;

        // 各トランスポート行
        foreach ($all as $transport) {
            $row = sprintf("%-15s", $transport);
            foreach ($envNames as $env) {
                $mark = in_array($transport, $this->environments[$env], true) ? '✓' : '✗';
                $row .= sprintf(" %-12s", $mark);
            }
            echo $row . PHP_EOL;
        }

        echo PHP_EOL . "共通トランスポート: " . implode(', ', $this->getCommon()) . PHP_EOL;

        $unique = $this->getUnique();
        if (!empty($unique)) {
            echo "環境差異:" . PHP_EOL;
            foreach ($unique as $transport => $envs) {
                echo "  {$transport}: " . implode(', ', $envs) . " のみ" . PHP_EOL;
            }
        }
    }
}

// 使用例
$matrix = new TransportCompatibilityMatrix();

// 各環境のトランスポート情報を登録(実際は各サーバーで stream_get_transports() の結果を収集)
$matrix->addEnvironment('production', ['tcp', 'udp', 'ssl', 'tls', 'tlsv1.2', 'tlsv1.3']);
$matrix->addEnvironment('staging',    ['tcp', 'udp', 'ssl', 'tls', 'tlsv1.2', 'tlsv1.3', 'unix']);
$matrix->addEnvironment('local',      ['tcp', 'udp', 'ssl', 'tls', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3', 'unix', 'udg']);

$matrix->printMatrix();

// 出力例:
// Transport       production   staging      local
// --------------------------------------------------
// ssl             ✓            ✓            ✓
// tcp             ✓            ✓            ✓
// tls             ✓            ✓            ✓
// tlsv1.0         ✗            ✗            ✓
// tlsv1.1         ✗            ✗            ✓
// tlsv1.2         ✓            ✓            ✓
// tlsv1.3         ✓            ✓            ✓
// udg             ✗            ✗            ✓
// udp             ✓            ✓            ✓
// unix            ✗            ✓            ✓
//
// 共通トランスポート: ssl, tcp, tls, tlsv1.2, tlsv1.3, udp
// 環境差異:
//   tlsv1.0: local のみ
//   tlsv1.1: local のみ
//   udg: local のみ
//   unix: staging, local のみ

関連する関数との比較

関数役割
stream_get_transports()利用可能なソケットトランスポート名の一覧を返す
stream_get_wrappers()利用可能なストリームラッパー名の一覧を返す
stream_get_filters()利用可能なストリームフィルター名の一覧を返す
stream_socket_client()トランスポートを使ってクライアントソケットを接続する
stream_socket_server()トランスポートを使ってサーバーソケットを作成する
stream_get_meta_data()開いたストリームのメタ情報(モード・タイムアウト等)を返す

注意点とベストプラクティス

1. tlsv1.0 / tlsv1.1 は非推奨

TLS 1.0・1.1 は現在セキュリティ上の理由で非推奨とされており、多くのサーバーで無効化されています。新規実装では tlsv1.2 以上を使いましょう。

// 推奨:tlsv1.2 以上
$preferred = array_intersect(['tlsv1.3', 'tlsv1.2'], stream_get_transports());

2. ssl と tls はエイリアス

ssl はシステムの OpenSSL 設定に従った TLS バージョンのエイリアス、tls は TLS 汎用エイリアスです。バージョンを明示したい場合は tlsv1.2 / tlsv1.3 を使いましょう。

3. Windows 環境では unix が存在しない

Unix ドメインソケットは Windows ではデフォルトで利用不可のため、クロスプラットフォームなコードでは事前確認が必須です。

if (in_array('unix', stream_get_transports(), true)) {
    // Unix ドメインソケットを使う処理
} else {
    // TCP フォールバック
}

4. 結果はキャッシュして使い回す

stream_get_transports() の結果は実行中に変わることはありません。ループ内で繰り返し呼ぶ代わりに変数に保存して使い回しましょう。

// NG
foreach ($hosts as $host) {
    if (in_array('tls', stream_get_transports())) { ... }
}

// OK
$transports = stream_get_transports();
foreach ($hosts as $host) {
    if (in_array('tls', $transports)) { ... }
}

まとめ

ポイント内容
基本動作現在の PHP 環境で使えるソケットトランスポート名を配列で返す
引数なし
返り値string[](順序不定)
主な用途SSL/TLS 対応確認・Unix ソケット可否・接続方式の自動選択
TLS 推奨tlsv1.2 / tlsv1.3 を優先。tlsv1.0 / tlsv1.1 は非推奨
Windows 注意unix / udg は Windows 環境では通常利用不可
キャッシュ結果は実行中に変わらないので変数に保存して再利用する
活用シーン環境診断・接続ファクトリ・自動フォールバック・互換性マトリクスなど

stream_get_transports() はシンプルな API ながら、環境依存のネットワーク接続を安全・堅牢に構築するための重要な起点となります。stream_get_wrappers() / stream_get_filters() と合わせてストリーム系の三兄弟として活用することで、PHP のストリーム機能を最大限に引き出せます。

タイトルとURLをコピーしました