address, $clientConfig, $this->logger, ); $this->onMessage = $onMessage; } public static function create( string $address, string $topic, string $channel, callable $onMessage, ?ClientConfig $clientConfig = null, ?LoggerInterface $logger = null, ): self { return new self( $address, $topic, $channel, $onMessage, $clientConfig ?? new ClientConfig(), $logger ?? new NullLogger(), ); } public function connect(): Promise { if (!$this->stream instanceof NullStream) { return call(static function (): void { }); } return call(function (): \Generator { yield parent::connect(); $this->logger->debug('Consumer {topic}:{channel} connected to {host}', [ 'topic' => $this->topic, 'channel' => $this->channel, 'host' => $this->address, ]); $this->run(); }); } private function run(): void { $buffer = new Buffer(); asyncCall(function () use ($buffer): \Generator { yield $this->stream->write(Command::sub($this->topic, $this->channel)); if (null !== ($chunk = yield $this->stream->read())) { $buffer->append($chunk); } /** @var Response $response */ $response = Parser::parse($buffer); if (!$response->isOk()) { return new Failure(new ConsumerException('Fail subscription.')); } yield $this->rdy(1); /** @phpstan-ignore-next-line */ asyncCall(function () use ($buffer): \Generator { while (null !== $chunk = yield $this->stream->read()) { $buffer->append($chunk); while ($frame = Parser::parse($buffer)) { switch (true) { case $frame instanceof Frame\Response: if ($frame->isHeartBeat()) { yield $this->stream->write(Command::nop()); break; } throw ConsumerException::response($frame); case $frame instanceof Frame\Error: $this->handleError($frame); break; case $frame instanceof Frame\Message: asyncCall($this->onMessage, Message::compose($frame, $this)); break; } if ($this->rdy !== $this->clientConfig->rdyCount) { yield $this->rdy($this->clientConfig->rdyCount); } } } $this->stream = new NullStream(); }); }); } /** * Update RDY state (indicate you are ready to receive N messages). * * @return Promise */ public function rdy(int $count): Promise { if ($this->rdy === $count) { return call(static function (): void { }); } $this->rdy = $count; return $this->stream->write(Command::rdy($count)); } /** * Finish a message (indicate successful processing). * * @return Promise * * @internal */ public function fin(string $id): Promise { return $this->stream->write(Command::fin($id)); } /** * Re-queue a message (indicate failure to process) The re-queued message is placed at the tail of the queue, * equivalent to having just published it, but for various implementation specific reasons that behavior should not * be explicitly relied upon and may change in the future. Similarly, a message that is in-flight and times out * behaves identically to an explicit REQ. * * @return Promise * * @internal */ public function req(string $id, int $timeout): Promise { return $this->stream->write(Command::req($id, $timeout)); } /** * Reset the timeout for an in-flight message. * * @return Promise * * @internal */ public function touch(string $id): Promise { return $this->stream->write(Command::touch($id)); } }