connection = $connection; $this->tagGenerator = new TagGenerator(); $this->responseHandler = new ResponseHandler(new Parser()); } public function __destruct() { $this->disconnect(); } public function connect(): void { if ($this->connection->isOpen()) { return; } $this->connection->open(); $responseStream = $this->connection->receive(); $greeting = $this->responseHandler->handle('*', $responseStream, new UnexpectedContinuationHandler()); match ($greeting->status->type) { StatusType::OK => null, // Do nothing StatusType::PREAUTH => throw new RuntimeException('pre-auth is not supported'), StatusType::BAD, StatusType::NO, StatusType::BYE => throw new ConnectionRejected($greeting->status->message), }; } public function disconnect(): void { $this->connection->close(); } /** * Perform STARTTLS negotiation (patch). * * Sends the STARTTLS command and upgrades the underlying socket to TLS. * The connection must be a SocketConnection (or any Connection that * implements upgradeTls()). Call this after connect() but before logIn(). * * @throws \RuntimeException if the server rejects STARTTLS * @throws \BadMethodCallException if the connection does not support TLS upgrade */ public function startTls(): void { if (!method_exists($this->connection, 'upgradeTls')) { throw new \BadMethodCallException( 'The current Connection implementation does not support STARTTLS upgrade' ); } $response = $this->send(new StartTlsCommand()); if ($response->status->type !== StatusType::OK) { throw new \RuntimeException( 'Server rejected STARTTLS: ' . $response->status->message ); } $this->connection->upgradeTls(); } public function send(Command $command): Response { $interaction = new CommandInteraction( $this->connection, $this->responseHandler, $this->tagGenerator->next(), $command, ); $response = $interaction->interact(); if ($response->status->type != StatusType::OK) { throw CommandFailed::withStatus($response->status); } return $response; } /** * Sends $command and returns a Generator that yields each untagged Line as * it arrives from the socket. CommandFailed is thrown (inside the generator) * if the server responds with NO or BAD. * * @return Generator */ public function sendStreaming(Command $command): Generator { $this->connect(); $interaction = new CommandInteraction( $this->connection, $this->responseHandler, $this->tagGenerator->next(), $command, ); yield from $interaction->streamInteract(); } }