port = $port; $this->host = $host; $this->transport = $transport; $this->timeout = $timeout; $this->verifyPeer = $verifyPeer; $this->verifyPeerName = $verifyPeerName; $this->allowSelfSigned = $allowSelfSigned; } public function __destruct() { $this->close(); } public function isOpen(): bool { return false !== $this->stream; } public function open(): void { if ($this->isOpen()) { return; } $this->stream = @stream_socket_client( sprintf('%s://%s:%s', $this->transport, $this->host, $this->port), $errorCode, $errorMessage, $this->timeout, context: stream_context_create([ 'ssl' => [ 'verify_peer' => $this->verifyPeer, 'verify_peer_name' => $this->verifyPeerName, 'allow_self_signed' => $this->allowSelfSigned, ] ]) ); if (false === $this->stream) { throw new ConnectionFailed( sprintf('SocketConnection failed [%s]: %s', $errorCode, $errorMessage) ); } } public function close(): void { if (!$this->stream) { return; } fclose($this->stream); $this->stream = false; } /** * Upgrade an open plain-TCP socket to TLS in-place (STARTTLS patch). * * Must be called after the server has responded OK to a STARTTLS command. * * @throws ConnectionFailed */ public function upgradeTls(): void { if (!$this->stream) { throw new ConnectionFailed('Cannot upgrade TLS: connection is not open'); } stream_context_set_option($this->stream, [ 'ssl' => [ 'peer_name' => $this->host, 'verify_peer' => $this->verifyPeer, 'verify_peer_name' => $this->verifyPeerName, 'allow_self_signed' => $this->allowSelfSigned, ], ]); $cryptoMethod = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT; $result = @stream_socket_enable_crypto( $this->stream, true, $cryptoMethod, ); if ($result !== true) { $last = error_get_last(); $detail = $last['message'] ?? 'unknown error'; throw new ConnectionFailed('STARTTLS upgrade failed: ' . $detail); } } public function send(string $data): void { if (!$this->stream) { throw new Exception('Unable to send data. SocketConnection is not open'); } fwrite($this->stream, $data); } public function receive(): ResponseStream { if (!$this->stream) { throw new Exception('Unable to receive data. SocketConnection is not open'); } return new SocketResponseStream($this->stream); } }