diff --git a/README.md b/README.md index be6ea42..dd3e395 100644 --- a/README.md +++ b/README.md @@ -193,26 +193,7 @@ $channel->qos( ### Asynchronous usage -Bunny supports both synchronous and asynchronous usage utilizing [ReactPHP](https://github.com/reactphp). The following example shows setting up a client and consuming a queue indefinitely. - -```php -(new Async\Client($eventLoop, $options))->connect()->then(function (Async\Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->qos(0, 5)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($event) { - $channel->consume( - function (Message $message, Channel $channel, Async\Client $client) use ($event) { - // Handle message - - $channel->ack($message); - }, - 'queue_name' - ); -}); -``` +**Node: Up to version `v0.5.x` Bunny had two different clients, one sync, and one async. As of `v0.6` both clients have been folder into one: An async client with a sync API.** ## AMQP interop diff --git a/benchmark/consumer.php b/benchmark/consumer.php index b245f78..4e892c8 100644 --- a/benchmark/consumer.php +++ b/benchmark/consumer.php @@ -1,6 +1,8 @@ queueDeclare("bench_queue"); $ch->exchangeDeclare("bench_exchange"); -$ch->queueBind("bench_queue", "bench_exchange"); +$ch->queueBind("bench_exchange", "bench_queue"); $t = null; $count = 0; -$ch->run(function (Message $msg, Channel $ch, Client $c) use (&$t, &$count) { +$ch->consume(function (Message $msg, Channel $ch, Client $c) use (&$t, &$count) { if ($t === null) { $t = microtime(true); } if ($msg->content === "quit") { printf("Pid: %s, Count: %s, Time: %.4f\n", getmypid(), $count, microtime(true) - $t); - $c->stop(); + $c->disconnect(); } else { ++$count; } }, "bench_queue", "", false, true); - -$c->disconnect(); diff --git a/benchmark/consumer_async.php b/benchmark/consumer_async.php deleted file mode 100644 index 470ae05..0000000 --- a/benchmark/consumer_async.php +++ /dev/null @@ -1,46 +0,0 @@ -connect()->then(function (Client $c) { - return $c->channel(); - -})->then(function (Channel $ch) { - return Promise\all([ - $ch, - $ch->queueDeclare("bench_queue"), - $ch->exchangeDeclare("bench_exchange"), - $ch->queueBind("bench_queue", "bench_exchange"), - ]); - -})->then(function ($r) { - /** @var Channel $ch */ - $ch = $r[0]; - - $t = null; - $count = 0; - - return $ch->consume(function (Message $msg, Channel $ch, Client $c) use (&$t, &$count) { - if ($t === null) { - $t = microtime(true); - } - - if ($msg->content === "quit") { - printf("Pid: %s, Count: %s, Time: %.4f\n", getmypid(), $count, microtime(true) - $t); - $c->disconnect()->then(function () use ($c){ - $c->stop(); - }); - - } else { - ++$count; - } - }, "bench_queue", "", false, true); -}); - -$c->run(); diff --git a/benchmark/producer.php b/benchmark/producer.php index 5322632..81bbb6d 100644 --- a/benchmark/producer.php +++ b/benchmark/producer.php @@ -1,6 +1,8 @@ queueDeclare("bench_queue"); $ch->exchangeDeclare("bench_exchange"); -$ch->queueBind("bench_queue", "bench_exchange"); +$ch->queueBind("bench_exchange", "bench_queue"); $body = <<connect()->then(function (Client $c) { - return $c->channel(); - -})->then(function (Channel $ch) { - return Promise\all([ - $ch, - $ch->queueDeclare("bench_queue"), - $ch->exchangeDeclare("bench_exchange"), - $ch->queueBind("bench_queue", "bench_exchange"), - ]); - -})->then(function ($r) use ($body, &$time, &$max) { - /** @var Channel $ch */ - $ch = $r[0]; - - $promises = []; - - for ($i = 0; $i < $max; $i++) { - $promises[] = $ch->publish($body, [], "bench_exchange"); - } - - $promises[] = $ch->publish("quit", [], "bench_exchange"); - - return Promise\all($promises); - -})->then(function () use ($c) { - return $c->disconnect(); - -})->then(function () use ($c, &$time) { - echo microtime(true) - $time, "\n"; - $c->stop(); -}); - -$c->run(); diff --git a/composer.json b/composer.json index 7121f38..7e02985 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "bunny/bunny", "type": "library", - "description": "Performant pure-PHP AMQP (RabbitMQ) sync/async (ReactPHP) library", + "description": "Performant pure-PHP AMQP (RabbitMQ) non-blocking ReactPHP library", "keywords": [ "rabbitmq", "rabbit", @@ -14,7 +14,10 @@ "exchange", "react", "react-php", - "reactphp" + "reactphp", + "async", + "await", + "fibers" ], "minimum-stability": "stable", "license": "MIT", @@ -25,29 +28,32 @@ } ], "require": { - "php": "^7.1 || ^8.0", - "react/event-loop": "^1.0 || ^0.5 || ^0.4", - "react/promise": "~2.2" + "php": "^8.1", + "react/event-loop": "^1.5", + "react/promise": "^3.1", + "react/stream": "^1.3", + "react/async": "^4.2", + "react/socket": "^1.15", + "react/promise-timer": "^1.10", + "evenement/evenement": "^3", + "react/promise-stream": "^1.7" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.5 || ^7.5.20", - "symfony/process": "^6.1 || ^4.4", - "phpstan/phpstan": "^1.10" - }, - "suggest": { - "ext-pcntl": "For using synchronous AMQP/RabbitMQ client" + "phpunit/phpunit": "^9.6", + "phpstan/phpstan": "^1.10", + "react/child-process": "^0.6.5", + "wyrihaximus/react-phpunit-run-tests-in-fiber": "^2 || ^1" }, "autoload": { "psr-4": { - "Bunny\\": "src/Bunny/" + "Bunny\\": "src/" } }, "autoload-dev": { "files": ["test/Library/functions.php"], "psr-4": { - "Bunny\\": "test/Bunny/", - "Bunny\\Test\\Library\\": "test/Library/" + "Bunny\\Test\\": "test/" } } } diff --git a/examples/worker-async.php b/examples/worker-async.php deleted file mode 100644 index 3742e0b..0000000 --- a/examples/worker-async.php +++ /dev/null @@ -1,91 +0,0 @@ - "rabbitmq.example.com", - "port" => 5672, - "vhost" => "/", - "user" => "appuser", - "password" => "apppass", -]; - -$client = new Client($loop, $clientConfig); -$client->connect()->then(function (Client $client) { - return $client->channel(); -}, function($reason) { - $reasonMsg = ""; - if (is_string($reason)) { - $reasonMsg = $reason; - } else if ($reason instanceof Throwable) { - $reasonMsg = $reason->getMessage(); - } - print "Rejected: {$reasonMsg}\n"; -})->then(function (Channel $channel) { - return $channel->qos(0, 1)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - return $channel->queueDeclare('test', false, true, false, false)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use (&$channelRef) { - $channelRef = $channel; - echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - echo " [x] Received ", $message->content, "\n"; - - // Do some work - we generate password hashes with a high cost - // sleep() gets interrupted by Ctrl+C so it's not very good for demos - // Performing multiple work units demonstrates that nothing is skipped - for ($i = 0; $i < 3; $i++) { - print "WU {$i}\n"; - password_hash(random_bytes(255), PASSWORD_BCRYPT, ["cost" => 15]); - } - echo " [x] Done ", $message->content, "\n"; - - $channel->ack($message)->then(function() use ($message) { - print "ACK :: {$message->content}\n"; - }, function($reason) { - $reasonMsg = ""; - if (is_string($reason)) { - $reasonMsg = $reason; - } else if ($reason instanceof Throwable) { - $reasonMsg = $reason->getMessage(); - } - print "ACK FAILED! - {$reasonMsg}\n"; - })->done(); - }, - 'test' - )->then(function (MethodBasicConsumeOkFrame $response) use (&$consumerTag) { - $consumerTag = $response->consumerTag; - })->done(); - -})->done(); - -// Capture signals - SIGINT = Ctrl+C; SIGTERM = `kill` -$loop->addSignal(SIGINT, function (int $signal) use (&$channelRef, &$consumerTag) { - print "Consumer cancelled\n"; - $channelRef->cancel($consumerTag)->done(function() { - exit(); - }); -}); -$loop->addSignal(SIGTERM, function (int $signal) use (&$channelRef, &$consumerTag) { - print "Consumer cancelled\n"; - $channelRef->cancel($consumerTag)->done(function() { - exit(); - }); -}); - -$loop->run(); diff --git a/examples/worker.php b/examples/worker.php new file mode 100644 index 0000000..a68d9d8 --- /dev/null +++ b/examples/worker.php @@ -0,0 +1,66 @@ +cancel($consumerTag); + + Loop::addTimer(3, static function () { + Loop::stop(); + }); +}); +Loop::addSignal(SIGTERM, function (int $signal) use (&$channel, &$consumerTag) { + print 'Consumer cancelled\n'; + $channel->cancel($consumerTag); + + Loop::addTimer(3, static function () { + Loop::stop(); + }); +}); + +$clientConfig = [ + "host" => "rabbitmq.example.com", + "port" => 5672, + "vhost" => "/", + "user" => "appuser", + "password" => "apppass", +]; + +$client = new Client($clientConfig); +$channel = $client->channel(); +$channel->qos(0, 13); +$channel->queueDeclare('hello', false, false, false, false); +$channelRef = $channel; +echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; +$response = $channel->consume( + async(function (Message $message, Channel $channel) { + echo ' [x] Received ', $message->content, "\n"; + + // Do some work - we generate password hashes with a high cost + // sleep() gets interrupted by Ctrl+C so it's not very good for demos + // Performing multiple work units demonstrates that nothing is skipped + for ($i = 0; $i < 3; $i++) { + print 'WU {$i}\n'; + password_hash(random_bytes(255), PASSWORD_BCRYPT, ['cost' => 15]); + } + echo ' [x] Done ', $message->content, "\n"; + + $channel->ack($message); + }), + 'hello', + noAck: true, +); +$consumerTag = $response->consumerTag; diff --git a/phpunit.xml b/phpunit.xml index d353aad..77010fd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ - test/Bunny + test/ diff --git a/spec/generate.php b/spec/generate.php index d8c98a4..38a5444 100644 --- a/spec/generate.php +++ b/spec/generate.php @@ -1,7 +1,14 @@ \n"; $protocolReaderContent .= " */\n"; -$protocolReaderContent .= " abstract public function consumeBits(Buffer \$buffer, \$n);\n\n"; +$protocolReaderContent .= " abstract public function consumeBits(Buffer \$buffer, int \$n): array;\n\n"; $consumeMethodFrameContent = ""; $consumeMethodFrameContent .= " /**\n"; $consumeMethodFrameContent .= " * Consumes AMQP method frame.\n"; -$consumeMethodFrameContent .= " *\n"; -$consumeMethodFrameContent .= " * @param Buffer \$buffer\n"; -$consumeMethodFrameContent .= " * @return MethodFrame\n"; $consumeMethodFrameContent .= " */\n"; -$consumeMethodFrameContent .= " public function consumeMethodFrame(Buffer \$buffer)\n"; +$consumeMethodFrameContent .= " public function consumeMethodFrame(Buffer \$buffer): MethodFrame\n"; $consumeMethodFrameContent .= " {\n"; $consumeMethodFrameContent .= " \$classId = \$buffer->consumeUint16();\n"; $consumeMethodFrameContent .= " \$methodId = \$buffer->consumeUint16();\n"; @@ -183,6 +188,9 @@ function amqpTypeToLength($type, $e) $consumeMethodFrameContent .= " "; $protocolWriterContent = "append('AMQP');\n"; $protocolWriterContent .= " \$buffer->appendUint8(0);\n"; @@ -236,187 +244,202 @@ function amqpTypeToLength($type, $e) $appendMethodFrameContent .= " * @param MethodFrame \$frame\n"; $appendMethodFrameContent .= " * @param Buffer \$buffer\n"; $appendMethodFrameContent .= " */\n"; -$appendMethodFrameContent .= " public function appendMethodFrame(MethodFrame \$frame, Buffer \$buffer)\n"; +$appendMethodFrameContent .= " public function appendMethodFrame(MethodFrame \$frame, Buffer \$buffer): void\n"; $appendMethodFrameContent .= " {\n"; $appendMethodFrameContent .= " \$buffer->appendUint16(\$frame->classId);\n"; $appendMethodFrameContent .= " \$buffer->appendUint16(\$frame->methodId);\n"; $appendMethodFrameContent .= "\n"; $appendMethodFrameContent .= " "; -$clientMethodsContent = "{'major-version'}}-{$spec->{'minor-version'}}-{$spec->{'revision'}} client methods\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * THIS CLASS IS GENERATED FROM {$specFileName}. **DO NOT EDIT!**\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @author Jakub Kulhan \n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= "trait ClientMethods\n"; -$clientMethodsContent .= "{\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /** @var array */\n"; -$clientMethodsContent .= " private \$cache = [];\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Returns AMQP protocol reader.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return Protocol\\ProtocolReader\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function getReader();\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Returns read buffer.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return Buffer\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function getReadBuffer();\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Returns AMQP protocol writer.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return Protocol\\ProtocolWriter\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function getWriter();\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Returns write buffer.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return Buffer\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function getWriteBuffer();\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Reads data from stream to read buffer.\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function feedReadBuffer();\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Writes all data from write buffer to stream.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return boolean|PromiseInterface\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function flushWriteBuffer();\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Enqueues given frame for later processing.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @param Protocol\\AbstractFrame \$frame\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function enqueue(Protocol\\AbstractFrame \$frame);\n"; -$clientMethodsContent .= "\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * Returns frame max size.\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return int\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " abstract protected function getFrameMax();\n"; -$clientMethodsContent .= "\n"; - - -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * @param int \$channel\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return Protocol\\ContentHeaderFrame|PromiseInterface\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " public function awaitContentHeader(\$channel)\n"; -$clientMethodsContent .= " {\n"; -$clientMethodsContent .= " if (\$this instanceof Async\\Client) {\n"; -$clientMethodsContent .= " \$deferred = new Deferred();\n"; -$clientMethodsContent .= " \$this->addAwaitCallback(function (\$frame) use (\$deferred, \$channel) {\n"; -$clientMethodsContent .= " if (\$frame instanceof Protocol\\ContentHeaderFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " \$deferred->resolve(\$frame);\n"; -$clientMethodsContent .= " return true;\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " \$this->channelCloseOk(\$channel)->done(function () use (\$frame, \$deferred) {\n"; -$clientMethodsContent .= " \$deferred->reject(new ClientException(\$frame->replyText, \$frame->replyCode));\n"; -$clientMethodsContent .= " });\n"; -$clientMethodsContent .= " return true;\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; -$clientMethodsContent .= " \$this->connectionCloseOk()->done(function () use (\$frame, \$deferred) {\n"; -$clientMethodsContent .= " \$deferred->reject(new ClientException(\$frame->replyText, \$frame->replyCode));\n"; -$clientMethodsContent .= " });\n"; -$clientMethodsContent .= " return true;\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " return false;\n"; -$clientMethodsContent .= " });\n"; -$clientMethodsContent .= " return \$deferred->promise();\n"; -$clientMethodsContent .= " } else {\n"; -$clientMethodsContent .= " for (;;) {\n"; -$clientMethodsContent .= " while ((\$frame = \$this->getReader()->consumeFrame(\$this->getReadBuffer())) === null) {\n"; -$clientMethodsContent .= " \$this->feedReadBuffer();\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " if (\$frame instanceof Protocol\\ContentHeaderFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " return \$frame;\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " \$this->channelCloseOk(\$channel);\n"; -$clientMethodsContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; -$clientMethodsContent .= " \$this->connectionCloseOk();\n"; -$clientMethodsContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; -$clientMethodsContent .= " } else {\n"; -$clientMethodsContent .= " \$this->enqueue(\$frame);\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " throw new \\LogicException('This statement should be never reached.');\n"; -$clientMethodsContent .= " }\n\n"; -$clientMethodsContent .= " /**\n"; -$clientMethodsContent .= " * @param int \$channel\n"; -$clientMethodsContent .= " *\n"; -$clientMethodsContent .= " * @return Protocol\\ContentBodyFrame|PromiseInterface\n"; -$clientMethodsContent .= " */\n"; -$clientMethodsContent .= " public function awaitContentBody(\$channel)\n"; -$clientMethodsContent .= " {\n"; -$clientMethodsContent .= " if (\$this instanceof Async\\Client) {\n"; -$clientMethodsContent .= " \$deferred = new Deferred();\n"; -$clientMethodsContent .= " \$this->addAwaitCallback(function (\$frame) use (\$deferred, \$channel) {\n"; -$clientMethodsContent .= " if (\$frame instanceof Protocol\\ContentBodyFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " \$deferred->resolve(\$frame);\n"; -$clientMethodsContent .= " return true;\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " \$this->channelCloseOk(\$channel)->done(function () use (\$frame, \$deferred) {\n"; -$clientMethodsContent .= " \$deferred->reject(new ClientException(\$frame->replyText, \$frame->replyCode));\n"; -$clientMethodsContent .= " });\n"; -$clientMethodsContent .= " return true;\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; -$clientMethodsContent .= " \$this->connectionCloseOk()->done(function () use (\$frame, \$deferred) {\n"; -$clientMethodsContent .= " \$deferred->reject(new ClientException(\$frame->replyText, \$frame->replyCode));\n"; -$clientMethodsContent .= " });\n"; -$clientMethodsContent .= " return true;\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " return false;\n"; -$clientMethodsContent .= " });\n"; -$clientMethodsContent .= " return \$deferred->promise();\n"; -$clientMethodsContent .= " } else {\n"; -$clientMethodsContent .= " for (;;) {\n"; -$clientMethodsContent .= " while ((\$frame = \$this->getReader()->consumeFrame(\$this->getReadBuffer())) === null) {\n"; -$clientMethodsContent .= " \$this->feedReadBuffer();\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " if (\$frame instanceof Protocol\\ContentBodyFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " return \$frame;\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; -$clientMethodsContent .= " \$this->channelCloseOk(\$channel);\n"; -$clientMethodsContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; -$clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; -$clientMethodsContent .= " \$this->connectionCloseOk();\n"; -$clientMethodsContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; -$clientMethodsContent .= " } else {\n"; -$clientMethodsContent .= " \$this->enqueue(\$frame);\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " }\n"; -$clientMethodsContent .= " throw new \\LogicException('This statement should be never reached.');\n"; -$clientMethodsContent .= " }\n\n"; +$connectionContent = "{'major-version'}}-{$spec->{'minor-version'}}-{$spec->{'revision'}} client methods\n"; +$connectionContent .= " *\n"; +$connectionContent .= " * THIS CLASS IS GENERATED FROM {$specFileName}. **DO NOT EDIT!**\n"; +$connectionContent .= " *\n"; +$connectionContent .= " * @author Jakub Kulhan \n"; +$connectionContent .= " */\n"; +$connectionContent .= "final class Connection\n"; +$connectionContent .= "{\n"; +$connectionContent .= " protected ?TimerInterface \$heartbeatTimer = null;\n"; +$connectionContent .= "\n"; +$connectionContent .= " /** @var float microtime of last write */\n"; +$connectionContent .= " protected float \$lastWrite = 0.0;\n"; +$connectionContent .= "\n"; +$connectionContent .= " private array \$cache = [];\n"; +$connectionContent .= "\n"; +$connectionContent .= " /** @var array */\n"; +$connectionContent .= " private array \$awaitList = [];\n"; +$connectionContent .= "\n"; +$connectionContent .= " public function __construct(\n"; +$connectionContent .= " private readonly Client \$client,\n"; +$connectionContent .= " private readonly ConnectionInterface \$connection,\n"; +$connectionContent .= " private readonly Buffer \$readBuffer,\n"; +$connectionContent .= " private readonly Buffer \$writeBuffer,\n"; +$connectionContent .= " private readonly ProtocolReader \$reader,\n"; +$connectionContent .= " private readonly ProtocolWriter \$writer,\n"; +$connectionContent .= " private readonly Channels \$channels,\n"; +$connectionContent .= " private readonly array \$options = [],\n"; +$connectionContent .= " ) {\n"; +$connectionContent .= " \$this->connection->on('data', function (string \$data): void {\n"; +$connectionContent .= " \$this->readBuffer->append(\$data);\n"; +$connectionContent .= "\n"; +$connectionContent .= " while ((\$frame = \$this->reader->consumeFrame(\$this->readBuffer)) !== null) {\n"; +$connectionContent .= " \$frameInAwaitList = false;\n"; +$connectionContent .= " foreach (\$this->awaitList as \$index => \$frameHandler) {\n"; +$connectionContent .= " if (\$frameHandler['filter'](\$frame)) {\n"; +$connectionContent .= " unset(\$this->awaitList[\$index]);\n"; +$connectionContent .= " \$frameHandler['promise']->resolve(\$frame);\n"; +$connectionContent .= " \$frameInAwaitList = true;\n"; +$connectionContent .= " }\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " if (\$frameInAwaitList) {\n"; +$connectionContent .= " continue;\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " if (\$frame->channel === 0) {\n"; +$connectionContent .= " \$this->onFrameReceived(\$frame);\n"; +$connectionContent .= " continue;\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " if (!\$this->channels->has(\$frame->channel)) {\n"; +$connectionContent .= " throw new ClientException(\n"; +$connectionContent .= " \"Received frame #{\$frame->type} on closed channel #{\$frame->channel}.\"\n"; +$connectionContent .= " );\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " \$this->channels->get(\$frame->channel)->onFrameReceived(\$frame);\n"; +$connectionContent .= " }\n"; +$connectionContent .= " });\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " public function disconnect(int \$code, string \$reason)\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$this->connectionClose(\$code, 0, 0, \$reason);\n"; +$connectionContent .= " \$this->connection->close();\n"; +$connectionContent .= "\n"; +$connectionContent .= " if (\$this->heartbeatTimer === null) {\n"; +$connectionContent .= " return;\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " Loop::cancelTimer(\$this->heartbeatTimer);\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " /**\n"; +$connectionContent .= " * Callback after connection-level frame has been received.\n"; +$connectionContent .= " *\n"; +$connectionContent .= " * @param AbstractFrame \$frame\n"; +$connectionContent .= " */\n"; +$connectionContent .= " private function onFrameReceived(AbstractFrame \$frame)\n"; +$connectionContent .= " {\n"; +$connectionContent .= " if (\$frame instanceof MethodFrame) {\n"; +$connectionContent .= " if (\$frame instanceof MethodConnectionCloseFrame) {\n"; +$connectionContent .= " \$this->disconnect(Constants::STATUS_CONNECTION_FORCED, \"Connection closed by server: ({\$frame->replyCode}) \" . \$frame->replyText);\n"; +$connectionContent .= " throw new ClientException('Connection closed by server: ' . \$frame->replyText, \$frame->replyCode);\n"; +$connectionContent .= " }\n"; +$connectionContent .= " } elseif (\$frame instanceof ContentHeaderFrame) {\n"; +$connectionContent .= " \$this->disconnect(Constants::STATUS_UNEXPECTED_FRAME, 'Got header frame on connection channel (#0).');\n"; +$connectionContent .= " } elseif (\$frame instanceof ContentBodyFrame) {\n"; +$connectionContent .= " \$this->disconnect(Constants::STATUS_UNEXPECTED_FRAME, 'Got body frame on connection channel (#0).');\n"; +$connectionContent .= " } elseif (\$frame instanceof HeartbeatFrame) {\n"; +$connectionContent .= " return;\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " throw new ClientException('Unhandled frame ' . get_class(\$frame) . '.');\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " public function appendProtocolHeader(): void\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$this->writer->appendProtocolHeader(\$this->writeBuffer);\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " public function flushWriteBuffer(): void\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$data = \$this->writeBuffer->read(\$this->writeBuffer->getLength());\n"; +$connectionContent .= " \$this->writeBuffer->discard(strlen(\$data));\n"; +$connectionContent .= "\n"; +$connectionContent .= " \$this->lastWrite = microtime(true);\n"; +$connectionContent .= " if (!\$this->connection->write(\$data)) {\n"; +$connectionContent .= " await(new Promise(function (callable \$resolve): void {\n"; +$connectionContent .= " \$this->connection->once('drain', static fn () => \$resolve(null));\n"; +$connectionContent .= " }));\n"; +$connectionContent .= " }\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " public function awaitContentHeader(int \$channel): ContentHeaderFrame\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$deferred = new Deferred();\n"; +$connectionContent .= " \$this->awaitList[] = [\n"; +$connectionContent .= " 'filter' => function (AbstractFrame \$frame) use (\$channel): bool {\n"; +$connectionContent .= " if (\$frame instanceof Protocol\\ContentHeaderFrame && \$frame->channel === \$channel) {\n"; +$connectionContent .= " return true;\n"; +$connectionContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; +$connectionContent .= " \$this->channelCloseOk(\$channel);\n"; +$connectionContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; +$connectionContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; +$connectionContent .= " \$this->connectionCloseOk();\n"; +$connectionContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " return false;\n"; +$connectionContent .= " },\n"; +$connectionContent .= " 'promise' => \$deferred,\n"; +$connectionContent .= " ];\n"; +$connectionContent .= "\n"; +$connectionContent .= " return await(\$deferred->promise());\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " public function awaitContentBody(int \$channel): ContentBodyFrame\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$deferred = new Deferred();\n"; +$connectionContent .= " \$this->awaitList[] = [\n"; +$connectionContent .= " 'filter' => function (AbstractFrame \$frame) use (\$channel): bool {\n"; +$connectionContent .= " if (\$frame instanceof Protocol\\ContentBodyFrame && \$frame->channel === \$channel) {\n"; +$connectionContent .= " return true;\n"; +$connectionContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; +$connectionContent .= " \$this->channelCloseOk(\$channel);\n"; +$connectionContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; +$connectionContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; +$connectionContent .= " \$this->connectionCloseOk();\n"; +$connectionContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " return false;\n"; +$connectionContent .= " },\n"; +$connectionContent .= " 'promise' => \$deferred,\n"; +$connectionContent .= " ];\n"; +$connectionContent .= "\n"; +$connectionContent .= " return await(\$deferred->promise());\n"; +$connectionContent .= " }\n"; + $channelMethodsContent = "classes as $class) { @@ -457,6 +476,9 @@ function amqpTypeToLength($type, $e) foreach ($class->methods as $method) { $className = "Method" . ucfirst($class->name) . dashedToCamel($method->name) . "Frame"; $content = "getChannelId()"]; $channelArguments = []; - $channelDocComment = ""; $hasNowait = false; if ($class->id !== 10) { - $clientArguments[] = "\$channel"; + $clientArguments[] = "int \$channel"; $clientSetters[] = "\$frame->channel = \$channel;"; } if (isset($method->content) && $method->content) { - $clientArguments[] = "\$body"; + $clientArguments[] = "string \$body"; $clientArguments[] = "array \$headers = []"; - $channelArguments[] = "\$body"; + $channelArguments[] = "string \$body"; $channelArguments[] = "array \$headers = []"; - $channelDocComment .= " * @param string \$body\n"; - $channelDocComment .= " * @param array \$headers\n"; $channelClientArguments[] = "\$body"; $channelClientArguments[] = "\$headers"; } @@ -562,7 +581,10 @@ function amqpTypeToLength($type, $e) array_unshift($payloadSizeExpressions, $staticPayloadSize); $previousType = null; - foreach ($method->arguments as $argument) { + foreach ([ + ...array_filter($method->arguments, static fn (\stdClass $argument): bool => !(isset($argument->{'default-value'}) || (isset($argument->{'default-value'}) && $argument->{'default-value'} instanceof \stdClass))), + ...array_filter($method->arguments, static fn (\stdClass $argument): bool => isset($argument->{'default-value'}) || (isset($argument->{'default-value'}) && $argument->{'default-value'} instanceof \stdClass)), + ] as $argument) { if (isset($argument->type)) { $type = $argument->type; } elseif (isset($argument->domain)) { @@ -581,7 +603,7 @@ function amqpTypeToLength($type, $e) } elseif ($class->id === 40 && $method->id === 10 && $name === "type") { $name = "exchangeType"; } - $properties .= " /** @var " . amqpTypeToPhpType($type) . " */\n"; + $properties .= " /** @var " . amqpTypeToPhpType($type) . (amqpTypeToPhpType($type) === 'array' ? '' : '') . " */\n"; $defaultValue = null; if (isset($argument->{'default-value'}) && $argument->{'default-value'} instanceof \stdClass) { $defaultValue = "[]"; @@ -590,6 +612,33 @@ function amqpTypeToLength($type, $e) } $properties .= " public \${$name}" . ($defaultValue !== null ? " = {$defaultValue}" : "") . ";\n\n"; + if (strpos($name, "reserved") !== 0) { + $clientArguments[] = amqpTypeToPhpType($type) . ' $' . $name . ($defaultValue !== null ? " = {$defaultValue}" : ""); + $channelArguments[] = amqpTypeToPhpType($type) . ' $' . $name . ($defaultValue !== null ? " = {$defaultValue}" : ""); + $channelClientArguments[] = "\${$name}"; + } + } + + foreach ($method->arguments as $argument) { + if (isset($argument->type)) { + $type = $argument->type; + } elseif (isset($argument->domain)) { + $type = domainToType($argument->domain); + } else { + throw new \InvalidArgumentException("{$class->name}.{$method->name}({$argument->name})"); + } + + $name = lcfirst(dashedToCamel($argument->name)); + if ($class->id === 10 && $method->id === 50 || $class->id === 20 && $method->id === 40) { + if ($name === "classId") { + $name = "closeClassId"; + } elseif ($name === "methodId") { + $name = "closeMethodId"; + } + } elseif ($class->id === 40 && $method->id === 10 && $name === "type") { + $name = "exchangeType"; + } + if ($type === "bit") { $bitVars[] = "\$frame->{$name}"; } else { @@ -608,14 +657,14 @@ function amqpTypeToLength($type, $e) if ($previousType === "bit") { $appendContent .= " \$this->appendBits([" . implode(", ", $appendBitExpressions) . "], \$buffer);\n"; $appendBitExpressions = []; - $clientAppendContent .= " \$this->getWriter()->appendBits([" . implode(", ", $clientAppendBitExpressions) . "], \$buffer);\n"; + $clientAppendContent .= " \$this->writer->appendBits([" . implode(", ", $clientAppendBitExpressions) . "], \$buffer);\n"; $clientAppendBitExpressions = []; } $appendContent .= " " . amqpTypeToAppend($type, "\$frame->{$name}") . ";\n"; if (strpos($name, "reserved") === 0) { $clientAppendContent .= " " . amqpTypeToAppend($type, "0") . ";\n"; } elseif ($type === "table") { - $clientAppendContent .= " \$this->getWriter()->appendTable(\${$name}, \$buffer);\n"; + $clientAppendContent .= " \$this->writer->appendTable(\${$name}, \$buffer);\n"; } else { $clientAppendContent .= " " . amqpTypeToAppend($type, "\${$name}") . ";\n"; } @@ -624,19 +673,14 @@ function amqpTypeToLength($type, $e) $previousType = $type; if (strpos($name, "reserved") !== 0) { - $clientArguments[] = "\${$name}" . ($defaultValue !== null ? " = {$defaultValue}" : ""); $clientSetters[] = "\$frame->{$name} = \${$name};"; - - $channelArguments[] = "\${$name}" . ($defaultValue !== null ? " = {$defaultValue}" : ""); - $channelDocComment .= " * @param " . amqpTypeToPhpType($type) . " \${$name}\n"; - $channelClientArguments[] = "\${$name}"; } } if ($previousType === "bit") { $appendContent .= " \$this->appendBits([" . implode(", ", $appendBitExpressions) . "], \$buffer);\n"; $appendBitExpressions = []; - $clientAppendContent .= " \$this->getWriter()->appendBits([" . implode(", ", $clientAppendBitExpressions) . "], \$buffer);\n"; + $clientAppendContent .= " \$this->writer->appendBits([" . implode(", ", $clientAppendBitExpressions) . "], \$buffer);\n"; $clientAppendBitExpressions = []; } @@ -660,7 +704,7 @@ function amqpTypeToLength($type, $e) $content .= " }\n\n"; $content .= $gettersSetters; $content .= "}\n"; - file_put_contents(__DIR__ . "/../src/Bunny/Protocol/{$className}.php", $content); + file_put_contents(__DIR__ . "/../src/Protocol/{$className}.php", $content); $consumeMethodFrameContent .= "if (\$methodId === {$methodIdConstant}) {\n"; $consumeMethodFrameContent .= $consumeContent; @@ -673,46 +717,46 @@ function amqpTypeToLength($type, $e) $methodName = dashedToCamel(($class->name !== "basic" ? $class->name . "-" : "") . $method->name); if (!isset($method->direction) || $method->direction === "CS") { - $clientMethodsContent .= " public function " . lcfirst($methodName) . "(" . implode(", ", $clientArguments) . ")\n"; - $clientMethodsContent .= " {\n"; + $connectionContent .= " public function " . lcfirst($methodName) . "(" . implode(", ", $clientArguments) . "): bool" . (isset($method->synchronous) && $method->synchronous ? "|Protocol\\" . dashedToCamel("method-" . $class->name . "-" . $method->name . "-ok-frame") : "") . ($class->id === 60 && $method->id === 70 ? "|Protocol\\MethodBasicGetEmptyFrame" : "") . "\n"; + $connectionContent .= " {\n"; if ($static) { - $clientMethodsContent .= " \$buffer = \$this->getWriteBuffer();\n"; + $connectionContent .= " \$buffer = \$this->writeBuffer;\n"; if ($class->id === 60 && $method->id === 40) { - $clientMethodsContent .= " \$ck = serialize([\$channel, \$headers, \$exchange, \$routingKey, \$mandatory, \$immediate]);\n"; - $clientMethodsContent .= " \$c = isset(\$this->cache[\$ck]) ? \$this->cache[\$ck] : null;\n"; - $clientMethodsContent .= " \$flags = 0; \$off0 = 0; \$len0 = 0; \$off1 = 0; \$len1 = 0; \$contentTypeLength = null; \$contentType = null; \$contentEncodingLength = null; \$contentEncoding = null; \$headersBuffer = null; \$deliveryMode = null; \$priority = null; \$correlationIdLength = null; \$correlationId = null; \$replyToLength = null; \$replyTo = null; \$expirationLength = null; \$expiration = null; \$messageIdLength = null; \$messageId = null; \$timestamp = null; \$typeLength = null; \$type = null; \$userIdLength = null; \$userId = null; \$appIdLength = null; \$appId = null; \$clusterIdLength = null; \$clusterId = null;\n"; - $clientMethodsContent .= " if (\$c) { \$buffer->append(\$c[0]); }\n"; - $clientMethodsContent .= " else {\n"; - $clientMethodsContent .= " \$off0 = \$buffer->getLength();\n"; + $connectionContent .= " \$ck = serialize([\$channel, \$headers, \$exchange, \$routingKey, \$mandatory, \$immediate]);\n"; + $connectionContent .= " \$c = isset(\$this->cache[\$ck]) ? \$this->cache[\$ck] : null;\n"; + $connectionContent .= " \$flags = 0; \$off0 = 0; \$len0 = 0; \$off1 = 0; \$len1 = 0; \$contentTypeLength = null; \$contentType = null; \$contentEncodingLength = null; \$contentEncoding = null; \$headersBuffer = null; \$deliveryMode = null; \$priority = null; \$correlationIdLength = null; \$correlationId = null; \$replyToLength = null; \$replyTo = null; \$expirationLength = null; \$expiration = null; \$messageIdLength = null; \$messageId = null; \$timestamp = null; \$typeLength = null; \$type = null; \$userIdLength = null; \$userId = null; \$appIdLength = null; \$appId = null; \$clusterIdLength = null; \$clusterId = null;\n"; + $connectionContent .= " if (\$c) { \$buffer->append(\$c[0]); }\n"; + $connectionContent .= " else {\n"; + $connectionContent .= " \$off0 = \$buffer->getLength();\n"; } - $clientMethodsContent .= " \$buffer->appendUint8(" . Constants::FRAME_METHOD . ");\n"; - $clientMethodsContent .= " \$buffer->appendUint16(" . ($class->id === 10 ? Constants::CONNECTION_CHANNEL : "\$channel") . ");\n"; - $clientMethodsContent .= " \$buffer->appendUint32(" . implode(" + ", $payloadSizeExpressions) . ");\n"; + $connectionContent .= " \$buffer->appendUint8(" . Constants::FRAME_METHOD . ");\n"; + $connectionContent .= " \$buffer->appendUint16(" . ($class->id === 10 ? Constants::CONNECTION_CHANNEL : "\$channel") . ");\n"; + $connectionContent .= " \$buffer->appendUint32(" . implode(" + ", $payloadSizeExpressions) . ");\n"; } else { - $clientMethodsContent .= " \$buffer = new Buffer();\n"; + $connectionContent .= " \$buffer = new Buffer();\n"; } - $clientMethodsContent .= " \$buffer->appendUint16({$class->id});\n"; - $clientMethodsContent .= " \$buffer->appendUint16({$method->id});\n"; - $clientMethodsContent .= $clientAppendContent; + $connectionContent .= " \$buffer->appendUint16({$class->id});\n"; + $connectionContent .= " \$buffer->appendUint16({$method->id});\n"; + $connectionContent .= $clientAppendContent; if ($static) { - $clientMethodsContent .= " \$buffer->appendUint8(" . Constants::FRAME_END . ");\n"; + $connectionContent .= " \$buffer->appendUint8(" . Constants::FRAME_END . ");\n"; } else { - $clientMethodsContent .= " \$frame = new Protocol\\MethodFrame({$class->id}, {$method->id});\n"; - $clientMethodsContent .= " \$frame->channel = " . ($class->id === 10 ? Constants::CONNECTION_CHANNEL : "\$channel") . ";\n"; - $clientMethodsContent .= " \$frame->payloadSize = \$buffer->getLength();\n"; - $clientMethodsContent .= " \$frame->payload = \$buffer;\n"; - $clientMethodsContent .= " \$this->getWriter()->appendFrame(\$frame, \$this->getWriteBuffer());\n"; + $connectionContent .= " \$frame = new Protocol\\MethodFrame({$class->id}, {$method->id});\n"; + $connectionContent .= " \$frame->channel = " . ($class->id === 10 ? Constants::CONNECTION_CHANNEL : "\$channel") . ";\n"; + $connectionContent .= " \$frame->payloadSize = \$buffer->getLength();\n"; + $connectionContent .= " \$frame->payload = \$buffer;\n"; + $connectionContent .= " \$this->writer->appendFrame(\$frame, \$this->writeBuffer);\n"; } if (isset($method->content) && $method->content) { if (!$static) { - $clientMethodsContent .= " \$buffer = \$this->getWriteBuffer();\n"; + $connectionContent .= " \$buffer = \$this->writeBuffer;\n"; } // FIXME: respect max body size agreed upon connection.tune - $clientMethodsContent .= " \$s = 14;\n"; + $connectionContent .= " \$s = 14;\n"; foreach ([ @@ -732,41 +776,41 @@ function amqpTypeToLength($type, $e) ] as $flag => $property ) { list($propertyName, $staticSize, $dynamicSize) = $property; - $clientMethodsContent .= " if (isset(\$headers['{$propertyName}'])) {\n"; - $clientMethodsContent .= " \$flags |= {$flag};\n"; - $clientMethodsContent .= " \$" . lcfirst(dashedToCamel($propertyName)) . " = \$headers['{$propertyName}'];\n"; + $connectionContent .= " if (isset(\$headers['{$propertyName}'])) {\n"; + $connectionContent .= " \$flags |= {$flag};\n"; + $connectionContent .= " \$" . lcfirst(dashedToCamel($propertyName)) . " = \$headers['{$propertyName}'];\n"; if ($staticSize) { - $clientMethodsContent .= " \$s += {$staticSize};\n"; + $connectionContent .= " \$s += {$staticSize};\n"; } if ($dynamicSize) { - $clientMethodsContent .= " \$s += {$dynamicSize};\n"; + $connectionContent .= " \$s += {$dynamicSize};\n"; } - $clientMethodsContent .= " unset(\$headers['{$propertyName}']);\n"; - $clientMethodsContent .= " }\n"; + $connectionContent .= " unset(\$headers['{$propertyName}']);\n"; + $connectionContent .= " }\n"; } - $clientMethodsContent .= " if (!empty(\$headers)) {\n"; - $clientMethodsContent .= " \$flags |= " . ContentHeaderFrame::FLAG_HEADERS . ";\n"; - $clientMethodsContent .= " \$this->getWriter()->appendTable(\$headers, \$headersBuffer = new Buffer());\n"; - $clientMethodsContent .= " \$s += \$headersBuffer->getLength();\n"; - $clientMethodsContent .= " }\n"; - - $clientMethodsContent .= " \$buffer->appendUint8(" . Constants::FRAME_HEADER . ");\n"; - $clientMethodsContent .= " \$buffer->appendUint16(\$channel);\n"; - $clientMethodsContent .= " \$buffer->appendUint32(\$s);\n"; - $clientMethodsContent .= " \$buffer->appendUint16({$class->id});\n"; - $clientMethodsContent .= " \$buffer->appendUint16(0);\n"; + $connectionContent .= " if (!empty(\$headers)) {\n"; + $connectionContent .= " \$flags |= " . ContentHeaderFrame::FLAG_HEADERS . ";\n"; + $connectionContent .= " \$this->writer->appendTable(\$headers, \$headersBuffer = new Buffer());\n"; + $connectionContent .= " \$s += \$headersBuffer->getLength();\n"; + $connectionContent .= " }\n"; + + $connectionContent .= " \$buffer->appendUint8(" . Constants::FRAME_HEADER . ");\n"; + $connectionContent .= " \$buffer->appendUint16(\$channel);\n"; + $connectionContent .= " \$buffer->appendUint32(\$s);\n"; + $connectionContent .= " \$buffer->appendUint16({$class->id});\n"; + $connectionContent .= " \$buffer->appendUint16(0);\n"; if ($class->id === 60 && $method->id === 40) { - $clientMethodsContent .= " \$len0 = \$buffer->getLength() - \$off0;\n"; - $clientMethodsContent .= " }\n"; + $connectionContent .= " \$len0 = \$buffer->getLength() - \$off0;\n"; + $connectionContent .= " }\n"; } - $clientMethodsContent .= " \$buffer->appendUint64(strlen(\$body));\n"; + $connectionContent .= " \$buffer->appendUint64(strlen(\$body));\n"; if ($class->id === 60 && $method->id === 40) { - $clientMethodsContent .= " if (\$c) { \$buffer->append(\$c[1]); }\n"; - $clientMethodsContent .= " else {\n"; - $clientMethodsContent .= " \$off1 = \$buffer->getLength();\n"; + $connectionContent .= " if (\$c) { \$buffer->append(\$c[1]); }\n"; + $connectionContent .= " else {\n"; + $connectionContent .= " \$off1 = \$buffer->getLength();\n"; } - $clientMethodsContent .= " \$buffer->appendUint16(\$flags);\n"; + $connectionContent .= " \$buffer->appendUint16(\$flags);\n"; foreach ([ ContentHeaderFrame::FLAG_CONTENT_TYPE => "\$buffer->appendUint8(\$contentTypeLength); \$buffer->append(\$contentType);", @@ -778,123 +822,85 @@ function amqpTypeToLength($type, $e) ContentHeaderFrame::FLAG_REPLY_TO => "\$buffer->appendUint8(\$replyToLength); \$buffer->append(\$replyTo);", ContentHeaderFrame::FLAG_EXPIRATION => "\$buffer->appendUint8(\$expirationLength); \$buffer->append(\$expiration);", ContentHeaderFrame::FLAG_MESSAGE_ID => "\$buffer->appendUint8(\$messageIdLength); \$buffer->append(\$messageId);", - ContentHeaderFrame::FLAG_TIMESTAMP => "\$this->getWriter()->appendTimestamp(\$timestamp, \$buffer);", + ContentHeaderFrame::FLAG_TIMESTAMP => "\$this->writer->appendTimestamp(\$timestamp, \$buffer);", ContentHeaderFrame::FLAG_TYPE => "\$buffer->appendUint8(\$typeLength); \$buffer->append(\$type);", ContentHeaderFrame::FLAG_USER_ID => "\$buffer->appendUint8(\$userIdLength); \$buffer->append(\$userId);", ContentHeaderFrame::FLAG_APP_ID => "\$buffer->appendUint8(\$appIdLength); \$buffer->append(\$appId);", ContentHeaderFrame::FLAG_CLUSTER_ID => "\$buffer->appendUint8(\$clusterIdLength); \$buffer->append(\$clusterId);", ] as $flag => $property ) { - $clientMethodsContent .= " if (\$flags & {$flag}) {\n"; - $clientMethodsContent .= " {$property}\n"; - $clientMethodsContent .= " }\n"; + $connectionContent .= " if (\$flags & {$flag}) {\n"; + $connectionContent .= " {$property}\n"; + $connectionContent .= " }\n"; } - $clientMethodsContent .= " \$buffer->appendUint8(" . Constants::FRAME_END . ");\n"; + $connectionContent .= " \$buffer->appendUint8(" . Constants::FRAME_END . ");\n"; if ($class->id === 60 && $method->id === 40) { - $clientMethodsContent .= " \$len1 = \$buffer->getLength() - \$off1;\n"; - $clientMethodsContent .= " }\n"; - $clientMethodsContent .= " if (!\$c) {\n"; - $clientMethodsContent .= " \$this->cache[\$ck] = [\$buffer->read(\$len0, \$off0), \$buffer->read(\$len1, \$off1)];\n"; - $clientMethodsContent .= " if (count(\$this->cache) > 100) { reset(\$this->cache); unset(\$this->cache[key(\$this->cache)]); }\n"; - $clientMethodsContent .= " }\n"; + $connectionContent .= " \$len1 = \$buffer->getLength() - \$off1;\n"; + $connectionContent .= " }\n"; + $connectionContent .= " if (!\$c) {\n"; + $connectionContent .= " \$this->cache[\$ck] = [\$buffer->read(\$len0, \$off0), \$buffer->read(\$len1, \$off1)];\n"; + $connectionContent .= " if (count(\$this->cache) > 100) { reset(\$this->cache); unset(\$this->cache[key(\$this->cache)]); }\n"; + $connectionContent .= " }\n"; } - $clientMethodsContent .= " for (\$payloadMax = \$this->getFrameMax() - 8 /* frame preface and frame end */, \$i = 0, \$l = strlen(\$body); \$i < \$l; \$i += \$payloadMax) {\n"; - $clientMethodsContent .= " \$payloadSize = \$l - \$i; if (\$payloadSize > \$payloadMax) { \$payloadSize = \$payloadMax; }\n"; - $clientMethodsContent .= " \$buffer->appendUint8(" . Constants::FRAME_BODY . ");\n"; - $clientMethodsContent .= " \$buffer->appendUint16(\$channel);\n"; - $clientMethodsContent .= " \$buffer->appendUint32(\$payloadSize);\n"; - $clientMethodsContent .= " \$buffer->append(substr(\$body, \$i, \$payloadSize));\n"; - $clientMethodsContent .= " \$buffer->appendUint8(" . Constants::FRAME_END . ");\n"; - $clientMethodsContent .= " }\n"; + $connectionContent .= " for (\$payloadMax = \$this->client->frameMax - 8 /* frame preface and frame end */, \$i = 0, \$l = strlen(\$body); \$i < \$l; \$i += \$payloadMax) {\n"; + $connectionContent .= " \$payloadSize = \$l - \$i; if (\$payloadSize > \$payloadMax) { \$payloadSize = \$payloadMax; }\n"; + $connectionContent .= " \$buffer->appendUint8(" . Constants::FRAME_BODY . ");\n"; + $connectionContent .= " \$buffer->appendUint16(\$channel);\n"; + $connectionContent .= " \$buffer->appendUint32(\$payloadSize);\n"; + $connectionContent .= " \$buffer->append(substr(\$body, \$i, \$payloadSize));\n"; + $connectionContent .= " \$buffer->appendUint8(" . Constants::FRAME_END . ");\n"; + $connectionContent .= " }\n"; } if (isset($method->synchronous) && $method->synchronous && $hasNowait) { - $clientMethodsContent .= " if (\$nowait) {\n"; - $clientMethodsContent .= " return \$this->flushWriteBuffer();\n"; - $clientMethodsContent .= " } else {\n"; - $clientMethodsContent .= " \$this->flushWriteBuffer();\n"; - $clientMethodsContent .= " return \$this->await" . $methodName . "Ok(" . ($class->id !== 10 ? "\$channel" : "") . ");\n"; - $clientMethodsContent .= " }\n"; + $connectionContent .= " \$this->flushWriteBuffer();\n"; + $connectionContent .= " if (!\$nowait) {\n"; + $connectionContent .= " return \$this->await" . $methodName . "Ok(" . ($class->id !== 10 ? "\$channel" : "") . ");\n"; + $connectionContent .= " }\n"; + $connectionContent .= " return false;\n"; } elseif (isset($method->synchronous) && $method->synchronous) { - $clientMethodsContent .= " \$this->flushWriteBuffer();\n"; - $clientMethodsContent .= " return \$this->await" . $methodName . "Ok(" . ($class->id !== 10 ? "\$channel" : "") . ");\n"; + $connectionContent .= " \$this->flushWriteBuffer();\n"; + $connectionContent .= " return \$this->await" . $methodName . "Ok(" . ($class->id !== 10 ? "\$channel" : "") . ");\n"; } else { - $clientMethodsContent .= " return \$this->flushWriteBuffer();\n"; + $connectionContent .= " \$this->flushWriteBuffer();\n"; + $connectionContent .= " return false;\n"; } - $clientMethodsContent .= " }\n\n"; + $connectionContent .= " }\n\n"; } if (!isset($method->direction) || $method->direction === "SC") { - $clientMethodsContent .= " /**\n"; - if ($class->id !== 10) { - $clientMethodsContent .= " * @param int \$channel\n"; - $clientMethodsContent .= " *\n"; - } - $clientMethodsContent .= " * @return Protocol\\{$className}" . ($class->id === 60 && $method->id === 71 ? "|Protocol\\" . str_replace("GetOk", "GetEmpty", $className) : "") . "|PromiseInterface\n"; - $clientMethodsContent .= " */\n"; - $clientMethodsContent .= " public function await" . $methodName . "(" . ($class->id !== 10 ? "\$channel" : "") . ")\n"; - $clientMethodsContent .= " {\n"; + $connectionContent .= " public function await" . $methodName . "(" . ($class->id !== 10 ? "int \$channel" : "") . "): Protocol\\{$className}" . ($class->id === 60 && $method->id === 71 ? '|Protocol\\' . str_replace("GetOk", "GetEmpty", $className) : "") . "\n"; + $connectionContent .= " {\n"; // async await - $clientMethodsContent .= " if (\$this instanceof Async\\Client) {\n"; - $clientMethodsContent .= " \$deferred = new Deferred();\n"; - $clientMethodsContent .= " \$this->addAwaitCallback(function (\$frame) use (\$deferred" . ($class->id !== 10 ? ", \$channel" : "") . ") {\n"; - $clientMethodsContent .= " if (\$frame instanceof Protocol\\{$className}" . ($class->id !== 10 ? " && \$frame->channel === \$channel" : "") . ") {\n"; - $clientMethodsContent .= " \$deferred->resolve(\$frame);\n"; - $clientMethodsContent .= " return true;\n"; + $connectionContent .= " \$deferred = new Deferred();\n"; + $connectionContent .= " \$this->awaitList[] = [\n"; + $connectionContent .= " 'filter' => function (Protocol\\AbstractFrame \$frame)" . ($class->id !== 10 ? " use (\$channel)" : "") . ": bool {\n"; + $connectionContent .= " if (\$frame instanceof Protocol\\{$className}" . ($class->id !== 10 ? " && \$frame->channel === \$channel" : "") . ") {\n"; + $connectionContent .= " return true;\n"; if ($class->id === 60 && $method->id === 71) { - $clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\" . str_replace("GetOk", "GetEmpty", $className) . ($class->id !== 10 ? " && \$frame->channel === \$channel" : "") . ") {\n"; - $clientMethodsContent .= " \$deferred->resolve(\$frame);\n"; - $clientMethodsContent .= " return true;\n"; + $connectionContent .= " } elseif (\$frame instanceof Protocol\\" . str_replace("GetOk", "GetEmpty", $className) . ($class->id !== 10 ? " && \$frame->channel === \$channel" : "") . ") {\n"; + $connectionContent .= " return true;\n"; } if ($class->id !== 10) { - $clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; - $clientMethodsContent .= " \$this->channelCloseOk(\$channel)->done(function () use (\$frame, \$deferred) {\n"; - $clientMethodsContent .= " \$deferred->reject(new ClientException(\$frame->replyText, \$frame->replyCode));\n"; - $clientMethodsContent .= " });\n"; - $clientMethodsContent .= " return true;\n"; + $connectionContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; + $connectionContent .= " \$this->channelCloseOk(\$channel);\n"; + $connectionContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; } - $clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; - $clientMethodsContent .= " \$this->connectionCloseOk()->done(function () use (\$frame, \$deferred) {\n"; - $clientMethodsContent .= " \$deferred->reject(new ClientException(\$frame->replyText, \$frame->replyCode));\n"; - $clientMethodsContent .= " });\n"; - $clientMethodsContent .= " return true;\n"; - $clientMethodsContent .= " }\n"; - $clientMethodsContent .= " return false;\n"; - $clientMethodsContent .= " });\n"; - $clientMethodsContent .= " return \$deferred->promise();\n"; - $clientMethodsContent .= " } else {\n"; - - // sync await - $clientMethodsContent .= " for (;;) {\n"; - $clientMethodsContent .= " while ((\$frame = \$this->getReader()->consumeFrame(\$this->getReadBuffer())) === null) {\n"; - $clientMethodsContent .= " \$this->feedReadBuffer();\n"; - $clientMethodsContent .= " }\n"; - $clientMethodsContent .= " if (\$frame instanceof Protocol\\{$className}" . ($class->id !== 10 ? " && \$frame->channel === \$channel" : "") . ") {\n"; - $clientMethodsContent .= " return \$frame;\n"; - if ($class->id === 60 && $method->id === 71) { - $clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\" . str_replace("GetOk", "GetEmpty", $className) . ($class->id !== 10 ? " && \$frame->channel === \$channel" : "") . ") {\n"; - $clientMethodsContent .= " return \$frame;\n"; - } - if ($class->id !== 10) { - $clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodChannelCloseFrame && \$frame->channel === \$channel) {\n"; - $clientMethodsContent .= " \$this->channelCloseOk(\$channel);\n"; - $clientMethodsContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; - } - $clientMethodsContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; - $clientMethodsContent .= " \$this->connectionCloseOk();\n"; - $clientMethodsContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; - $clientMethodsContent .= " } else {\n"; - $clientMethodsContent .= " \$this->enqueue(\$frame);\n"; - $clientMethodsContent .= " }\n"; - $clientMethodsContent .= " }\n"; - $clientMethodsContent .= " }\n"; - $clientMethodsContent .= " throw new \\LogicException('This statement should be never reached.');\n"; - $clientMethodsContent .= " }\n\n"; + $connectionContent .= " } elseif (\$frame instanceof Protocol\\MethodConnectionCloseFrame) {\n"; + $connectionContent .= " \$this->connectionCloseOk();\n"; + $connectionContent .= " throw new ClientException(\$frame->replyText, \$frame->replyCode);\n"; + $connectionContent .= " }\n"; + $connectionContent .= " return false;\n"; + $connectionContent .= " },\n"; + $connectionContent .= " 'promise' => \$deferred,\n"; + $connectionContent .= " ];\n"; + $connectionContent .= " return await(\$deferred->promise());\n"; + $connectionContent .= " }\n\n"; } if ($class->id !== 10 && @@ -904,12 +910,8 @@ function amqpTypeToLength($type, $e) ) { $channelMethodsContent .= " /**\n"; $channelMethodsContent .= " * Calls {$class->name}.{$method->name} AMQP method.\n"; - $channelMethodsContent .= " *\n"; - $channelMethodsContent .= $channelDocComment; - $channelMethodsContent .= " *\n"; - $channelMethodsContent .= " * @return boolean|Promise\\PromiseInterface" . (isset($method->synchronous) && $method->synchronous ? "|Protocol\\" . dashedToCamel("method-" . $class->name . "-" . $method->name . "-ok-frame") : "") . ($class->id === 60 && $method->id === 70 ? "|Protocol\\MethodBasicGetEmptyFrame" : "") . "\n"; $channelMethodsContent .= " */\n"; - $channelMethodsContent .= " public function " . lcfirst($methodName) . "(" . implode(", ", $channelArguments) . ")\n"; + $channelMethodsContent .= " public function " . lcfirst($methodName) . "(" . implode(", ", $channelArguments) . "): bool" . (isset($method->synchronous) && $method->synchronous ? "|Protocol\\" . dashedToCamel("method-" . $class->name . "-" . $method->name . "-ok-frame") : "") . ($class->id === 60 && $method->id === 70 ? "|Protocol\\MethodBasicGetEmptyFrame" : "") . "\n"; $channelMethodsContent .= " {\n"; $channelMethodsContent .= " return \$this->getClient()->" . lcfirst($methodName) . "(" . implode(", ", $channelClientArguments) . ");\n"; $channelMethodsContent .= " }\n\n"; @@ -933,7 +935,7 @@ function amqpTypeToLength($type, $e) $protocolReaderContent .= $consumeMethodFrameContent; $protocolReaderContent .= "}\n"; -file_put_contents(__DIR__ . "/../src/Bunny/Protocol/ProtocolReaderGenerated.php", $protocolReaderContent); +file_put_contents(__DIR__ . "/../src/Protocol/ProtocolReaderGenerated.php", $protocolReaderContent); $appendMethodFrameContent .= " {\n"; $appendMethodFrameContent .= " throw new ProtocolException('Unhandled method frame ' . get_class(\$frame) . '.');\n"; @@ -942,10 +944,36 @@ function amqpTypeToLength($type, $e) $protocolWriterContent .= $appendMethodFrameContent; $protocolWriterContent .= "}\n"; -file_put_contents(__DIR__ . "/../src/Bunny/Protocol/ProtocolWriterGenerated.php", $protocolWriterContent); - -$clientMethodsContent .= "}\n"; -file_put_contents(__DIR__ . "/../src/Bunny/ClientMethods.php", $clientMethodsContent); +file_put_contents(__DIR__ . "/../src/Protocol/ProtocolWriterGenerated.php", $protocolWriterContent); + +$connectionContent .= " public function startHeathbeatTimer(): void\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$this->heartbeatTimer = Loop::addTimer(\$this->options['heartbeat'], [\$this, 'onHeartbeat']);\n"; +$connectionContent .= " \$this->connection->on('drain', [\$this, 'onHeartbeat']);\n"; +$connectionContent .= " }\n"; +$connectionContent .= "\n"; +$connectionContent .= " /**\n"; +$connectionContent .= " * Callback when heartbeat timer timed out.\n"; +$connectionContent .= " */\n"; +$connectionContent .= " public function onHeartbeat()\n"; +$connectionContent .= " {\n"; +$connectionContent .= " \$now = microtime(true);\n"; +$connectionContent .= " \$nextHeartbeat = (\$this->lastWrite ?: \$now) + \$this->options['heartbeat'];\n"; +$connectionContent .= "\n"; +$connectionContent .= " if (\$now >= \$nextHeartbeat) {\n"; +$connectionContent .= " \$this->writer->appendFrame(new HeartbeatFrame(), \$this->writeBuffer);\n"; +$connectionContent .= " \$this->flushWriteBuffer();\n"; +$connectionContent .= "\n"; +$connectionContent .= " \$this->heartbeatTimer = Loop::addTimer(\$this->options['heartbeat'], [\$this, 'onHeartbeat']);\n"; +$connectionContent .= " if (is_callable(\$this->options['heartbeat_callback'] ?? null)) {\n"; +$connectionContent .= " \$this->options['heartbeat_callback'](\$this);\n"; +$connectionContent .= " }\n"; +$connectionContent .= " } else {\n"; +$connectionContent .= " \$this->heartbeatTimer = Loop::addTimer(\$nextHeartbeat - \$now, [\$this, 'onHeartbeat']);\n"; +$connectionContent .= " }\n"; +$connectionContent .= " }\n"; +$connectionContent .= "}\n"; +file_put_contents(__DIR__ . "/../src/Connection.php", $connectionContent); $channelMethodsContent .= "}\n"; -file_put_contents(__DIR__ . "/../src/Bunny/ChannelMethods.php", $channelMethodsContent); +file_put_contents(__DIR__ . "/../src/ChannelMethods.php", $channelMethodsContent); diff --git a/src/Bunny/AbstractClient.php b/src/Bunny/AbstractClient.php deleted file mode 100644 index 9c4d1c0..0000000 --- a/src/Bunny/AbstractClient.php +++ /dev/null @@ -1,515 +0,0 @@ - "exchangeDelete". - * Methods from "basic" class are not prefixed with "basic" - e.g. "basic.publish" is just "publish". - * - * @author Jakub Kulhan - */ -abstract class AbstractClient -{ - - use ClientMethods; - - /** @var array */ - protected $options; - - /** @var resource|null */ - protected $stream; - - /** @var int */ - protected $state = ClientStateEnum::NOT_CONNECTED; - - /** @var Buffer */ - protected $readBuffer; - - /** @var Buffer */ - protected $writeBuffer; - - /** @var ProtocolReader */ - protected $reader; - - /** @var ProtocolWriter */ - protected $writer; - - /** @var AbstractFrame[] */ - protected $queue; - - /** @var Channel[] */ - protected $channels = []; - - /** @var Promise\PromiseInterface|null */ - protected $disconnectPromise; - - /** @var int */ - protected $frameMax = 0xFFFF; - - /** @var int */ - protected $nextChannelId = 1; - - /** @var int */ - protected $channelMax = 0xFFFF; - - /** @var float microtime of last read*/ - protected $lastRead = 0.0; - - /** @var float microtime of last write */ - protected $lastWrite = 0.0; - - /** - * Constructor. - * - * @param array $options - */ - public function __construct(array $options = []) - { - if (!isset($options["host"])) { - $options["host"] = "127.0.0.1"; - } - - if (!isset($options["port"])) { - $options["port"] = 5672; - } - - if (!isset($options["vhost"])) { - if (isset($options["virtual_host"])) { - $options["vhost"] = $options["virtual_host"]; - unset($options["virtual_host"]); - } elseif (isset($options["path"])) { - $options["vhost"] = $options["path"]; - unset($options["path"]); - } else { - $options["vhost"] = "/"; - } - } - - if (!isset($options["user"])) { - if (isset($options["username"])) { - $options["user"] = $options["username"]; - unset($options["username"]); - } else { - $options["user"] = "guest"; - } - } - - if (!isset($options["password"])) { - if (isset($options["pass"])) { - $options["password"] = $options["pass"]; - unset($options["pass"]); - } else { - $options["password"] = "guest"; - } - } - - if (!isset($options["timeout"])) { - $options["timeout"] = 1; - } - - if (!isset($options["heartbeat"])) { - $options["heartbeat"] = 60.0; - } elseif ($options["heartbeat"] >= 2**15) { - throw new InvalidArgumentException("Heartbeat too high: the value is a signed int16."); - } - - if (is_callable($options['heartbeat_callback'] ?? null)) { - $this->options['heartbeat_callback'] = $options['heartbeat_callback']; - } - - $this->options = $options; - - $this->init(); - } - - /** - * Initializes instance. - */ - protected function init() - { - $this->state = ClientStateEnum::NOT_CONNECTED; - $this->readBuffer = new Buffer(); - $this->writeBuffer = new Buffer(); - $this->reader = new ProtocolReader(); - $this->writer = new ProtocolWriter(); - $this->queue = []; - } - - /** - * Returns AMQP protocol reader. - * - * @return ProtocolReader - */ - protected function getReader() - { - return $this->reader; - } - - /** - * Returns AMQP protocol writer. - * - * @return ProtocolWriter - */ - protected function getWriter() - { - return $this->writer; - } - - /** - * Returns read buffer. - * - * @return Buffer - */ - protected function getReadBuffer() - { - return $this->readBuffer; - } - - /** - * Returns write buffer. - * - * @return Buffer - */ - protected function getWriteBuffer() - { - return $this->writeBuffer; - } - - /** - * Enqueues given frame for later processing. - * - * @param AbstractFrame $frame - */ - protected function enqueue(AbstractFrame $frame) - { - $this->queue[] = $frame; - } - - /** - * Creates stream according to options passed in constructor. - * - * @return resource - */ - protected function getStream() - { - if ($this->stream === null) { - $streamScheme = 'tcp'; - - $context = stream_context_create(); - if (isset($this->options['ssl']) && is_array($this->options['ssl'])) { - if (!stream_context_set_option($context, ['ssl' => $this->options['ssl']])) { - throw new ClientException("Failed to set SSL-options."); - } - $streamScheme = 'ssl'; - } - - // see https://github.com/nrk/predis/blob/v1.0/src/Connection/StreamConnection.php - $uri = $streamScheme."://{$this->options["host"]}:{$this->options["port"]}"; - $flags = STREAM_CLIENT_CONNECT; - - if (isset($this->options["async_connect"]) && !!$this->options["async_connect"]) { - $flags |= STREAM_CLIENT_ASYNC_CONNECT; - } - - if (isset($this->options["persistent"]) && !!$this->options["persistent"]) { - $flags |= STREAM_CLIENT_PERSISTENT; - - if (!isset($this->options["path"])) { - throw new ClientException("If you need persistent connection, you have to specify 'path' option."); - } - - $uri .= (strpos($this->options["path"], "/") === 0) ? $this->options["path"] : "/" . $this->options["path"]; - } - - stream_context_set_option( - $context, [ - "socket" => [ - "tcp_nodelay" => true - ] - ]); - - $this->stream = @stream_socket_client($uri, $errno, $errstr, (float)$this->options["timeout"], $flags, $context); - - if (!$this->stream) { - throw new ClientException( - "Could not connect to {$this->options["host"]}:{$this->options["port"]}: {$errstr}.", - $errno - ); - } - - if (isset($this->options["read_write_timeout"])) { - $readWriteTimeout = (float)$this->options["read_write_timeout"]; - if ($readWriteTimeout < 0) { - $readWriteTimeout = -1; - } - $readWriteTimeoutSeconds = floor($readWriteTimeout); - $readWriteTimeoutMicroseconds = ($readWriteTimeout - $readWriteTimeoutSeconds) * 10e6; - stream_set_timeout($this->stream, $readWriteTimeoutSeconds, $readWriteTimeoutMicroseconds); - } - - if (isset($this->options["tcp_nodelay"]) && function_exists("socket_import_stream")) { - $socket = socket_import_stream($this->stream); - socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int)$this->options["tcp_nodelay"]); - } - - if ($this->options["async"]) { - stream_set_blocking($this->stream, 0); - } - } - - return $this->stream; - } - - /** - * Closes stream. - */ - protected function closeStream() - { - @fclose($this->stream); - $this->stream = null; - } - - /** - * Reads data from stream into {@link readBuffer}. - */ - protected function read() - { - $s = @fread($this->stream, $this->frameMax); - - if ($s === false) { - $info = stream_get_meta_data($this->stream); - - if (isset($info["timed_out"]) && $info["timed_out"]) { - $this->disconnect(Constants::STATUS_RESOURCE_ERROR, "Connection closed by server unexpectedly"); - throw new ClientException("Timeout reached while reading from stream."); - } - } - - if (@feof($this->stream)) { - $this->disconnect(Constants::STATUS_RESOURCE_ERROR, "Connection closed by server unexpectedly"); - throw new ClientException("Broken pipe or closed connection."); - } - - $this->readBuffer->append($s); - $this->lastRead = microtime(true); - } - - /** - * Writes data from {@link writeBuffer} to stream. - */ - protected function write() - { - if (($written = @fwrite($this->getStream(), $this->writeBuffer->read($this->writeBuffer->getLength()))) === false) { - throw new ClientException("Could not write data to socket."); - } - - if ($written === 0) { - throw new ClientException("Broken pipe or closed connection."); - } - - fflush($this->getStream()); // flush internal PHP buffers - - $this->writeBuffer->discard($written); - $this->lastWrite = microtime(true); - } - - /** - * Responds to authentication challenge - * - * @param MethodConnectionStartFrame $start - * @return boolean|Promise\PromiseInterface - */ - protected function authResponse(MethodConnectionStartFrame $start) - { - if (strpos($start->mechanisms, "AMQPLAIN") === false) { - throw new ClientException("Server does not support AMQPLAIN mechanism (supported: {$start->mechanisms})."); - } - - $responseBuffer = new Buffer(); - $this->writer->appendTable([ - "LOGIN" => $this->options["user"], - "PASSWORD" => $this->options["password"], - ], $responseBuffer); - $responseBuffer->discard(4); - - return $this->connectionStartOk([], "AMQPLAIN", $responseBuffer->read($responseBuffer->getLength()), "en_US"); - } - - /** - * Disconnects the client. - * - * Always returns a promise (even sync client) - * - * @param int $replyCode - * @param string $replyText - * @return Promise\PromiseInterface - */ - abstract public function disconnect($replyCode = 0, $replyText = ""); - - /** - * Returns true if client is connected to server. - * - * @return boolean - */ - public function isConnected() - { - return $this->state !== ClientStateEnum::NOT_CONNECTED && $this->state !== ClientStateEnum::ERROR; - } - - /** - * Returns current client state. - * - * @return int - */ - public function getState() - { - return $this->state; - } - - /** - * Creates and opens new channel. - * - * Channel gets first available channel id. - * - * @return Channel|Promise\PromiseInterface - */ - public function channel() - { - // since we not found any free channel, then make one - $channelId = $this->findChannelId(); - - $this->channels[$channelId] = new Channel($this, $channelId); - $response = $this->channelOpen($channelId); - - if ($response instanceof MethodChannelOpenOkFrame) { - return $this->channels[$channelId]; - - } elseif ($response instanceof Promise\PromiseInterface) { - return $response->then(function () use ($channelId) { - return $this->channels[$channelId]; - }); - - } else { - $this->state = ClientStateEnum::ERROR; - - throw new ClientException( - "channel.open unexpected response of type " . gettype($response) . - (is_object($response) ? "(" . get_class($response) . ")" : "") . - "." - ); - } - } - - /** - * Removes channel. - * - * @param int $channelId - * @return void - */ - public function removeChannel($channelId) - { - unset($this->channels[$channelId]); - } - - /** - * Callback after connection-level frame has been received. - * - * @param AbstractFrame $frame - */ - public function onFrameReceived(AbstractFrame $frame) - { - if ($frame instanceof MethodFrame) { - if ($frame instanceof MethodConnectionCloseFrame) { - $this->disconnect(Constants::STATUS_CONNECTION_FORCED, "Connection closed by server: ({$frame->replyCode}) " . $frame->replyText); - throw new ClientException("Connection closed by server: " . $frame->replyText, $frame->replyCode); - } else { - throw new ClientException("Unhandled method frame " . get_class($frame) . "."); - } - - } elseif ($frame instanceof ContentHeaderFrame) { - $this->disconnect(Constants::STATUS_UNEXPECTED_FRAME, "Got header frame on connection channel (#0)."); - - } elseif ($frame instanceof ContentBodyFrame) { - $this->disconnect(Constants::STATUS_UNEXPECTED_FRAME, "Got body frame on connection channel (#0)."); - - } elseif ($frame instanceof HeartbeatFrame) { - $this->lastRead = microtime(true); - - } else { - throw new ClientException("Unhandled frame " . get_class($frame) . "."); - } - } - - /** - * @return int - */ - protected function getFrameMax() - { - return $this->frameMax; - } - - /** - * Wait for messages on connection and process them. Will process messages for at most $maxSeconds. - * - * @param float $maxSeconds - * @return void - */ - abstract public function run($maxSeconds = null); - - - /** - * @return int - */ - protected function findChannelId() - { - // first check in range [next, max] ... - for ( - $channelId = $this->nextChannelId; - $channelId <= $this->channelMax; - ++$channelId - ) { - if (!isset($this->channels[$channelId])) { - $this->nextChannelId = $channelId + 1; - - return $channelId; - } - } - - // then check in range [min, next) ... - for ( - $channelId = 1; - $channelId < $this->nextChannelId; - ++$channelId - ) { - if (!isset($this->channels[$channelId])) { - $this->nextChannelId = $channelId + 1; - - return $channelId; - } - } - - throw new ClientException("No available channels"); - } - -} diff --git a/src/Bunny/Async/Client.php b/src/Bunny/Async/Client.php deleted file mode 100644 index 41ac1e0..0000000 --- a/src/Bunny/Async/Client.php +++ /dev/null @@ -1,347 +0,0 @@ - "exchangeDelete". - * Methods from "basic" class are not prefixed with "basic" - e.g. "basic.publish" is just "publish". - * - * Usage: - * - * $c = new Bunny\Async\Client([ - * "host" => "127.0.0.1", - * "port" => 5672, - * "vhost" => "/", - * "user" => "guest", - * "password" => "guest", - * ]); - * - * // $c->connect() returns React\Promise\PromiseInterface - * - * $c->connect()->then(function(Bunny\Async\Client $c) { - * // work with connected client - * - * }, function ($e) { - * // an exception occurred - * }); - * - * @author Jakub Kulhan - */ -class Client extends AbstractClient -{ - - /** @var LoopInterface */ - protected $eventLoop; - - /** @var Promise\PromiseInterface|null */ - protected $flushWriteBufferPromise; - - /** @var callable[] */ - protected $awaitCallbacks; - - /** @var Timer */ - protected $stopTimer; - - /** @var Timer */ - protected $heartbeatTimer; - - /** - * Constructor. - * - * @param LoopInterface $eventLoop - * @param array $options see {@link AbstractClient} for available options - */ - public function __construct(LoopInterface $eventLoop = null, array $options = []) - { - $options["async"] = true; - parent::__construct($options); - - if ($eventLoop === null) { - $eventLoop = Factory::create(); - } - - $this->eventLoop = $eventLoop; - } - - /** - * Destructor. - * - * Clean shutdown = disconnect if connected. - */ - public function __destruct() - { - if ($this->isConnected()) { - $this->disconnect(); - } - } - - /** - * Initializes instance. - */ - protected function init() - { - parent::init(); - $this->flushWriteBufferPromise = null; - $this->awaitCallbacks = []; - $this->disconnectPromise = null; - } - - /** - * Calls {@link eventLoop}'s run() method. Processes messages for at most $maxSeconds. - * - * @param float $maxSeconds - */ - public function run($maxSeconds = null) - { - if ($maxSeconds !== null) { - $this->stopTimer = $this->eventLoop->addTimer($maxSeconds, function () { - $this->stop(); - }); - } - - $this->eventLoop->run(); - } - - /** - * Calls {@link eventLoop}'s stop() method. - */ - public function stop() - { - if ($this->stopTimer) { - $this->eventLoop->cancelTimer($this->stopTimer); - $this->stopTimer = null; - } - - $this->eventLoop->stop(); - } - - /** - * Reads data from stream to read buffer. - */ - protected function feedReadBuffer() - { - throw new \LogicException("feedReadBuffer() in async client does not make sense."); - } - - /** - * Asynchronously sends buffered data over the wire. - * - * - Calls {@link eventLoops}'s addWriteStream() with client's stream. - * - Consecutive calls will return the same instance of promise. - * - * @return Promise\PromiseInterface - */ - protected function flushWriteBuffer() - { - if ($this->flushWriteBufferPromise) { - return $this->flushWriteBufferPromise; - - } else { - $deferred = new Promise\Deferred(); - - $this->eventLoop->addWriteStream($this->getStream(), function ($stream) use ($deferred) { - try { - $this->write(); - - if ($this->writeBuffer->isEmpty()) { - $this->eventLoop->removeWriteStream($stream); - $this->flushWriteBufferPromise = null; - $deferred->resolve(true); - } - - } catch (\Exception $e) { - $this->eventLoop->removeWriteStream($stream); - $this->flushWriteBufferPromise = null; - $deferred->reject($e); - } - }); - - return $this->flushWriteBufferPromise = $deferred->promise(); - } - } - - /** - * Connects to AMQP server. - * - * Calling connect() multiple times will result in error. - * - * @return Promise\PromiseInterface - */ - public function connect() - { - if ($this->state !== ClientStateEnum::NOT_CONNECTED) { - return Promise\reject(new ClientException("Client already connected/connecting.")); - } - - $this->state = ClientStateEnum::CONNECTING; - $this->writer->appendProtocolHeader($this->writeBuffer); - - try { - $this->eventLoop->addReadStream($this->getStream(), [$this, "onDataAvailable"]); - } catch (\Exception $e) { - return Promise\reject($e); - } - - return $this->flushWriteBuffer()->then(function () { - return $this->awaitConnectionStart(); - - })->then(function (MethodConnectionStartFrame $start) { - return $this->authResponse($start); - - })->then(function () { - return $this->awaitConnectionTune(); - - })->then(function (MethodConnectionTuneFrame $tune) { - $this->frameMax = $tune->frameMax; - if ($tune->channelMax > 0) { - $this->channelMax = $tune->channelMax; - } - return $this->connectionTuneOk($tune->channelMax, $tune->frameMax, $this->options["heartbeat"]); - - })->then(function () { - return $this->connectionOpen($this->options["vhost"]); - - })->then(function () { - $this->heartbeatTimer = $this->eventLoop->addTimer($this->options["heartbeat"], [$this, "onHeartbeat"]); - - $this->state = ClientStateEnum::CONNECTED; - return $this; - - }); - } - - /** - * Disconnects client from server. - * - * - Calling disconnect() if client is not connected will result in error. - * - Calling disconnect() multiple times will result in the same promise. - * - * @param int $replyCode - * @param string $replyText - * @return Promise\PromiseInterface - */ - public function disconnect($replyCode = 0, $replyText = "") - { - if ($this->state === ClientStateEnum::DISCONNECTING) { - return $this->disconnectPromise; - } - - if ($this->state !== ClientStateEnum::CONNECTED) { - return Promise\reject(new ClientException("Client is not connected.")); - } - - $this->state = ClientStateEnum::DISCONNECTING; - - $promises = []; - - if ($replyCode === 0) { - foreach ($this->channels as $channel) { - $promises[] = $channel->close($replyCode, $replyText); - } - } - else{ - foreach($this->channels as $channel){ - $this->removeChannel($channel->getChannelId()); - } - } - - if ($this->heartbeatTimer) { - $this->eventLoop->cancelTimer($this->heartbeatTimer); - $this->heartbeatTimer = null; - } - - return $this->disconnectPromise = Promise\all($promises)->then(function () use ($replyCode, $replyText) { - if (!empty($this->channels)) { - throw new \LogicException("All channels have to be closed by now."); - } - if($replyCode !== 0){ - return null; - } - return $this->connectionClose($replyCode, $replyText, 0, 0); - })->then(function () { - $this->eventLoop->removeReadStream($this->getStream()); - $this->closeStream(); - $this->init(); - return $this; - }); - } - - /** - * Adds callback to process incoming frames. - * - * Callback is passed instance of {@link \Bunny\Protocol|AbstractFrame}. If callback returns TRUE, frame is said to - * be handled and further handlers (other await callbacks, default handler) won't be called. - * - * @param callable $callback - */ - public function addAwaitCallback(callable $callback) - { - $this->awaitCallbacks[] = $callback; - } - - /** - * {@link eventLoop}'s read stream callback notifying client that data from server arrived. - */ - public function onDataAvailable() - { - $this->read(); - - while (($frame = $this->reader->consumeFrame($this->readBuffer)) !== null) { - foreach ($this->awaitCallbacks as $k => $callback) { - if ($callback($frame) === true) { - unset($this->awaitCallbacks[$k]); - continue 2; // CONTINUE WHILE LOOP - } - } - - if ($frame->channel === 0) { - $this->onFrameReceived($frame); - - } else { - if (!isset($this->channels[$frame->channel])) { - throw new ClientException( - "Received frame #{$frame->type} on closed channel #{$frame->channel}." - ); - } - - $this->channels[$frame->channel]->onFrameReceived($frame); - } - } - } - - /** - * Callback when heartbeat timer timed out. - */ - public function onHeartbeat() - { - $now = microtime(true); - $nextHeartbeat = ($this->lastWrite ?: $now) + $this->options["heartbeat"]; - - if ($now >= $nextHeartbeat) { - $this->writer->appendFrame(new HeartbeatFrame(), $this->writeBuffer); - $this->flushWriteBuffer()->done(function () { - $this->heartbeatTimer = $this->eventLoop->addTimer($this->options["heartbeat"], [$this, "onHeartbeat"]); - }); - - if (is_callable($this->options['heartbeat_callback'] ?? null)) { - $this->options['heartbeat_callback']->call($this); - } - } else { - $this->heartbeatTimer = $this->eventLoop->addTimer($nextHeartbeat - $now, [$this, "onHeartbeat"]); - } - } - -} diff --git a/src/Bunny/ChannelMethods.php b/src/Bunny/ChannelMethods.php deleted file mode 100644 index 38555c7..0000000 --- a/src/Bunny/ChannelMethods.php +++ /dev/null @@ -1,357 +0,0 @@ - - */ -trait ChannelMethods -{ - - /** - * Returns underlying client instance. - * - * @return AbstractClient - */ - abstract public function getClient(); - - /** - * Returns channel id. - * - * @return int - */ - abstract public function getChannelId(); - - /** - * Calls exchange.declare AMQP method. - * - * @param string $exchange - * @param string $exchangeType - * @param boolean $passive - * @param boolean $durable - * @param boolean $autoDelete - * @param boolean $internal - * @param boolean $nowait - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodExchangeDeclareOkFrame - */ - public function exchangeDeclare($exchange, $exchangeType = 'direct', $passive = false, $durable = false, $autoDelete = false, $internal = false, $nowait = false, $arguments = []) - { - return $this->getClient()->exchangeDeclare($this->getChannelId(), $exchange, $exchangeType, $passive, $durable, $autoDelete, $internal, $nowait, $arguments); - } - - /** - * Calls exchange.delete AMQP method. - * - * @param string $exchange - * @param boolean $ifUnused - * @param boolean $nowait - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodExchangeDeleteOkFrame - */ - public function exchangeDelete($exchange, $ifUnused = false, $nowait = false) - { - return $this->getClient()->exchangeDelete($this->getChannelId(), $exchange, $ifUnused, $nowait); - } - - /** - * Calls exchange.bind AMQP method. - * - * @param string $destination - * @param string $source - * @param string $routingKey - * @param boolean $nowait - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodExchangeBindOkFrame - */ - public function exchangeBind($destination, $source, $routingKey = '', $nowait = false, $arguments = []) - { - return $this->getClient()->exchangeBind($this->getChannelId(), $destination, $source, $routingKey, $nowait, $arguments); - } - - /** - * Calls exchange.unbind AMQP method. - * - * @param string $destination - * @param string $source - * @param string $routingKey - * @param boolean $nowait - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodExchangeUnbindOkFrame - */ - public function exchangeUnbind($destination, $source, $routingKey = '', $nowait = false, $arguments = []) - { - return $this->getClient()->exchangeUnbind($this->getChannelId(), $destination, $source, $routingKey, $nowait, $arguments); - } - - /** - * Calls queue.declare AMQP method. - * - * @param string $queue - * @param boolean $passive - * @param boolean $durable - * @param boolean $exclusive - * @param boolean $autoDelete - * @param boolean $nowait - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodQueueDeclareOkFrame - */ - public function queueDeclare($queue = '', $passive = false, $durable = false, $exclusive = false, $autoDelete = false, $nowait = false, $arguments = []) - { - return $this->getClient()->queueDeclare($this->getChannelId(), $queue, $passive, $durable, $exclusive, $autoDelete, $nowait, $arguments); - } - - /** - * Calls queue.bind AMQP method. - * - * @param string $queue - * @param string $exchange - * @param string $routingKey - * @param boolean $nowait - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodQueueBindOkFrame - */ - public function queueBind($queue, $exchange, $routingKey = '', $nowait = false, $arguments = []) - { - return $this->getClient()->queueBind($this->getChannelId(), $queue, $exchange, $routingKey, $nowait, $arguments); - } - - /** - * Calls queue.purge AMQP method. - * - * @param string $queue - * @param boolean $nowait - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodQueuePurgeOkFrame - */ - public function queuePurge($queue = '', $nowait = false) - { - return $this->getClient()->queuePurge($this->getChannelId(), $queue, $nowait); - } - - /** - * Calls queue.delete AMQP method. - * - * @param string $queue - * @param boolean $ifUnused - * @param boolean $ifEmpty - * @param boolean $nowait - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodQueueDeleteOkFrame - */ - public function queueDelete($queue = '', $ifUnused = false, $ifEmpty = false, $nowait = false) - { - return $this->getClient()->queueDelete($this->getChannelId(), $queue, $ifUnused, $ifEmpty, $nowait); - } - - /** - * Calls queue.unbind AMQP method. - * - * @param string $queue - * @param string $exchange - * @param string $routingKey - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodQueueUnbindOkFrame - */ - public function queueUnbind($queue, $exchange, $routingKey = '', $arguments = []) - { - return $this->getClient()->queueUnbind($this->getChannelId(), $queue, $exchange, $routingKey, $arguments); - } - - /** - * Calls basic.qos AMQP method. - * - * @param int $prefetchSize - * @param int $prefetchCount - * @param boolean $global - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodBasicQosOkFrame - */ - public function qos($prefetchSize = 0, $prefetchCount = 0, $global = false) - { - return $this->getClient()->qos($this->getChannelId(), $prefetchSize, $prefetchCount, $global); - } - - /** - * Calls basic.consume AMQP method. - * - * @param string $queue - * @param string $consumerTag - * @param boolean $noLocal - * @param boolean $noAck - * @param boolean $exclusive - * @param boolean $nowait - * @param array $arguments - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodBasicConsumeOkFrame - */ - public function consume($queue = '', $consumerTag = '', $noLocal = false, $noAck = false, $exclusive = false, $nowait = false, $arguments = []) - { - return $this->getClient()->consume($this->getChannelId(), $queue, $consumerTag, $noLocal, $noAck, $exclusive, $nowait, $arguments); - } - - /** - * Calls basic.cancel AMQP method. - * - * @param string $consumerTag - * @param boolean $nowait - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodBasicCancelOkFrame - */ - public function cancel($consumerTag, $nowait = false) - { - return $this->getClient()->cancel($this->getChannelId(), $consumerTag, $nowait); - } - - /** - * Calls basic.publish AMQP method. - * - * @param string $body - * @param array $headers - * @param string $exchange - * @param string $routingKey - * @param boolean $mandatory - * @param boolean $immediate - * - * @return boolean|Promise\PromiseInterface - */ - public function publish($body, array $headers = [], $exchange = '', $routingKey = '', $mandatory = false, $immediate = false) - { - return $this->getClient()->publish($this->getChannelId(), $body, $headers, $exchange, $routingKey, $mandatory, $immediate); - } - - /** - * Calls basic.get AMQP method. - * - * @param string $queue - * @param boolean $noAck - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodBasicGetOkFrame|Protocol\MethodBasicGetEmptyFrame - */ - public function get($queue = '', $noAck = false) - { - return $this->getClient()->get($this->getChannelId(), $queue, $noAck); - } - - /** - * Calls basic.ack AMQP method. - * - * @param int $deliveryTag - * @param boolean $multiple - * - * @return boolean|Promise\PromiseInterface - */ - public function ack($deliveryTag = 0, $multiple = false) - { - return $this->getClient()->ack($this->getChannelId(), $deliveryTag, $multiple); - } - - /** - * Calls basic.reject AMQP method. - * - * @param int $deliveryTag - * @param boolean $requeue - * - * @return boolean|Promise\PromiseInterface - */ - public function reject($deliveryTag, $requeue = true) - { - return $this->getClient()->reject($this->getChannelId(), $deliveryTag, $requeue); - } - - /** - * Calls basic.recover-async AMQP method. - * - * @param boolean $requeue - * - * @return boolean|Promise\PromiseInterface - */ - public function recoverAsync($requeue = false) - { - return $this->getClient()->recoverAsync($this->getChannelId(), $requeue); - } - - /** - * Calls basic.recover AMQP method. - * - * @param boolean $requeue - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodBasicRecoverOkFrame - */ - public function recover($requeue = false) - { - return $this->getClient()->recover($this->getChannelId(), $requeue); - } - - /** - * Calls basic.nack AMQP method. - * - * @param int $deliveryTag - * @param boolean $multiple - * @param boolean $requeue - * - * @return boolean|Promise\PromiseInterface - */ - public function nack($deliveryTag = 0, $multiple = false, $requeue = true) - { - return $this->getClient()->nack($this->getChannelId(), $deliveryTag, $multiple, $requeue); - } - - /** - * Calls tx.select AMQP method. - * - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodTxSelectOkFrame - */ - public function txSelect() - { - return $this->getClient()->txSelect($this->getChannelId()); - } - - /** - * Calls tx.commit AMQP method. - * - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodTxCommitOkFrame - */ - public function txCommit() - { - return $this->getClient()->txCommit($this->getChannelId()); - } - - /** - * Calls tx.rollback AMQP method. - * - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodTxRollbackOkFrame - */ - public function txRollback() - { - return $this->getClient()->txRollback($this->getChannelId()); - } - - /** - * Calls confirm.select AMQP method. - * - * @param boolean $nowait - * - * @return boolean|Promise\PromiseInterface|Protocol\MethodConfirmSelectOkFrame - */ - public function confirmSelect($nowait = false) - { - return $this->getClient()->confirmSelect($this->getChannelId(), $nowait); - } - -} diff --git a/src/Bunny/Client.php b/src/Bunny/Client.php deleted file mode 100644 index c059963..0000000 --- a/src/Bunny/Client.php +++ /dev/null @@ -1,278 +0,0 @@ - "exchangeDelete". - * Methods from "basic" class are not prefixed with "basic" - e.g. "basic.publish" is just "publish". - * - * Usage: - * - * $c = new Bunny\Client([ - * "host" => "127.0.0.1", - * "port" => 5672, - * "vhost" => "/", - * "user" => "guest", - * "password" => "guest", - * ]); - * - * $c->connect(); - * // work with connected client, e.g. $c->channel() - * - * @author Jakub Kulhan - */ -class Client extends AbstractClient -{ - - /** @var boolean */ - protected $running = true; - - /** - * Constructor. - * - * @param array $options - */ - public function __construct(array $options = []) - { - $options["async"] = false; - parent::__construct($options); - } - - /** - * Destructor. - * - * Clean shutdown = disconnect if connected. - */ - public function __destruct() - { - if ($this->isConnected()) { - $this->disconnect()->done(function () { - $this->stop(); - }); - - // has to re-check if connected, because disconnect() can set connection state immediately - if ($this->isConnected()) { - $this->run(); - } - } - } - - /** - * Reads data from stream to {@link readBuffer}. - * - * @return boolean - */ - protected function feedReadBuffer() - { - $this->read(); - return true; - } - - /** - * Writes all data from {@link writeBuffer} to stream. - * - * @return boolean - */ - protected function flushWriteBuffer() - { - while (!$this->writeBuffer->isEmpty()) { - $this->write(); - } - return true; - } - - /** - * Synchronously connects to AMQP server. - * - * @throws \Exception - * @return self - */ - public function connect() - { - if ($this->state !== ClientStateEnum::NOT_CONNECTED) { - throw new ClientException("Client already connected/connecting."); - } - - try { - $this->state = ClientStateEnum::CONNECTING; - - $this->writer->appendProtocolHeader($this->writeBuffer); - $this->flushWriteBuffer(); - $this->authResponse($this->awaitConnectionStart()); - $tune = $this->awaitConnectionTune(); - $this->connectionTuneOk($tune->channelMax, $tune->frameMax, $this->options["heartbeat"]); // FIXME: options heartbeat - $this->frameMax = $tune->frameMax; - if ($tune->channelMax > 0) { - $this->channelMax = $tune->channelMax; - } - $this->connectionOpen($this->options["vhost"]); - - $this->state = ClientStateEnum::CONNECTED; - - return $this; - - } catch (\Exception $e) { - $this->state = ClientStateEnum::ERROR; - throw $e; - } - } - - /** - * Disconnects from AMQP server. - * - * @param int $replyCode - * @param string $replyText - * @return Promise\PromiseInterface - */ - public function disconnect($replyCode = 0, $replyText = "") - { - if ($this->state === ClientStateEnum::DISCONNECTING) { - return $this->disconnectPromise; - } - - if ($this->state !== ClientStateEnum::CONNECTED) { - return Promise\reject(new ClientException("Client is not connected.")); - } - - $this->state = ClientStateEnum::DISCONNECTING; - - $promises = []; - - if ($replyCode === 0) { - foreach ($this->channels as $channel) { - $promises[] = $channel->close(); - } - } - - return $this->disconnectPromise = Promise\all($promises)->then(function () use ($replyCode, $replyText) { - if (!empty($this->channels)) { - throw new \LogicException("All channels have to be closed by now."); - } - - $this->connectionClose($replyCode, $replyText, 0, 0); - $this->closeStream(); - $this->init(); - return $this; - }); - } - - /** - * Runs it's own event loop, processes frames as they arrive. Processes messages for at most $maxSeconds. - * - * @param float $maxSeconds - */ - public function run($maxSeconds = null) - { - if (!$this->isConnected()) { - throw new ClientException("Client has to be connected."); - } - - $this->running = true; - $startTime = microtime(true); - $stopTime = null; - if ($maxSeconds !== null) { - $stopTime = $startTime + $maxSeconds; - } - - do { - if (!empty($this->queue)) { - $frame = array_shift($this->queue); - - } else { - if (($frame = $this->reader->consumeFrame($this->readBuffer)) === null) { - $now = microtime(true); - $nextStreamSelectTimeout = ($this->lastWrite ?: $now) + $this->options["heartbeat"]; - if (!isset($nextHeartbeat)) { - $nextHeartbeat = $now + $this->options["heartbeat"]; - } - if ($stopTime !== null && $stopTime < $nextStreamSelectTimeout) { - $nextStreamSelectTimeout = $stopTime; - } - $tvSec = max(intval($nextStreamSelectTimeout - $now), 0); - $tvUsec = max(intval(($nextStreamSelectTimeout - $now - $tvSec) * 1000000), 0); - - $r = [$this->getStream()]; - $w = null; - $e = null; - - if (($n = @stream_select($r, $w, $e, $tvSec, $tvUsec)) === false) { - $lastError = error_get_last(); - - // Note: The word "Unable" within the stream_select error message was spelled "unable" in PHP - // versions < 8. - if ($lastError !== null && - preg_match("/^stream_select\\(\\): [Uu]nable to select \\[(\\d+)\\]:/", $lastError["message"], $m) && - intval($m[1]) === PCNTL_EINTR - ) { - // got interrupted by signal, dispatch signals & continue - pcntl_signal_dispatch(); - $n = 0; - - } else { - throw new ClientException(sprintf( - "stream_select() failed: %s", - $lastError ? $lastError["message"] : "Unknown error." - )); - } - } - - $now = microtime(true); - - if ($now >= $nextHeartbeat) { - $nextHeartbeat = $now + $this->options["heartbeat"]; - $this->writer->appendFrame(new HeartbeatFrame(), $this->writeBuffer); - $this->flushWriteBuffer(); - - if (is_callable($this->options['heartbeat_callback'] ?? null)) { - $this->options['heartbeat_callback']->call($this); - } - } - - if ($stopTime !== null && $now >= $stopTime) { - break; - } - - if ($n > 0) { - $this->feedReadBuffer(); - } - - continue; - } - } - - /** @var AbstractFrame $frame */ - - if ($frame->channel === 0) { - $this->onFrameReceived($frame); - - } else { - if (!isset($this->channels[$frame->channel])) { - throw new ClientException( - "Received frame #{$frame->type} on closed channel #{$frame->channel}." - ); - } - - $this->channels[$frame->channel]->onFrameReceived($frame); - } - - - } while ($this->running); - } - - /** - * Stops client's event loop. - */ - public function stop() - { - $this->running = false; - } - -} diff --git a/src/Bunny/ClientMethods.php b/src/Bunny/ClientMethods.php deleted file mode 100644 index 48dc8bb..0000000 --- a/src/Bunny/ClientMethods.php +++ /dev/null @@ -1,2581 +0,0 @@ - - */ -trait ClientMethods -{ - - /** @var array */ - private $cache = []; - - /** - * Returns AMQP protocol reader. - * - * @return Protocol\ProtocolReader - */ - abstract protected function getReader(); - - /** - * Returns read buffer. - * - * @return Buffer - */ - abstract protected function getReadBuffer(); - - /** - * Returns AMQP protocol writer. - * - * @return Protocol\ProtocolWriter - */ - abstract protected function getWriter(); - - /** - * Returns write buffer. - * - * @return Buffer - */ - abstract protected function getWriteBuffer(); - - /** - * Reads data from stream to read buffer. - */ - abstract protected function feedReadBuffer(); - - /** - * Writes all data from write buffer to stream. - * - * @return boolean|PromiseInterface - */ - abstract protected function flushWriteBuffer(); - - /** - * Enqueues given frame for later processing. - * - * @param Protocol\AbstractFrame $frame - */ - abstract protected function enqueue(Protocol\AbstractFrame $frame); - - /** - * Returns frame max size. - * - * @return int - */ - abstract protected function getFrameMax(); - - /** - * @param int $channel - * - * @return Protocol\ContentHeaderFrame|PromiseInterface - */ - public function awaitContentHeader($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\ContentHeaderFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\ContentHeaderFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - /** - * @param int $channel - * - * @return Protocol\ContentBodyFrame|PromiseInterface - */ - public function awaitContentBody($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\ContentBodyFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\ContentBodyFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - /** - * @return Protocol\MethodConnectionStartFrame|PromiseInterface - */ - public function awaitConnectionStart() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionStartFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionStartFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function connectionStartOk($clientProperties, $mechanism, $response, $locale = 'en_US') - { - $buffer = new Buffer(); - $buffer->appendUint16(10); - $buffer->appendUint16(11); - $this->getWriter()->appendTable($clientProperties, $buffer); - $buffer->appendUint8(strlen($mechanism)); $buffer->append($mechanism); - $buffer->appendUint32(strlen($response)); $buffer->append($response); - $buffer->appendUint8(strlen($locale)); $buffer->append($locale); - $frame = new Protocol\MethodFrame(10, 11); - $frame->channel = 0; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - return $this->flushWriteBuffer(); - } - - /** - * @return Protocol\MethodConnectionSecureFrame|PromiseInterface - */ - public function awaitConnectionSecure() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionSecureFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionSecureFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function connectionSecureOk($response) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16(0); - $buffer->appendUint32(8 + strlen($response)); - $buffer->appendUint16(10); - $buffer->appendUint16(21); - $buffer->appendUint32(strlen($response)); $buffer->append($response); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @return Protocol\MethodConnectionTuneFrame|PromiseInterface - */ - public function awaitConnectionTune() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionTuneFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionTuneFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function connectionTuneOk($channelMax = 0, $frameMax = 0, $heartbeat = 0) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16(0); - $buffer->appendUint32(12); - $buffer->appendUint16(10); - $buffer->appendUint16(31); - $buffer->appendInt16($channelMax); - $buffer->appendInt32($frameMax); - $buffer->appendInt16($heartbeat); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - public function connectionOpen($virtualHost = '/', $capabilities = '', $insist = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16(0); - $buffer->appendUint32(7 + strlen($virtualHost) + strlen($capabilities)); - $buffer->appendUint16(10); - $buffer->appendUint16(40); - $buffer->appendUint8(strlen($virtualHost)); $buffer->append($virtualHost); - $buffer->appendUint8(strlen($capabilities)); $buffer->append($capabilities); - $this->getWriter()->appendBits([$insist], $buffer); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitConnectionOpenOk(); - } - - /** - * @return Protocol\MethodConnectionOpenOkFrame|PromiseInterface - */ - public function awaitConnectionOpenOk() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionOpenOkFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionOpenOkFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function connectionClose($replyCode, $replyText, $closeClassId, $closeMethodId) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16(0); - $buffer->appendUint32(11 + strlen($replyText)); - $buffer->appendUint16(10); - $buffer->appendUint16(50); - $buffer->appendInt16($replyCode); - $buffer->appendUint8(strlen($replyText)); $buffer->append($replyText); - $buffer->appendInt16($closeClassId); - $buffer->appendInt16($closeMethodId); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitConnectionCloseOk(); - } - - /** - * @return Protocol\MethodConnectionCloseFrame|PromiseInterface - */ - public function awaitConnectionClose() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionCloseFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function connectionCloseOk() - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16(0); - $buffer->appendUint32(4); - $buffer->appendUint16(10); - $buffer->appendUint16(51); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @return Protocol\MethodConnectionCloseOkFrame|PromiseInterface - */ - public function awaitConnectionCloseOk() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionCloseOkFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionCloseOkFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - /** - * @return Protocol\MethodConnectionBlockedFrame|PromiseInterface - */ - public function awaitConnectionBlocked() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionBlockedFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionBlockedFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - /** - * @return Protocol\MethodConnectionUnblockedFrame|PromiseInterface - */ - public function awaitConnectionUnblocked() - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred) { - if ($frame instanceof Protocol\MethodConnectionUnblockedFrame) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConnectionUnblockedFrame) { - return $frame; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function channelOpen($channel, $outOfBand = '') - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(5 + strlen($outOfBand)); - $buffer->appendUint16(20); - $buffer->appendUint16(10); - $buffer->appendUint8(strlen($outOfBand)); $buffer->append($outOfBand); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitChannelOpenOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodChannelOpenOkFrame|PromiseInterface - */ - public function awaitChannelOpenOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodChannelOpenOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodChannelOpenOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function channelFlow($channel, $active) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(5); - $buffer->appendUint16(20); - $buffer->appendUint16(20); - $this->getWriter()->appendBits([$active], $buffer); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitChannelFlowOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodChannelFlowFrame|PromiseInterface - */ - public function awaitChannelFlow($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodChannelFlowFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodChannelFlowFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function channelFlowOk($channel, $active) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(5); - $buffer->appendUint16(20); - $buffer->appendUint16(21); - $this->getWriter()->appendBits([$active], $buffer); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @param int $channel - * - * @return Protocol\MethodChannelFlowOkFrame|PromiseInterface - */ - public function awaitChannelFlowOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodChannelFlowOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodChannelFlowOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function channelClose($channel, $replyCode, $replyText, $closeClassId, $closeMethodId) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(11 + strlen($replyText)); - $buffer->appendUint16(20); - $buffer->appendUint16(40); - $buffer->appendInt16($replyCode); - $buffer->appendUint8(strlen($replyText)); $buffer->append($replyText); - $buffer->appendInt16($closeClassId); - $buffer->appendInt16($closeMethodId); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @param int $channel - * - * @return Protocol\MethodChannelCloseFrame|PromiseInterface - */ - public function awaitChannelClose($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function channelCloseOk($channel) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(4); - $buffer->appendUint16(20); - $buffer->appendUint16(41); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @param int $channel - * - * @return Protocol\MethodChannelCloseOkFrame|PromiseInterface - */ - public function awaitChannelCloseOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodChannelCloseOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodChannelCloseOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function accessRequest($channel, $realm = '/data', $exclusive = false, $passive = true, $active = true, $write = true, $read = true) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(6 + strlen($realm)); - $buffer->appendUint16(30); - $buffer->appendUint16(10); - $buffer->appendUint8(strlen($realm)); $buffer->append($realm); - $this->getWriter()->appendBits([$exclusive, $passive, $active, $write, $read], $buffer); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitAccessRequestOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodAccessRequestOkFrame|PromiseInterface - */ - public function awaitAccessRequestOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodAccessRequestOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodAccessRequestOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function exchangeDeclare($channel, $exchange, $exchangeType = 'direct', $passive = false, $durable = false, $autoDelete = false, $internal = false, $nowait = false, $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(40); - $buffer->appendUint16(10); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); - $buffer->appendUint8(strlen($exchangeType)); $buffer->append($exchangeType); - $this->getWriter()->appendBits([$passive, $durable, $autoDelete, $internal, $nowait], $buffer); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(40, 10); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitExchangeDeclareOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodExchangeDeclareOkFrame|PromiseInterface - */ - public function awaitExchangeDeclareOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodExchangeDeclareOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodExchangeDeclareOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function exchangeDelete($channel, $exchange, $ifUnused = false, $nowait = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(8 + strlen($exchange)); - $buffer->appendUint16(40); - $buffer->appendUint16(20); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); - $this->getWriter()->appendBits([$ifUnused, $nowait], $buffer); - $buffer->appendUint8(206); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitExchangeDeleteOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodExchangeDeleteOkFrame|PromiseInterface - */ - public function awaitExchangeDeleteOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodExchangeDeleteOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodExchangeDeleteOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function exchangeBind($channel, $destination, $source, $routingKey = '', $nowait = false, $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(40); - $buffer->appendUint16(30); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($destination)); $buffer->append($destination); - $buffer->appendUint8(strlen($source)); $buffer->append($source); - $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); - $this->getWriter()->appendBits([$nowait], $buffer); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(40, 30); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitExchangeBindOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodExchangeBindOkFrame|PromiseInterface - */ - public function awaitExchangeBindOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodExchangeBindOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodExchangeBindOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function exchangeUnbind($channel, $destination, $source, $routingKey = '', $nowait = false, $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(40); - $buffer->appendUint16(40); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($destination)); $buffer->append($destination); - $buffer->appendUint8(strlen($source)); $buffer->append($source); - $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); - $this->getWriter()->appendBits([$nowait], $buffer); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(40, 40); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitExchangeUnbindOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodExchangeUnbindOkFrame|PromiseInterface - */ - public function awaitExchangeUnbindOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodExchangeUnbindOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodExchangeUnbindOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function queueDeclare($channel, $queue = '', $passive = false, $durable = false, $exclusive = false, $autoDelete = false, $nowait = false, $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(50); - $buffer->appendUint16(10); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $this->getWriter()->appendBits([$passive, $durable, $exclusive, $autoDelete, $nowait], $buffer); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(50, 10); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitQueueDeclareOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodQueueDeclareOkFrame|PromiseInterface - */ - public function awaitQueueDeclareOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodQueueDeclareOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodQueueDeclareOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function queueBind($channel, $queue, $exchange, $routingKey = '', $nowait = false, $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(50); - $buffer->appendUint16(20); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); - $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); - $this->getWriter()->appendBits([$nowait], $buffer); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(50, 20); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitQueueBindOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodQueueBindOkFrame|PromiseInterface - */ - public function awaitQueueBindOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodQueueBindOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodQueueBindOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function queuePurge($channel, $queue = '', $nowait = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(8 + strlen($queue)); - $buffer->appendUint16(50); - $buffer->appendUint16(30); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $this->getWriter()->appendBits([$nowait], $buffer); - $buffer->appendUint8(206); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitQueuePurgeOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodQueuePurgeOkFrame|PromiseInterface - */ - public function awaitQueuePurgeOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodQueuePurgeOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodQueuePurgeOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function queueDelete($channel, $queue = '', $ifUnused = false, $ifEmpty = false, $nowait = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(8 + strlen($queue)); - $buffer->appendUint16(50); - $buffer->appendUint16(40); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $this->getWriter()->appendBits([$ifUnused, $ifEmpty, $nowait], $buffer); - $buffer->appendUint8(206); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitQueueDeleteOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodQueueDeleteOkFrame|PromiseInterface - */ - public function awaitQueueDeleteOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodQueueDeleteOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodQueueDeleteOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function queueUnbind($channel, $queue, $exchange, $routingKey = '', $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(50); - $buffer->appendUint16(50); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); - $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(50, 50); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - $this->flushWriteBuffer(); - return $this->awaitQueueUnbindOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodQueueUnbindOkFrame|PromiseInterface - */ - public function awaitQueueUnbindOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodQueueUnbindOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodQueueUnbindOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function qos($channel, $prefetchSize = 0, $prefetchCount = 0, $global = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(11); - $buffer->appendUint16(60); - $buffer->appendUint16(10); - $buffer->appendInt32($prefetchSize); - $buffer->appendInt16($prefetchCount); - $this->getWriter()->appendBits([$global], $buffer); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitQosOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicQosOkFrame|PromiseInterface - */ - public function awaitQosOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicQosOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicQosOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function consume($channel, $queue = '', $consumerTag = '', $noLocal = false, $noAck = false, $exclusive = false, $nowait = false, $arguments = []) - { - $buffer = new Buffer(); - $buffer->appendUint16(60); - $buffer->appendUint16(20); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $buffer->appendUint8(strlen($consumerTag)); $buffer->append($consumerTag); - $this->getWriter()->appendBits([$noLocal, $noAck, $exclusive, $nowait], $buffer); - $this->getWriter()->appendTable($arguments, $buffer); - $frame = new Protocol\MethodFrame(60, 20); - $frame->channel = $channel; - $frame->payloadSize = $buffer->getLength(); - $frame->payload = $buffer; - $this->getWriter()->appendFrame($frame, $this->getWriteBuffer()); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitConsumeOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicConsumeOkFrame|PromiseInterface - */ - public function awaitConsumeOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicConsumeOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicConsumeOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function cancel($channel, $consumerTag, $nowait = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(6 + strlen($consumerTag)); - $buffer->appendUint16(60); - $buffer->appendUint16(30); - $buffer->appendUint8(strlen($consumerTag)); $buffer->append($consumerTag); - $this->getWriter()->appendBits([$nowait], $buffer); - $buffer->appendUint8(206); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitCancelOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicCancelOkFrame|PromiseInterface - */ - public function awaitCancelOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicCancelOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicCancelOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function publish($channel, $body, array $headers = [], $exchange = '', $routingKey = '', $mandatory = false, $immediate = false) - { - $buffer = $this->getWriteBuffer(); - $ck = serialize([$channel, $headers, $exchange, $routingKey, $mandatory, $immediate]); - $c = isset($this->cache[$ck]) ? $this->cache[$ck] : null; - $flags = 0; $off0 = 0; $len0 = 0; $off1 = 0; $len1 = 0; $contentTypeLength = null; $contentType = null; $contentEncodingLength = null; $contentEncoding = null; $headersBuffer = null; $deliveryMode = null; $priority = null; $correlationIdLength = null; $correlationId = null; $replyToLength = null; $replyTo = null; $expirationLength = null; $expiration = null; $messageIdLength = null; $messageId = null; $timestamp = null; $typeLength = null; $type = null; $userIdLength = null; $userId = null; $appIdLength = null; $appId = null; $clusterIdLength = null; $clusterId = null; - if ($c) { $buffer->append($c[0]); } - else { - $off0 = $buffer->getLength(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(9 + strlen($exchange) + strlen($routingKey)); - $buffer->appendUint16(60); - $buffer->appendUint16(40); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); - $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); - $this->getWriter()->appendBits([$mandatory, $immediate], $buffer); - $buffer->appendUint8(206); - $s = 14; - if (isset($headers['content-type'])) { - $flags |= 32768; - $contentType = $headers['content-type']; - $s += 1; - $s += $contentTypeLength = strlen($contentType); - unset($headers['content-type']); - } - if (isset($headers['content-encoding'])) { - $flags |= 16384; - $contentEncoding = $headers['content-encoding']; - $s += 1; - $s += $contentEncodingLength = strlen($contentEncoding); - unset($headers['content-encoding']); - } - if (isset($headers['delivery-mode'])) { - $flags |= 4096; - $deliveryMode = $headers['delivery-mode']; - $s += 1; - unset($headers['delivery-mode']); - } - if (isset($headers['priority'])) { - $flags |= 2048; - $priority = $headers['priority']; - $s += 1; - unset($headers['priority']); - } - if (isset($headers['correlation-id'])) { - $flags |= 1024; - $correlationId = $headers['correlation-id']; - $s += 1; - $s += $correlationIdLength = strlen($correlationId); - unset($headers['correlation-id']); - } - if (isset($headers['reply-to'])) { - $flags |= 512; - $replyTo = $headers['reply-to']; - $s += 1; - $s += $replyToLength = strlen($replyTo); - unset($headers['reply-to']); - } - if (isset($headers['expiration'])) { - $flags |= 256; - $expiration = $headers['expiration']; - $s += 1; - $s += $expirationLength = strlen($expiration); - unset($headers['expiration']); - } - if (isset($headers['message-id'])) { - $flags |= 128; - $messageId = $headers['message-id']; - $s += 1; - $s += $messageIdLength = strlen($messageId); - unset($headers['message-id']); - } - if (isset($headers['timestamp'])) { - $flags |= 64; - $timestamp = $headers['timestamp']; - $s += 8; - unset($headers['timestamp']); - } - if (isset($headers['type'])) { - $flags |= 32; - $type = $headers['type']; - $s += 1; - $s += $typeLength = strlen($type); - unset($headers['type']); - } - if (isset($headers['user-id'])) { - $flags |= 16; - $userId = $headers['user-id']; - $s += 1; - $s += $userIdLength = strlen($userId); - unset($headers['user-id']); - } - if (isset($headers['app-id'])) { - $flags |= 8; - $appId = $headers['app-id']; - $s += 1; - $s += $appIdLength = strlen($appId); - unset($headers['app-id']); - } - if (isset($headers['cluster-id'])) { - $flags |= 4; - $clusterId = $headers['cluster-id']; - $s += 1; - $s += $clusterIdLength = strlen($clusterId); - unset($headers['cluster-id']); - } - if (!empty($headers)) { - $flags |= 8192; - $this->getWriter()->appendTable($headers, $headersBuffer = new Buffer()); - $s += $headersBuffer->getLength(); - } - $buffer->appendUint8(2); - $buffer->appendUint16($channel); - $buffer->appendUint32($s); - $buffer->appendUint16(60); - $buffer->appendUint16(0); - $len0 = $buffer->getLength() - $off0; - } - $buffer->appendUint64(strlen($body)); - if ($c) { $buffer->append($c[1]); } - else { - $off1 = $buffer->getLength(); - $buffer->appendUint16($flags); - if ($flags & 32768) { - $buffer->appendUint8($contentTypeLength); $buffer->append($contentType); - } - if ($flags & 16384) { - $buffer->appendUint8($contentEncodingLength); $buffer->append($contentEncoding); - } - if ($flags & 8192) { - $buffer->append($headersBuffer); - } - if ($flags & 4096) { - $buffer->appendUint8($deliveryMode); - } - if ($flags & 2048) { - $buffer->appendUint8($priority); - } - if ($flags & 1024) { - $buffer->appendUint8($correlationIdLength); $buffer->append($correlationId); - } - if ($flags & 512) { - $buffer->appendUint8($replyToLength); $buffer->append($replyTo); - } - if ($flags & 256) { - $buffer->appendUint8($expirationLength); $buffer->append($expiration); - } - if ($flags & 128) { - $buffer->appendUint8($messageIdLength); $buffer->append($messageId); - } - if ($flags & 64) { - $this->getWriter()->appendTimestamp($timestamp, $buffer); - } - if ($flags & 32) { - $buffer->appendUint8($typeLength); $buffer->append($type); - } - if ($flags & 16) { - $buffer->appendUint8($userIdLength); $buffer->append($userId); - } - if ($flags & 8) { - $buffer->appendUint8($appIdLength); $buffer->append($appId); - } - if ($flags & 4) { - $buffer->appendUint8($clusterIdLength); $buffer->append($clusterId); - } - $buffer->appendUint8(206); - $len1 = $buffer->getLength() - $off1; - } - if (!$c) { - $this->cache[$ck] = [$buffer->read($len0, $off0), $buffer->read($len1, $off1)]; - if (count($this->cache) > 100) { reset($this->cache); unset($this->cache[key($this->cache)]); } - } - for ($payloadMax = $this->getFrameMax() - 8 /* frame preface and frame end */, $i = 0, $l = strlen($body); $i < $l; $i += $payloadMax) { - $payloadSize = $l - $i; if ($payloadSize > $payloadMax) { $payloadSize = $payloadMax; } - $buffer->appendUint8(3); - $buffer->appendUint16($channel); - $buffer->appendUint32($payloadSize); - $buffer->append(substr($body, $i, $payloadSize)); - $buffer->appendUint8(206); - } - return $this->flushWriteBuffer(); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicReturnFrame|PromiseInterface - */ - public function awaitReturn($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicReturnFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicReturnFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicDeliverFrame|PromiseInterface - */ - public function awaitDeliver($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicDeliverFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicDeliverFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function get($channel, $queue = '', $noAck = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(8 + strlen($queue)); - $buffer->appendUint16(60); - $buffer->appendUint16(70); - $buffer->appendInt16(0); - $buffer->appendUint8(strlen($queue)); $buffer->append($queue); - $this->getWriter()->appendBits([$noAck], $buffer); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitGetOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicGetOkFrame|Protocol\MethodBasicGetEmptyFrame|PromiseInterface - */ - public function awaitGetOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicGetOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodBasicGetEmptyFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicGetOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodBasicGetEmptyFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function ack($channel, $deliveryTag = 0, $multiple = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(13); - $buffer->appendUint16(60); - $buffer->appendUint16(80); - $buffer->appendInt64($deliveryTag); - $this->getWriter()->appendBits([$multiple], $buffer); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicAckFrame|PromiseInterface - */ - public function awaitAck($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicAckFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicAckFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function reject($channel, $deliveryTag, $requeue = true) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(13); - $buffer->appendUint16(60); - $buffer->appendUint16(90); - $buffer->appendInt64($deliveryTag); - $this->getWriter()->appendBits([$requeue], $buffer); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - public function recoverAsync($channel, $requeue = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(5); - $buffer->appendUint16(60); - $buffer->appendUint16(100); - $this->getWriter()->appendBits([$requeue], $buffer); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - public function recover($channel, $requeue = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(5); - $buffer->appendUint16(60); - $buffer->appendUint16(110); - $this->getWriter()->appendBits([$requeue], $buffer); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitRecoverOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicRecoverOkFrame|PromiseInterface - */ - public function awaitRecoverOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicRecoverOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicRecoverOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function nack($channel, $deliveryTag = 0, $multiple = false, $requeue = true) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(13); - $buffer->appendUint16(60); - $buffer->appendUint16(120); - $buffer->appendInt64($deliveryTag); - $this->getWriter()->appendBits([$multiple, $requeue], $buffer); - $buffer->appendUint8(206); - return $this->flushWriteBuffer(); - } - - /** - * @param int $channel - * - * @return Protocol\MethodBasicNackFrame|PromiseInterface - */ - public function awaitNack($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodBasicNackFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodBasicNackFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function txSelect($channel) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(4); - $buffer->appendUint16(90); - $buffer->appendUint16(10); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitTxSelectOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodTxSelectOkFrame|PromiseInterface - */ - public function awaitTxSelectOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodTxSelectOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodTxSelectOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function txCommit($channel) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(4); - $buffer->appendUint16(90); - $buffer->appendUint16(20); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitTxCommitOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodTxCommitOkFrame|PromiseInterface - */ - public function awaitTxCommitOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodTxCommitOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodTxCommitOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function txRollback($channel) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(4); - $buffer->appendUint16(90); - $buffer->appendUint16(30); - $buffer->appendUint8(206); - $this->flushWriteBuffer(); - return $this->awaitTxRollbackOk($channel); - } - - /** - * @param int $channel - * - * @return Protocol\MethodTxRollbackOkFrame|PromiseInterface - */ - public function awaitTxRollbackOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodTxRollbackOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodTxRollbackOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - - public function confirmSelect($channel, $nowait = false) - { - $buffer = $this->getWriteBuffer(); - $buffer->appendUint8(1); - $buffer->appendUint16($channel); - $buffer->appendUint32(5); - $buffer->appendUint16(85); - $buffer->appendUint16(10); - $this->getWriter()->appendBits([$nowait], $buffer); - $buffer->appendUint8(206); - if ($nowait) { - return $this->flushWriteBuffer(); - } else { - $this->flushWriteBuffer(); - return $this->awaitConfirmSelectOk($channel); - } - } - - /** - * @param int $channel - * - * @return Protocol\MethodConfirmSelectOkFrame|PromiseInterface - */ - public function awaitConfirmSelectOk($channel) - { - if ($this instanceof Async\Client) { - $deferred = new Deferred(); - $this->addAwaitCallback(function ($frame) use ($deferred, $channel) { - if ($frame instanceof Protocol\MethodConfirmSelectOkFrame && $frame->channel === $channel) { - $deferred->resolve($frame); - return true; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel)->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk()->done(function () use ($frame, $deferred) { - $deferred->reject(new ClientException($frame->replyText, $frame->replyCode)); - }); - return true; - } - return false; - }); - return $deferred->promise(); - } else { - for (;;) { - while (($frame = $this->getReader()->consumeFrame($this->getReadBuffer())) === null) { - $this->feedReadBuffer(); - } - if ($frame instanceof Protocol\MethodConfirmSelectOkFrame && $frame->channel === $channel) { - return $frame; - } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { - $this->channelCloseOk($channel); - throw new ClientException($frame->replyText, $frame->replyCode); - } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { - $this->connectionCloseOk(); - throw new ClientException($frame->replyText, $frame->replyCode); - } else { - $this->enqueue($frame); - } - } - } - throw new \LogicException('This statement should be never reached.'); - } - -} diff --git a/src/Bunny/Message.php b/src/Bunny/Message.php deleted file mode 100644 index 6bdf985..0000000 --- a/src/Bunny/Message.php +++ /dev/null @@ -1,82 +0,0 @@ - - */ -class Message -{ - - /** @var string */ - public $consumerTag; - - /** @var int */ - public $deliveryTag; - - /** @var boolean */ - public $redelivered; - - /** @var string */ - public $exchange; - - /** @var string */ - public $routingKey; - - /** @var array */ - public $headers; - - /** @var string */ - public $content; - - /** - * Constructor. - * - * @param string $consumerTag - * @param string $deliveryTag - * @param boolean $redelivered - * @param string $exchange - * @param string $routingKey - * @param array $headers - * @param string $content - */ - public function __construct($consumerTag, $deliveryTag, $redelivered, $exchange, $routingKey, array $headers, $content) - { - $this->consumerTag = $consumerTag; - $this->deliveryTag = $deliveryTag; - $this->redelivered = $redelivered; - $this->exchange = $exchange; - $this->routingKey = $routingKey; - $this->headers = $headers; - $this->content = $content; - } - - /** - * Returns header or default value. - * - * @param string $name - * @param mixed $default - * @return mixed - */ - public function getHeader($name, $default = null) - { - if (isset($this->headers[$name])) { - return $this->headers[$name]; - } else { - return $default; - } - } - - /** - * Returns TRUE if message has given header. - * - * @param string $name - * @return boolean - */ - public function hasHeader($name) - { - return isset($this->headers[$name]); - } - -} diff --git a/src/Bunny/Channel.php b/src/Channel.php similarity index 66% rename from src/Bunny/Channel.php rename to src/Channel.php index 8dcaddf..754ba8d 100644 --- a/src/Bunny/Channel.php +++ b/src/Channel.php @@ -1,4 +1,7 @@ + * @final Will be marked final in a future major release */ -class Channel +class Channel implements ChannelInterface, EventEmitterInterface { + use EventEmitterTrait; use ChannelMethods { ChannelMethods::consume as private consumeImpl; @@ -45,86 +54,65 @@ class Channel ChannelMethods::confirmSelect as private confirmSelectImpl; } - /** @var AbstractClient */ - protected $client; - - /** @var int */ - protected $channelId; - /** @var callable[] */ - protected $returnCallbacks = []; + private $returnCallbacks = []; /** @var callable[] */ - protected $deliverCallbacks = []; + private $deliverCallbacks = []; /** @var callable[] */ - protected $ackCallbacks = []; + private $ackCallbacks = []; - /** @var MethodBasicReturnFrame */ - protected $returnFrame; + /** @var ?MethodBasicReturnFrame */ + private $returnFrame; - /** @var MethodBasicDeliverFrame */ - protected $deliverFrame; + /** @var ?MethodBasicDeliverFrame */ + private $deliverFrame; - /** @var MethodBasicGetOkFrame */ - protected $getOkFrame; + /** @var ?MethodBasicGetOkFrame */ + private $getOkFrame; - /** @var ContentHeaderFrame */ - protected $headerFrame; + /** @var ?ContentHeaderFrame */ + private $headerFrame; /** @var int */ - protected $bodySizeRemaining; + private $bodySizeRemaining; /** @var Buffer */ - protected $bodyBuffer; + private $bodyBuffer; /** @var int */ - protected $state = ChannelStateEnum::READY; + private $state = ChannelStateEnum::READY; /** @var int */ - protected $mode = ChannelModeEnum::REGULAR; + private $mode = ChannelModeEnum::REGULAR; /** @var Deferred */ - protected $closeDeferred; + private $closeDeferred; /** @var PromiseInterface */ - protected $closePromise; + private $closePromise; - /** @var Deferred */ - protected $getDeferred; + /** @var ?Deferred */ + private $getDeferred; /** @var int */ - protected $deliveryTag; + private $deliveryTag; - /** - * Constructor. - * - * @param AbstractClient $client - * @param int $channelId - */ - public function __construct(AbstractClient $client, $channelId) + public function __construct(private Connection $connection, private Client $client, readonly public int $channelId) { - $this->client = $client; - $this->channelId = $channelId; $this->bodyBuffer = new Buffer(); } - /** - * Returns underlying client instance. - * - * @return AbstractClient - */ - public function getClient() + public function getClient(): Connection { - return $this->client; + return $this->connection; } /** * Returns channel id. - * - * @return int */ - public function getChannelId() + public function getChannelId(): int { return $this->channelId; } @@ -134,7 +122,7 @@ public function getChannelId() * * @return int */ - public function getMode() + public function getMode(): int { return $this->mode; } @@ -211,44 +199,33 @@ public function removeAckListener(callable $callback) * Closes channel. * * Always returns a promise, because there can be outstanding messages to be processed. - * - * @param int $replyCode - * @param string $replyText - * @return PromiseInterface */ - public function close($replyCode = 0, $replyText = "") + public function close(int $replyCode = 0, string $replyText = ""): void { if ($this->state === ChannelStateEnum::CLOSED) { throw new ChannelException("Trying to close already closed channel #{$this->channelId}."); } if ($this->state === ChannelStateEnum::CLOSING) { - return $this->closePromise; + await($this->closePromise); + return; } $this->state = ChannelStateEnum::CLOSING; - $this->client->channelClose($this->channelId, $replyCode, $replyText, 0, 0); + $this->connection->channelClose($this->channelId, $replyCode, 0, 0, $replyText); $this->closeDeferred = new Deferred(); - return $this->closePromise = $this->closeDeferred->promise()->then(function () { - $this->client->removeChannel($this->channelId); + $this->closePromise = $this->closeDeferred->promise()->then(function () { + $this->emit('close'); }); + + await($this->closePromise); } /** * Creates new consumer on channel. - * - * @param callable $callback - * @param string $queue - * @param string $consumerTag - * @param bool $noLocal - * @param bool $noAck - * @param bool $exclusive - * @param bool $nowait - * @param array $arguments - * @return MethodBasicConsumeOkFrame|PromiseInterface */ - public function consume(callable $callback, $queue = "", $consumerTag = "", $noLocal = false, $noAck = false, $exclusive = false, $nowait = false, $arguments = []) + public function consume(callable $callback, string $queue = "", string $consumerTag = "", bool $noLocal = false, bool $noAck = false, bool $exclusive = false, bool $nowait = false, array $arguments = []): MethodBasicConsumeOkFrame { $response = $this->consumeImpl($queue, $consumerTag, $noLocal, $noAck, $exclusive, $nowait, $arguments); @@ -256,99 +233,41 @@ public function consume(callable $callback, $queue = "", $consumerTag = "", $noL $this->deliverCallbacks[$response->consumerTag] = $callback; return $response; - } elseif ($response instanceof PromiseInterface) { - return $response->then(function (MethodBasicConsumeOkFrame $response) use ($callback) { - $this->deliverCallbacks[$response->consumerTag] = $callback; - return $response; - }); - - } else { - throw new ChannelException( - "basic.consume unexpected response of type " . gettype($response) . - (is_object($response) ? " (" . get_class($response) . ")" : "") . - "." - ); } - } - - /** - * Convenience method that registers consumer and then starts client event loop. - * - * @param callable $callback - * @param string $queue - * @param string $consumerTag - * @param bool $noLocal - * @param bool $noAck - * @param bool $exclusive - * @param bool $nowait - * @param array $arguments - */ - public function run(callable $callback, $queue = "", $consumerTag = "", $noLocal = false, $noAck = false, $exclusive = false, $nowait = false, $arguments = []) - { - $response = $this->consume($callback, $queue, $consumerTag, $noLocal, $noAck, $exclusive, $nowait, $arguments); - if ($response instanceof MethodBasicConsumeOkFrame) { - $this->client->run(); - - } elseif ($response instanceof PromiseInterface) { - $response->done(function () { - $this->client->run(); - }); - - } else { - throw new ChannelException( - "Unexpected response of type " . gettype($response) . - (is_object($response) ? " (" . get_class($response) . ")" : "") . - "." - ); - } + throw new ChannelException( + "basic.consume unexpected response of type " . gettype($response) . "." + ); } /** * Acks given message. - * - * @param Message $message - * @param boolean $multiple - * @return boolean|PromiseInterface */ - public function ack(Message $message, $multiple = false) + public function ack(Message $message, bool $multiple = false): bool { return $this->ackImpl($message->deliveryTag, $multiple); } /** * Nacks given message. - * - * @param Message $message - * @param boolean $multiple - * @param boolean $requeue - * @return boolean|PromiseInterface */ - public function nack(Message $message, $multiple = false, $requeue = true) + public function nack(Message $message, bool $multiple = false, bool $requeue = true): bool { return $this->nackImpl($message->deliveryTag, $multiple, $requeue); } /** * Rejects given message. - * - * @param Message $message - * @param bool $requeue - * @return boolean|PromiseInterface */ - public function reject(Message $message, $requeue = true) + public function reject(Message $message, bool $requeue = true): bool { return $this->rejectImpl($message->deliveryTag, $requeue); } /** * Synchronously returns message if there is any waiting in the queue. - * - * @param string $queue - * @param bool $noAck - * @return Message|PromiseInterface|null */ - public function get($queue = "", $noAck = false) + public function get(string $queue = "", bool $noAck = false): Message|null { if ($this->getDeferred !== null) { throw new ChannelException("Another 'basic.get' already in progress. You should use 'basic.consume' instead of multiple 'basic.get'."); @@ -356,47 +275,26 @@ public function get($queue = "", $noAck = false) $response = $this->getImpl($queue, $noAck); - if ($response instanceof PromiseInterface) { - $this->getDeferred = new Deferred(); - - $response->done(function ($frame) { - if ($frame instanceof MethodBasicGetEmptyFrame) { - // deferred has to be first nullified and then resolved, otherwise results in race condition - $deferred = $this->getDeferred; - $this->getDeferred = null; - $deferred->resolve(null); - - } elseif ($frame instanceof MethodBasicGetOkFrame) { - $this->getOkFrame = $frame; - $this->state = ChannelStateEnum::AWAITING_HEADER; - - } else { - throw new \LogicException("This statement should never be reached."); - } - }); - - return $this->getDeferred->promise(); - - } elseif ($response instanceof MethodBasicGetEmptyFrame) { + if ($response instanceof MethodBasicGetEmptyFrame) { return null; } elseif ($response instanceof MethodBasicGetOkFrame) { $this->state = ChannelStateEnum::AWAITING_HEADER; - $headerFrame = $this->getClient()->awaitContentHeader($this->getChannelId()); + $headerFrame = $this->connection->awaitContentHeader($this->getChannelId()); $this->headerFrame = $headerFrame; $this->bodySizeRemaining = $headerFrame->bodySize; $this->state = ChannelStateEnum::AWAITING_BODY; while ($this->bodySizeRemaining > 0) { - $bodyFrame = $this->getClient()->awaitContentBody($this->getChannelId()); + $bodyFrame = $this->connection->awaitContentBody($this->getChannelId()); $this->bodyBuffer->append($bodyFrame->payload); $this->bodySizeRemaining -= $bodyFrame->payloadSize; if ($this->bodySizeRemaining < 0) { $this->state = ChannelStateEnum::ERROR; - $this->client->disconnect(Constants::STATUS_SYNTAX_ERROR, $errorMessage = "Body overflow, received " . (-$this->bodySizeRemaining) . " more bytes."); + $this->connection->disconnect(Constants::STATUS_SYNTAX_ERROR, $errorMessage = "Body overflow, received " . (-$this->bodySizeRemaining) . " more bytes."); throw new ChannelException($errorMessage); } } @@ -424,27 +322,13 @@ public function get($queue = "", $noAck = false) /** * Published message to given exchange. - * - * @param string $body - * @param array $headers - * @param string $exchange - * @param string $routingKey - * @param bool $mandatory - * @param bool $immediate - * @return bool|PromiseInterface|int */ - public function publish($body, array $headers = [], $exchange = '', $routingKey = '', $mandatory = false, $immediate = false) + public function publish($body, array $headers = [], string $exchange = '', string $routingKey = '', bool $mandatory = false, bool $immediate = false): bool|int { $response = $this->publishImpl($body, $headers, $exchange, $routingKey, $mandatory, $immediate); if ($this->mode === ChannelModeEnum::CONFIRM) { - if ($response instanceof PromiseInterface) { - return $response->then(function () { - return ++$this->deliveryTag; - }); - } else { - return ++$this->deliveryTag; - } + return ++$this->deliveryTag; } else { return $response; } @@ -452,12 +336,8 @@ public function publish($body, array $headers = [], $exchange = '', $routingKey /** * Cancels given consumer subscription. - * - * @param string $consumerTag - * @param bool $nowait - * @return bool|Protocol\MethodBasicCancelOkFrame|PromiseInterface */ - public function cancel($consumerTag, $nowait = false) + public function cancel(string $consumerTag, bool $nowait = false): bool|\Bunny\Protocol\MethodBasicCancelOkFrame { $response = $this->cancelImpl($consumerTag, $nowait); unset($this->deliverCallbacks[$consumerTag]); @@ -466,35 +346,23 @@ public function cancel($consumerTag, $nowait = false) /** * Changes channel to transactional mode. All messages are published to queues only after {@link txCommit()} is called. - * - * @return Protocol\MethodTxSelectOkFrame|PromiseInterface */ - public function txSelect() + public function txSelect(): \Bunny\Protocol\MethodTxSelectOkFrame { if ($this->mode !== ChannelModeEnum::REGULAR) { throw new ChannelException("Channel not in regular mode, cannot change to transactional mode."); } $response = $this->txSelectImpl(); + $this->mode = ChannelModeEnum::TRANSACTIONAL; - if ($response instanceof PromiseInterface) { - return $response->then(function ($response) { - $this->mode = ChannelModeEnum::TRANSACTIONAL; - return $response; - }); - - } else { - $this->mode = ChannelModeEnum::TRANSACTIONAL; - return $response; - } + return $response; } /** * Commit transaction. - * - * @return Protocol\MethodTxCommitOkFrame|PromiseInterface */ - public function txCommit() + public function txCommit(): \Bunny\Protocol\MethodTxCommitOkFrame { if ($this->mode !== ChannelModeEnum::TRANSACTIONAL) { throw new ChannelException("Channel not in transactional mode, cannot call 'tx.commit'."); @@ -505,10 +373,8 @@ public function txCommit() /** * Rollback transaction. - * - * @return Protocol\MethodTxRollbackOkFrame|PromiseInterface */ - public function txRollback() + public function txRollback(): \Bunny\Protocol\MethodTxRollbackOkFrame { if ($this->mode !== ChannelModeEnum::TRANSACTIONAL) { throw new ChannelException("Channel not in transactional mode, cannot call 'tx.rollback'."); @@ -519,31 +385,20 @@ public function txRollback() /** * Changes channel to confirm mode. Broker then asynchronously sends 'basic.ack's for published messages. - * - * @param bool $nowait - * @return Protocol\MethodConfirmSelectOkFrame|PromiseInterface */ - public function confirmSelect(callable $callback = null, $nowait = false) + public function confirmSelect(callable $callback = null, bool $nowait = false): \Bunny\Protocol\MethodConfirmSelectOkFrame { if ($this->mode !== ChannelModeEnum::REGULAR) { throw new ChannelException("Channel not in regular mode, cannot change to transactional mode."); } $response = $this->confirmSelectImpl($nowait); + $this->enterConfirmMode($callback); - if ($response instanceof PromiseInterface) { - return $response->then(function ($response) use ($callback) { - $this->enterConfirmMode($callback); - return $response; - }); - - } else { - $this->enterConfirmMode($callback); - return $response; - } + return $response; } - private function enterConfirmMode(callable $callback = null) + private function enterConfirmMode(callable $callback = null): void { $this->mode = ChannelModeEnum::CONFIRM; $this->deliveryTag = 0; @@ -558,7 +413,7 @@ private function enterConfirmMode(callable $callback = null) * * @param AbstractFrame $frame */ - public function onFrameReceived(AbstractFrame $frame) + public function onFrameReceived(AbstractFrame $frame): void { if ($this->state === ChannelStateEnum::ERROR) { throw new ChannelException("Channel in error state."); @@ -585,7 +440,7 @@ public function onFrameReceived(AbstractFrame $frame) throw new \LogicException("Unhandled channel state."); } - $this->client->disconnect(Constants::STATUS_UNEXPECTED_FRAME, $msg); + $this->connection->disconnect(Constants::STATUS_UNEXPECTED_FRAME, $msg); throw new ChannelException("Unexpected frame: " . $msg); } @@ -597,8 +452,8 @@ public function onFrameReceived(AbstractFrame $frame) $this->closeDeferred->resolve($this->channelId); } - // break reference cycle, must be called after resolving promise - $this->client = null; +// // break reference cycle, must be called after resolving promise +// $this->client = null; // break consumers' reference cycle $this->deliverCallbacks = []; @@ -643,7 +498,7 @@ public function onFrameReceived(AbstractFrame $frame) throw new \LogicException("Unhandled channel state."); } - $this->client->disconnect(Constants::STATUS_UNEXPECTED_FRAME, $msg); + $this->connection->disconnect(Constants::STATUS_UNEXPECTED_FRAME, $msg); throw new ChannelException("Unexpected frame: " . $msg); } @@ -675,7 +530,7 @@ public function onFrameReceived(AbstractFrame $frame) throw new \LogicException("Unhandled channel state."); } - $this->client->disconnect(Constants::STATUS_UNEXPECTED_FRAME, $msg); + $this->connection->disconnect(Constants::STATUS_UNEXPECTED_FRAME, $msg); throw new ChannelException("Unexpected frame: " . $msg); } @@ -685,7 +540,7 @@ public function onFrameReceived(AbstractFrame $frame) if ($this->bodySizeRemaining < 0) { $this->state = ChannelStateEnum::ERROR; - $this->client->disconnect(Constants::STATUS_SYNTAX_ERROR, "Body overflow, received " . (-$this->bodySizeRemaining) . " more bytes."); + $this->connection->disconnect(Constants::STATUS_SYNTAX_ERROR, "Body overflow, received " . (-$this->bodySizeRemaining) . " more bytes."); } elseif ($this->bodySizeRemaining === 0) { $this->state = ChannelStateEnum::READY; @@ -693,7 +548,7 @@ public function onFrameReceived(AbstractFrame $frame) } } elseif ($frame instanceof HeartbeatFrame) { - $this->client->disconnect(Constants::STATUS_UNEXPECTED_FRAME, "Got heartbeat on non-zero channel."); + $this->connection->disconnect(Constants::STATUS_UNEXPECTED_FRAME, "Got heartbeat on non-zero channel."); throw new ChannelException("Unexpected heartbeat frame."); } else { @@ -704,7 +559,7 @@ public function onFrameReceived(AbstractFrame $frame) /** * Callback after content body has been completely received. */ - protected function onBodyComplete() + private function onBodyComplete(): void { if ($this->returnFrame) { $content = $this->bodyBuffer->consume($this->bodyBuffer->getLength()); @@ -719,7 +574,7 @@ protected function onBodyComplete() ); foreach ($this->returnCallbacks as $callback) { - $callback($message, $this->returnFrame); + async(fn () => $callback($message, $this->returnFrame))(); } $this->returnFrame = null; diff --git a/src/ChannelInterface.php b/src/ChannelInterface.php new file mode 100644 index 0000000..3e65b22 --- /dev/null +++ b/src/ChannelInterface.php @@ -0,0 +1,120 @@ + + * @final Will be marked final in a future major release + */ +interface ChannelInterface +{ + /** + * Returns the channel mode. + * + * @return int + */ + public function getMode(): int; + /** + * Listener is called whenever 'basic.return' frame is received with arguments (Message $returnedMessage, MethodBasicReturnFrame $frame) + * + * @param callable $callback + * @return $this + */ + public function addReturnListener(callable $callback); + + /** + * Removes registered return listener. If the callback is not registered, this is noop. + * + * @param callable $callback + * @return $this + */ + public function removeReturnListener(callable $callback); + + /** + * Listener is called whenever 'basic.ack' or 'basic.nack' is received. + * + * @param callable $callback + * @return $this + */ + public function addAckListener(callable $callback); + + /** + * Removes registered ack/nack listener. If the callback is not registered, this is noop. + * + * @param callable $callback + * @return $this + */ + public function removeAckListener(callable $callback); + + /** + * Closes channel. + * + * Always returns a promise, because there can be outstanding messages to be processed. + */ + public function close(int $replyCode = 0, string $replyText = ""): void; + + /** + * Creates new consumer on channel. + */ + public function consume(callable $callback, string $queue = "", string $consumerTag = "", bool $noLocal = false, bool $noAck = false, bool $exclusive = false, bool $nowait = false, array $arguments = []): MethodBasicConsumeOkFrame; + + /** + * Acks given message. + */ + public function ack(Message $message, bool $multiple = false): bool; + + /** + * Nacks given message. + */ + public function nack(Message $message, bool $multiple = false, bool $requeue = true): bool; + + /** + * Rejects given message. + */ + public function reject(Message $message, bool $requeue = true): bool; + + /** + * Synchronously returns message if there is any waiting in the queue. + */ + public function get(string $queue = "", bool $noAck = false): Message|null; + + /** + * Published message to given exchange. + */ + public function publish($body, array $headers = [], string $exchange = '', string $routingKey = '', bool $mandatory = false, bool $immediate = false): bool|int; + + /** + * Cancels given consumer subscription. + */ + public function cancel(string $consumerTag, bool $nowait = false): bool|\Bunny\Protocol\MethodBasicCancelOkFrame; + + /** + * Changes channel to transactional mode. All messages are published to queues only after {@link txCommit()} is called. + */ + public function txSelect(): \Bunny\Protocol\MethodTxSelectOkFrame; + + /** + * Commit transaction. + */ + public function txCommit(): \Bunny\Protocol\MethodTxCommitOkFrame; + + /** + * Rollback transaction. + */ + public function txRollback(): \Bunny\Protocol\MethodTxRollbackOkFrame; + + /** + * Changes channel to confirm mode. Broker then asynchronously sends 'basic.ack's for published messages. + */ + public function confirmSelect(callable $callback = null, bool $nowait = false): \Bunny\Protocol\MethodConfirmSelectOkFrame; +} + diff --git a/src/ChannelMethods.php b/src/ChannelMethods.php new file mode 100644 index 0000000..8c92f36 --- /dev/null +++ b/src/ChannelMethods.php @@ -0,0 +1,214 @@ + + */ +trait ChannelMethods +{ + + /** + * Returns underlying client instance. + */ + abstract public function getClient(): Connection; + + /** + * Returns channel id. + */ + abstract public function getChannelId(): int; + + /** + * Calls exchange.declare AMQP method. + */ + public function exchangeDeclare(string $exchange, string $exchangeType = 'direct', bool $passive = false, bool $durable = false, bool $autoDelete = false, bool $internal = false, bool $nowait = false, array $arguments = []): bool|Protocol\MethodExchangeDeclareOkFrame + { + return $this->getClient()->exchangeDeclare($this->getChannelId(), $exchange, $exchangeType, $passive, $durable, $autoDelete, $internal, $nowait, $arguments); + } + + /** + * Calls exchange.delete AMQP method. + */ + public function exchangeDelete(string $exchange, bool $ifUnused = false, bool $nowait = false): bool|Protocol\MethodExchangeDeleteOkFrame + { + return $this->getClient()->exchangeDelete($this->getChannelId(), $exchange, $ifUnused, $nowait); + } + + /** + * Calls exchange.bind AMQP method. + */ + public function exchangeBind(string $destination, string $source, string $routingKey = '', bool $nowait = false, array $arguments = []): bool|Protocol\MethodExchangeBindOkFrame + { + return $this->getClient()->exchangeBind($this->getChannelId(), $destination, $source, $routingKey, $nowait, $arguments); + } + + /** + * Calls exchange.unbind AMQP method. + */ + public function exchangeUnbind(string $destination, string $source, string $routingKey = '', bool $nowait = false, array $arguments = []): bool|Protocol\MethodExchangeUnbindOkFrame + { + return $this->getClient()->exchangeUnbind($this->getChannelId(), $destination, $source, $routingKey, $nowait, $arguments); + } + + /** + * Calls queue.declare AMQP method. + */ + public function queueDeclare(string $queue = '', bool $passive = false, bool $durable = false, bool $exclusive = false, bool $autoDelete = false, bool $nowait = false, array $arguments = []): bool|Protocol\MethodQueueDeclareOkFrame + { + return $this->getClient()->queueDeclare($this->getChannelId(), $queue, $passive, $durable, $exclusive, $autoDelete, $nowait, $arguments); + } + + /** + * Calls queue.bind AMQP method. + */ + public function queueBind(string $exchange, string $queue = '', string $routingKey = '', bool $nowait = false, array $arguments = []): bool|Protocol\MethodQueueBindOkFrame + { + return $this->getClient()->queueBind($this->getChannelId(), $exchange, $queue, $routingKey, $nowait, $arguments); + } + + /** + * Calls queue.purge AMQP method. + */ + public function queuePurge(string $queue = '', bool $nowait = false): bool|Protocol\MethodQueuePurgeOkFrame + { + return $this->getClient()->queuePurge($this->getChannelId(), $queue, $nowait); + } + + /** + * Calls queue.delete AMQP method. + */ + public function queueDelete(string $queue = '', bool $ifUnused = false, bool $ifEmpty = false, bool $nowait = false): bool|Protocol\MethodQueueDeleteOkFrame + { + return $this->getClient()->queueDelete($this->getChannelId(), $queue, $ifUnused, $ifEmpty, $nowait); + } + + /** + * Calls queue.unbind AMQP method. + */ + public function queueUnbind(string $exchange, string $queue = '', string $routingKey = '', array $arguments = []): bool|Protocol\MethodQueueUnbindOkFrame + { + return $this->getClient()->queueUnbind($this->getChannelId(), $exchange, $queue, $routingKey, $arguments); + } + + /** + * Calls basic.qos AMQP method. + */ + public function qos(int $prefetchSize = 0, int $prefetchCount = 0, bool $global = false): bool|Protocol\MethodBasicQosOkFrame + { + return $this->getClient()->qos($this->getChannelId(), $prefetchSize, $prefetchCount, $global); + } + + /** + * Calls basic.consume AMQP method. + */ + public function consume(string $queue = '', string $consumerTag = '', bool $noLocal = false, bool $noAck = false, bool $exclusive = false, bool $nowait = false, array $arguments = []): bool|Protocol\MethodBasicConsumeOkFrame + { + return $this->getClient()->consume($this->getChannelId(), $queue, $consumerTag, $noLocal, $noAck, $exclusive, $nowait, $arguments); + } + + /** + * Calls basic.cancel AMQP method. + */ + public function cancel(string $consumerTag, bool $nowait = false): bool|Protocol\MethodBasicCancelOkFrame + { + return $this->getClient()->cancel($this->getChannelId(), $consumerTag, $nowait); + } + + /** + * Calls basic.publish AMQP method. + */ + public function publish(string $body, array $headers = [], string $exchange = '', string $routingKey = '', bool $mandatory = false, bool $immediate = false): bool + { + return $this->getClient()->publish($this->getChannelId(), $body, $headers, $exchange, $routingKey, $mandatory, $immediate); + } + + /** + * Calls basic.get AMQP method. + */ + public function get(string $queue = '', bool $noAck = false): bool|Protocol\MethodBasicGetOkFrame|Protocol\MethodBasicGetEmptyFrame + { + return $this->getClient()->get($this->getChannelId(), $queue, $noAck); + } + + /** + * Calls basic.ack AMQP method. + */ + public function ack(int $deliveryTag = 0, bool $multiple = false): bool + { + return $this->getClient()->ack($this->getChannelId(), $deliveryTag, $multiple); + } + + /** + * Calls basic.reject AMQP method. + */ + public function reject(int $deliveryTag, bool $requeue = true): bool + { + return $this->getClient()->reject($this->getChannelId(), $deliveryTag, $requeue); + } + + /** + * Calls basic.recover-async AMQP method. + */ + public function recoverAsync(bool $requeue = false): bool + { + return $this->getClient()->recoverAsync($this->getChannelId(), $requeue); + } + + /** + * Calls basic.recover AMQP method. + */ + public function recover(bool $requeue = false): bool|Protocol\MethodBasicRecoverOkFrame + { + return $this->getClient()->recover($this->getChannelId(), $requeue); + } + + /** + * Calls basic.nack AMQP method. + */ + public function nack(int $deliveryTag = 0, bool $multiple = false, bool $requeue = true): bool + { + return $this->getClient()->nack($this->getChannelId(), $deliveryTag, $multiple, $requeue); + } + + /** + * Calls tx.select AMQP method. + */ + public function txSelect(): bool|Protocol\MethodTxSelectOkFrame + { + return $this->getClient()->txSelect($this->getChannelId()); + } + + /** + * Calls tx.commit AMQP method. + */ + public function txCommit(): bool|Protocol\MethodTxCommitOkFrame + { + return $this->getClient()->txCommit($this->getChannelId()); + } + + /** + * Calls tx.rollback AMQP method. + */ + public function txRollback(): bool|Protocol\MethodTxRollbackOkFrame + { + return $this->getClient()->txRollback($this->getChannelId()); + } + + /** + * Calls confirm.select AMQP method. + */ + public function confirmSelect(bool $nowait = false): bool|Protocol\MethodConfirmSelectOkFrame + { + return $this->getClient()->confirmSelect($this->getChannelId(), $nowait); + } + +} diff --git a/src/Bunny/ChannelModeEnum.php b/src/ChannelModeEnum.php similarity index 94% rename from src/Bunny/ChannelModeEnum.php rename to src/ChannelModeEnum.php index 013633d..e8f0a0e 100644 --- a/src/Bunny/ChannelModeEnum.php +++ b/src/ChannelModeEnum.php @@ -1,4 +1,7 @@ + */ + private array $channels = []; + + public function set(int $channelid, Channel $channel): void + { + $this->channels[$channelid] = $channel; + } + + public function has(int $channelid): bool + { + return array_key_exists($channelid, $this->channels); + } + + public function get(int $channelid): Channel + { + return $this->channels[$channelid]; + } + + public function unset(int $channelid): void + { + unset($this->channels[$channelid]); + } + + + /** + * @return iterable + */ + public function all(): iterable + { + yield from $this->channels; + } +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..192d250 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,308 @@ + 'exchangeDelete'. + * Methods from 'basic' class are not prefixed with 'basic' - e.g. 'basic.publish' is just 'publish'. + * + * Usage: + * + * $c = new Bunny\Client([ + * 'host' => '127.0.0.1', + * 'port' => 5672, + * 'vhost' => '/', + * 'user' => 'guest', + * 'password' => 'guest', + * ]); + * + * // client is lazy and will connect once you open a channel, e.g. $c->channel() + * + * @author Jakub Kulhan + * @final Will be marked final in a future major release + */ +class Client implements ClientInterface +{ + private readonly array $options; + + private readonly Connector $connector; + + /** @var int */ + private int $state = ClientStateEnum::NOT_CONNECTED; + + private ?Connection $connection = null; + + private Channels $channels; + + /** @var int */ + public int $frameMax = 0xFFFF; + + /** @var int */ + private int $nextChannelId = 1; + + /** @var int */ + private int $channelMax = 0xFFFF; + + /** + * Constructor. + * + * @param array $options + */ + public function __construct(array $options = []) + { + if (!isset($options['host'])) { + $options['host'] = '127.0.0.1'; + } + + if (!isset($options['port'])) { + $options['port'] = 5672; + } + + if (!isset($options['vhost'])) { + if (isset($options['virtual_host'])) { + $options['vhost'] = $options['virtual_host']; + unset($options['virtual_host']); + } elseif (isset($options['path'])) { + $options['vhost'] = $options['path']; + unset($options['path']); + } else { + $options['vhost'] = '/'; + } + } + + if (!isset($options['user'])) { + if (isset($options['username'])) { + $options['user'] = $options['username']; + unset($options['username']); + } else { + $options['user'] = 'guest'; + } + } + + if (!isset($options['password'])) { + if (isset($options['pass'])) { + $options['password'] = $options['pass']; + unset($options['pass']); + } else { + $options['password'] = 'guest'; + } + } + + if (!isset($options['timeout'])) { + $options['timeout'] = 1; + } + + if (!isset($options['heartbeat'])) { + $options['heartbeat'] = 60.0; + } elseif ($options['heartbeat'] >= 2**15) { + throw new \InvalidArgumentException('Heartbeat too high: the value is a signed int16.'); + } + + if (!(is_callable($options['heartbeat_callback'] ?? null))) { + unset($options['heartbeat_callback']); + } + + + if (isset($options['ssl']) && is_array($options['ssl'])) { + $options['tls'] = $options['ssl']; + } + + + $this->options = $options; + $this->connector = new Connector($this->options); + + + $this->state = ClientStateEnum::NOT_CONNECTED; + $this->channels = new Channels(); + } + + /** + * Creates and opens new channel. + * + * Channel gets first available channel id. + */ + public function channel(): ChannelInterface + { + + if (!$this->isConnected()) { + $this->connect(); + } + + $channelId = $this->findChannelId(); + + $channel = new Channel($this->connection, $this, $channelId); + $channel->once('close', function () use ($channelId) { + $this->channels->unset($channelId); + }); + $this->channels->set($channelId, $channel); + $response = $this->connection->channelOpen($channelId); + + if ($response instanceof MethodChannelOpenOkFrame) { + return $channel; + } + + $this->state = ClientStateEnum::ERROR; + + throw new ClientException( + 'channel.open unexpected response of type ' . gettype($response) . '.' + ); + } + + /** + * Connects to AMQP server. + * + * Calling connect() multiple times will result in error. + */ + public function connect(): self + { + if ($this->state !== ClientStateEnum::NOT_CONNECTED) { + throw new ClientException('Client already connected/connecting.'); + } + + $this->state = ClientStateEnum::CONNECTING; + + $streamScheme = 'tcp'; + if (isset($this->options['tls']) && is_array($this->options['tls'])) { + $streamScheme = 'tls'; + } + $uri = $streamScheme . "://{$this->options['host']}:{$this->options['port']}"; + + try { + $this->connection = new Connection( + $this, + await($this->connector->connect($uri)), + new Buffer(), + new Buffer(), + new ProtocolReader(), + new ProtocolWriter(), + $this->channels, + $this->options, + ); + $this->connection->appendProtocolHeader(); + $this->connection->flushWriteBuffer(); + $start = $this->connection->awaitConnectionStart(); + $this->authResponse($start); + $tune = $this->connection->awaitConnectionTune(); + $this->frameMax = $tune->frameMax; + if ($tune->channelMax > 0) { + $this->channelMax = $tune->channelMax; + } + $this->connection->connectionTuneOk($tune->channelMax, $tune->frameMax, (int)$this->options['heartbeat']); + $this->connection->connectionOpen($this->options['vhost']); + $this->connection->startHeathbeatTimer(); + + $this->state = ClientStateEnum::CONNECTED; + } catch (\Throwable $thrown) { + throw new ClientException('Could not connect to ' . $uri . ': ' . $thrown->getMessage(), $thrown->getCode(), $thrown); + } + + return $this; + } + + /** + * Responds to authentication challenge + * + * @param MethodConnectionStartFrame $start + */ + protected function authResponse(MethodConnectionStartFrame $start): void + { + if (strpos($start->mechanisms, 'AMQPLAIN') === false) { + throw new ClientException('Server does not support AMQPLAIN mechanism (supported: {$start->mechanisms}).'); + } + + $responseBuffer = new Buffer(); + (new ProtocolWriter())->appendTable([ + 'LOGIN' => $this->options['user'], + 'PASSWORD' => $this->options['password'], + ], $responseBuffer); + $responseBuffer->discard(4); + + $this->connection->connectionStartOk($responseBuffer->read($responseBuffer->getLength()), [], 'AMQPLAIN', 'en_US'); + } + + /** + * Disconnects the client. + */ + public function disconnect(int $replyCode = 0, string $replyText = ''): void + { + if ($this->state === ClientStateEnum::DISCONNECTING) { + return; + } + + if ($this->state !== ClientStateEnum::CONNECTED) { + throw new ClientException('Client is not connected.'); + } + + $this->state = ClientStateEnum::DISCONNECTING; + + $promises = []; + foreach ($this->channels->all() as $channelId => $channel) { + $promises[] = async(static function () use ($channel, $replyCode, $replyText): void { + $channel->close($replyCode, $replyText); + })(); + } + await(all($promises)); + + $this->connection->disconnect($replyCode, $replyText); + + $this->state = ClientStateEnum::NOT_CONNECTED; + } + + /** + * Returns true if client is connected to server. + */ + public function isConnected(): bool + { + return $this->state !== ClientStateEnum::NOT_CONNECTED && $this->state !== ClientStateEnum::ERROR; + } + + /** + * @return int + */ + private function findChannelId(): int + { + // first check in range [next, max] ... + for ( + $channelId = $this->nextChannelId; + $channelId <= $this->channelMax; + ++$channelId + ) { + if (!$this->channels->has($channelId)) { + $this->nextChannelId = $channelId + 1; + + return $channelId; + } + } + + // then check in range [min, next) ... + for ( + $channelId = 1; + $channelId < $this->nextChannelId; + ++$channelId + ) { + if (!$this->channels->has($channelId)) { + $this->nextChannelId = $channelId + 1; + + return $channelId; + } + } + + throw new ClientException('No available channels'); + } +} diff --git a/src/ClientInterface.php b/src/ClientInterface.php new file mode 100644 index 0000000..799f95f --- /dev/null +++ b/src/ClientInterface.php @@ -0,0 +1,14 @@ + + */ +final class Connection +{ + protected ?TimerInterface $heartbeatTimer = null; + + /** @var float microtime of last write */ + protected float $lastWrite = 0.0; + + private array $cache = []; + + /** @var array */ + private array $awaitList = []; + + public function __construct( + private readonly Client $client, + private readonly ConnectionInterface $connection, + private readonly Buffer $readBuffer, + private readonly Buffer $writeBuffer, + private readonly ProtocolReader $reader, + private readonly ProtocolWriter $writer, + private readonly Channels $channels, + private readonly array $options = [], + ) { + $this->connection->on('data', function (string $data): void { + $this->readBuffer->append($data); + + while (($frame = $this->reader->consumeFrame($this->readBuffer)) !== null) { + $frameInAwaitList = false; + foreach ($this->awaitList as $index => $frameHandler) { + if ($frameHandler['filter']($frame)) { + unset($this->awaitList[$index]); + $frameHandler['promise']->resolve($frame); + $frameInAwaitList = true; + } + } + + if ($frameInAwaitList) { + continue; + } + + if ($frame->channel === 0) { + $this->onFrameReceived($frame); + continue; + } + + if (!$this->channels->has($frame->channel)) { + throw new ClientException( + "Received frame #{$frame->type} on closed channel #{$frame->channel}." + ); + } + + $this->channels->get($frame->channel)->onFrameReceived($frame); + } + }); + } + + public function disconnect(int $code, string $reason) + { + $this->connectionClose($code, 0, 0, $reason); + $this->connection->close(); + + if ($this->heartbeatTimer === null) { + return; + } + + Loop::cancelTimer($this->heartbeatTimer); + } + + /** + * Callback after connection-level frame has been received. + * + * @param AbstractFrame $frame + */ + private function onFrameReceived(AbstractFrame $frame) + { + if ($frame instanceof MethodFrame) { + if ($frame instanceof MethodConnectionCloseFrame) { + $this->disconnect(Constants::STATUS_CONNECTION_FORCED, "Connection closed by server: ({$frame->replyCode}) " . $frame->replyText); + throw new ClientException('Connection closed by server: ' . $frame->replyText, $frame->replyCode); + } + } elseif ($frame instanceof ContentHeaderFrame) { + $this->disconnect(Constants::STATUS_UNEXPECTED_FRAME, 'Got header frame on connection channel (#0).'); + } elseif ($frame instanceof ContentBodyFrame) { + $this->disconnect(Constants::STATUS_UNEXPECTED_FRAME, 'Got body frame on connection channel (#0).'); + } elseif ($frame instanceof HeartbeatFrame) { + return; + } + + throw new ClientException('Unhandled frame ' . get_class($frame) . '.'); + } + + public function appendProtocolHeader(): void + { + $this->writer->appendProtocolHeader($this->writeBuffer); + } + + public function flushWriteBuffer(): void + { + $data = $this->writeBuffer->read($this->writeBuffer->getLength()); + $this->writeBuffer->discard(strlen($data)); + + $this->lastWrite = microtime(true); + if (!$this->connection->write($data)) { + await(new Promise(function (callable $resolve): void { + $this->connection->once('drain', static fn () => $resolve(null)); + })); + } + } + + public function awaitContentHeader(int $channel): ContentHeaderFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\ContentHeaderFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + + return false; + }, + 'promise' => $deferred, + ]; + + return await($deferred->promise()); + } + + public function awaitContentBody(int $channel): ContentBodyFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\ContentBodyFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + + return false; + }, + 'promise' => $deferred, + ]; + + return await($deferred->promise()); + } + public function awaitConnectionStart(): Protocol\MethodConnectionStartFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionStartFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function connectionStartOk(string $response, array $clientProperties = [], string $mechanism = 'PLAIN', string $locale = 'en_US'): bool + { + $buffer = new Buffer(); + $buffer->appendUint16(10); + $buffer->appendUint16(11); + $this->writer->appendTable($clientProperties, $buffer); + $buffer->appendUint8(strlen($mechanism)); $buffer->append($mechanism); + $buffer->appendUint32(strlen($response)); $buffer->append($response); + $buffer->appendUint8(strlen($locale)); $buffer->append($locale); + $frame = new Protocol\MethodFrame(10, 11); + $frame->channel = 0; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + return false; + } + + public function awaitConnectionSecure(): Protocol\MethodConnectionSecureFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionSecureFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function connectionSecureOk(string $response): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16(0); + $buffer->appendUint32(8 + strlen($response)); + $buffer->appendUint16(10); + $buffer->appendUint16(21); + $buffer->appendUint32(strlen($response)); $buffer->append($response); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitConnectionTune(): Protocol\MethodConnectionTuneFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionTuneFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function connectionTuneOk(int $channelMax = 0, int $frameMax = 0, int $heartbeat = 0): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16(0); + $buffer->appendUint32(12); + $buffer->appendUint16(10); + $buffer->appendUint16(31); + $buffer->appendInt16($channelMax); + $buffer->appendInt32($frameMax); + $buffer->appendInt16($heartbeat); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function connectionOpen(string $virtualHost = '/', string $capabilities = '', bool $insist = false): bool|Protocol\MethodConnectionOpenOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16(0); + $buffer->appendUint32(7 + strlen($virtualHost) + strlen($capabilities)); + $buffer->appendUint16(10); + $buffer->appendUint16(40); + $buffer->appendUint8(strlen($virtualHost)); $buffer->append($virtualHost); + $buffer->appendUint8(strlen($capabilities)); $buffer->append($capabilities); + $this->writer->appendBits([$insist], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitConnectionOpenOk(); + } + + public function awaitConnectionOpenOk(): Protocol\MethodConnectionOpenOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionOpenOkFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function connectionClose(int $replyCode, int $closeClassId, int $closeMethodId, string $replyText = ''): bool|Protocol\MethodConnectionCloseOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16(0); + $buffer->appendUint32(11 + strlen($replyText)); + $buffer->appendUint16(10); + $buffer->appendUint16(50); + $buffer->appendInt16($replyCode); + $buffer->appendUint8(strlen($replyText)); $buffer->append($replyText); + $buffer->appendInt16($closeClassId); + $buffer->appendInt16($closeMethodId); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitConnectionCloseOk(); + } + + public function awaitConnectionClose(): Protocol\MethodConnectionCloseFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionCloseFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function connectionCloseOk(): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16(0); + $buffer->appendUint32(4); + $buffer->appendUint16(10); + $buffer->appendUint16(51); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitConnectionCloseOk(): Protocol\MethodConnectionCloseOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionCloseOkFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function awaitConnectionBlocked(): Protocol\MethodConnectionBlockedFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionBlockedFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function awaitConnectionUnblocked(): Protocol\MethodConnectionUnblockedFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame): bool { + if ($frame instanceof Protocol\MethodConnectionUnblockedFrame) { + return true; + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function channelOpen(int $channel, string $outOfBand = ''): bool|Protocol\MethodChannelOpenOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(5 + strlen($outOfBand)); + $buffer->appendUint16(20); + $buffer->appendUint16(10); + $buffer->appendUint8(strlen($outOfBand)); $buffer->append($outOfBand); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitChannelOpenOk($channel); + } + + public function awaitChannelOpenOk(int $channel): Protocol\MethodChannelOpenOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodChannelOpenOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function channelFlow(int $channel, bool $active): bool|Protocol\MethodChannelFlowOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(5); + $buffer->appendUint16(20); + $buffer->appendUint16(20); + $this->writer->appendBits([$active], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitChannelFlowOk($channel); + } + + public function awaitChannelFlow(int $channel): Protocol\MethodChannelFlowFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodChannelFlowFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function channelFlowOk(int $channel, bool $active): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(5); + $buffer->appendUint16(20); + $buffer->appendUint16(21); + $this->writer->appendBits([$active], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitChannelFlowOk(int $channel): Protocol\MethodChannelFlowOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodChannelFlowOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function channelClose(int $channel, int $replyCode, int $closeClassId, int $closeMethodId, string $replyText = ''): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(11 + strlen($replyText)); + $buffer->appendUint16(20); + $buffer->appendUint16(40); + $buffer->appendInt16($replyCode); + $buffer->appendUint8(strlen($replyText)); $buffer->append($replyText); + $buffer->appendInt16($closeClassId); + $buffer->appendInt16($closeMethodId); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitChannelClose(int $channel): Protocol\MethodChannelCloseFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function channelCloseOk(int $channel): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(4); + $buffer->appendUint16(20); + $buffer->appendUint16(41); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitChannelCloseOk(int $channel): Protocol\MethodChannelCloseOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodChannelCloseOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function accessRequest(int $channel, string $realm = '/data', bool $exclusive = false, bool $passive = true, bool $active = true, bool $write = true, bool $read = true): bool|Protocol\MethodAccessRequestOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(6 + strlen($realm)); + $buffer->appendUint16(30); + $buffer->appendUint16(10); + $buffer->appendUint8(strlen($realm)); $buffer->append($realm); + $this->writer->appendBits([$exclusive, $passive, $active, $write, $read], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitAccessRequestOk($channel); + } + + public function awaitAccessRequestOk(int $channel): Protocol\MethodAccessRequestOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodAccessRequestOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function exchangeDeclare(int $channel, string $exchange, string $exchangeType = 'direct', bool $passive = false, bool $durable = false, bool $autoDelete = false, bool $internal = false, bool $nowait = false, array $arguments = []): bool|Protocol\MethodExchangeDeclareOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(40); + $buffer->appendUint16(10); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); + $buffer->appendUint8(strlen($exchangeType)); $buffer->append($exchangeType); + $this->writer->appendBits([$passive, $durable, $autoDelete, $internal, $nowait], $buffer); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(40, 10); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitExchangeDeclareOk($channel); + } + return false; + } + + public function awaitExchangeDeclareOk(int $channel): Protocol\MethodExchangeDeclareOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodExchangeDeclareOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function exchangeDelete(int $channel, string $exchange, bool $ifUnused = false, bool $nowait = false): bool|Protocol\MethodExchangeDeleteOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(8 + strlen($exchange)); + $buffer->appendUint16(40); + $buffer->appendUint16(20); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); + $this->writer->appendBits([$ifUnused, $nowait], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitExchangeDeleteOk($channel); + } + return false; + } + + public function awaitExchangeDeleteOk(int $channel): Protocol\MethodExchangeDeleteOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodExchangeDeleteOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function exchangeBind(int $channel, string $destination, string $source, string $routingKey = '', bool $nowait = false, array $arguments = []): bool|Protocol\MethodExchangeBindOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(40); + $buffer->appendUint16(30); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($destination)); $buffer->append($destination); + $buffer->appendUint8(strlen($source)); $buffer->append($source); + $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); + $this->writer->appendBits([$nowait], $buffer); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(40, 30); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitExchangeBindOk($channel); + } + return false; + } + + public function awaitExchangeBindOk(int $channel): Protocol\MethodExchangeBindOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodExchangeBindOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function exchangeUnbind(int $channel, string $destination, string $source, string $routingKey = '', bool $nowait = false, array $arguments = []): bool|Protocol\MethodExchangeUnbindOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(40); + $buffer->appendUint16(40); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($destination)); $buffer->append($destination); + $buffer->appendUint8(strlen($source)); $buffer->append($source); + $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); + $this->writer->appendBits([$nowait], $buffer); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(40, 40); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitExchangeUnbindOk($channel); + } + return false; + } + + public function awaitExchangeUnbindOk(int $channel): Protocol\MethodExchangeUnbindOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodExchangeUnbindOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function queueDeclare(int $channel, string $queue = '', bool $passive = false, bool $durable = false, bool $exclusive = false, bool $autoDelete = false, bool $nowait = false, array $arguments = []): bool|Protocol\MethodQueueDeclareOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(50); + $buffer->appendUint16(10); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $this->writer->appendBits([$passive, $durable, $exclusive, $autoDelete, $nowait], $buffer); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(50, 10); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitQueueDeclareOk($channel); + } + return false; + } + + public function awaitQueueDeclareOk(int $channel): Protocol\MethodQueueDeclareOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodQueueDeclareOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function queueBind(int $channel, string $exchange, string $queue = '', string $routingKey = '', bool $nowait = false, array $arguments = []): bool|Protocol\MethodQueueBindOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(50); + $buffer->appendUint16(20); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); + $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); + $this->writer->appendBits([$nowait], $buffer); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(50, 20); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitQueueBindOk($channel); + } + return false; + } + + public function awaitQueueBindOk(int $channel): Protocol\MethodQueueBindOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodQueueBindOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function queuePurge(int $channel, string $queue = '', bool $nowait = false): bool|Protocol\MethodQueuePurgeOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(8 + strlen($queue)); + $buffer->appendUint16(50); + $buffer->appendUint16(30); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $this->writer->appendBits([$nowait], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitQueuePurgeOk($channel); + } + return false; + } + + public function awaitQueuePurgeOk(int $channel): Protocol\MethodQueuePurgeOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodQueuePurgeOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function queueDelete(int $channel, string $queue = '', bool $ifUnused = false, bool $ifEmpty = false, bool $nowait = false): bool|Protocol\MethodQueueDeleteOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(8 + strlen($queue)); + $buffer->appendUint16(50); + $buffer->appendUint16(40); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $this->writer->appendBits([$ifUnused, $ifEmpty, $nowait], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitQueueDeleteOk($channel); + } + return false; + } + + public function awaitQueueDeleteOk(int $channel): Protocol\MethodQueueDeleteOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodQueueDeleteOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function queueUnbind(int $channel, string $exchange, string $queue = '', string $routingKey = '', array $arguments = []): bool|Protocol\MethodQueueUnbindOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(50); + $buffer->appendUint16(50); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); + $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(50, 50); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + return $this->awaitQueueUnbindOk($channel); + } + + public function awaitQueueUnbindOk(int $channel): Protocol\MethodQueueUnbindOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodQueueUnbindOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function qos(int $channel, int $prefetchSize = 0, int $prefetchCount = 0, bool $global = false): bool|Protocol\MethodBasicQosOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(11); + $buffer->appendUint16(60); + $buffer->appendUint16(10); + $buffer->appendInt32($prefetchSize); + $buffer->appendInt16($prefetchCount); + $this->writer->appendBits([$global], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitQosOk($channel); + } + + public function awaitQosOk(int $channel): Protocol\MethodBasicQosOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicQosOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function consume(int $channel, string $queue = '', string $consumerTag = '', bool $noLocal = false, bool $noAck = false, bool $exclusive = false, bool $nowait = false, array $arguments = []): bool|Protocol\MethodBasicConsumeOkFrame + { + $buffer = new Buffer(); + $buffer->appendUint16(60); + $buffer->appendUint16(20); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $buffer->appendUint8(strlen($consumerTag)); $buffer->append($consumerTag); + $this->writer->appendBits([$noLocal, $noAck, $exclusive, $nowait], $buffer); + $this->writer->appendTable($arguments, $buffer); + $frame = new Protocol\MethodFrame(60, 20); + $frame->channel = $channel; + $frame->payloadSize = $buffer->getLength(); + $frame->payload = $buffer; + $this->writer->appendFrame($frame, $this->writeBuffer); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitConsumeOk($channel); + } + return false; + } + + public function awaitConsumeOk(int $channel): Protocol\MethodBasicConsumeOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicConsumeOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function cancel(int $channel, string $consumerTag, bool $nowait = false): bool|Protocol\MethodBasicCancelOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(6 + strlen($consumerTag)); + $buffer->appendUint16(60); + $buffer->appendUint16(30); + $buffer->appendUint8(strlen($consumerTag)); $buffer->append($consumerTag); + $this->writer->appendBits([$nowait], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitCancelOk($channel); + } + return false; + } + + public function awaitCancelOk(int $channel): Protocol\MethodBasicCancelOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicCancelOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function publish(int $channel, string $body, array $headers = [], string $exchange = '', string $routingKey = '', bool $mandatory = false, bool $immediate = false): bool + { + $buffer = $this->writeBuffer; + $ck = serialize([$channel, $headers, $exchange, $routingKey, $mandatory, $immediate]); + $c = isset($this->cache[$ck]) ? $this->cache[$ck] : null; + $flags = 0; $off0 = 0; $len0 = 0; $off1 = 0; $len1 = 0; $contentTypeLength = null; $contentType = null; $contentEncodingLength = null; $contentEncoding = null; $headersBuffer = null; $deliveryMode = null; $priority = null; $correlationIdLength = null; $correlationId = null; $replyToLength = null; $replyTo = null; $expirationLength = null; $expiration = null; $messageIdLength = null; $messageId = null; $timestamp = null; $typeLength = null; $type = null; $userIdLength = null; $userId = null; $appIdLength = null; $appId = null; $clusterIdLength = null; $clusterId = null; + if ($c) { $buffer->append($c[0]); } + else { + $off0 = $buffer->getLength(); + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(9 + strlen($exchange) + strlen($routingKey)); + $buffer->appendUint16(60); + $buffer->appendUint16(40); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($exchange)); $buffer->append($exchange); + $buffer->appendUint8(strlen($routingKey)); $buffer->append($routingKey); + $this->writer->appendBits([$mandatory, $immediate], $buffer); + $buffer->appendUint8(206); + $s = 14; + if (isset($headers['content-type'])) { + $flags |= 32768; + $contentType = $headers['content-type']; + $s += 1; + $s += $contentTypeLength = strlen($contentType); + unset($headers['content-type']); + } + if (isset($headers['content-encoding'])) { + $flags |= 16384; + $contentEncoding = $headers['content-encoding']; + $s += 1; + $s += $contentEncodingLength = strlen($contentEncoding); + unset($headers['content-encoding']); + } + if (isset($headers['delivery-mode'])) { + $flags |= 4096; + $deliveryMode = $headers['delivery-mode']; + $s += 1; + unset($headers['delivery-mode']); + } + if (isset($headers['priority'])) { + $flags |= 2048; + $priority = $headers['priority']; + $s += 1; + unset($headers['priority']); + } + if (isset($headers['correlation-id'])) { + $flags |= 1024; + $correlationId = $headers['correlation-id']; + $s += 1; + $s += $correlationIdLength = strlen($correlationId); + unset($headers['correlation-id']); + } + if (isset($headers['reply-to'])) { + $flags |= 512; + $replyTo = $headers['reply-to']; + $s += 1; + $s += $replyToLength = strlen($replyTo); + unset($headers['reply-to']); + } + if (isset($headers['expiration'])) { + $flags |= 256; + $expiration = $headers['expiration']; + $s += 1; + $s += $expirationLength = strlen($expiration); + unset($headers['expiration']); + } + if (isset($headers['message-id'])) { + $flags |= 128; + $messageId = $headers['message-id']; + $s += 1; + $s += $messageIdLength = strlen($messageId); + unset($headers['message-id']); + } + if (isset($headers['timestamp'])) { + $flags |= 64; + $timestamp = $headers['timestamp']; + $s += 8; + unset($headers['timestamp']); + } + if (isset($headers['type'])) { + $flags |= 32; + $type = $headers['type']; + $s += 1; + $s += $typeLength = strlen($type); + unset($headers['type']); + } + if (isset($headers['user-id'])) { + $flags |= 16; + $userId = $headers['user-id']; + $s += 1; + $s += $userIdLength = strlen($userId); + unset($headers['user-id']); + } + if (isset($headers['app-id'])) { + $flags |= 8; + $appId = $headers['app-id']; + $s += 1; + $s += $appIdLength = strlen($appId); + unset($headers['app-id']); + } + if (isset($headers['cluster-id'])) { + $flags |= 4; + $clusterId = $headers['cluster-id']; + $s += 1; + $s += $clusterIdLength = strlen($clusterId); + unset($headers['cluster-id']); + } + if (!empty($headers)) { + $flags |= 8192; + $this->writer->appendTable($headers, $headersBuffer = new Buffer()); + $s += $headersBuffer->getLength(); + } + $buffer->appendUint8(2); + $buffer->appendUint16($channel); + $buffer->appendUint32($s); + $buffer->appendUint16(60); + $buffer->appendUint16(0); + $len0 = $buffer->getLength() - $off0; + } + $buffer->appendUint64(strlen($body)); + if ($c) { $buffer->append($c[1]); } + else { + $off1 = $buffer->getLength(); + $buffer->appendUint16($flags); + if ($flags & 32768) { + $buffer->appendUint8($contentTypeLength); $buffer->append($contentType); + } + if ($flags & 16384) { + $buffer->appendUint8($contentEncodingLength); $buffer->append($contentEncoding); + } + if ($flags & 8192) { + $buffer->append($headersBuffer); + } + if ($flags & 4096) { + $buffer->appendUint8($deliveryMode); + } + if ($flags & 2048) { + $buffer->appendUint8($priority); + } + if ($flags & 1024) { + $buffer->appendUint8($correlationIdLength); $buffer->append($correlationId); + } + if ($flags & 512) { + $buffer->appendUint8($replyToLength); $buffer->append($replyTo); + } + if ($flags & 256) { + $buffer->appendUint8($expirationLength); $buffer->append($expiration); + } + if ($flags & 128) { + $buffer->appendUint8($messageIdLength); $buffer->append($messageId); + } + if ($flags & 64) { + $this->writer->appendTimestamp($timestamp, $buffer); + } + if ($flags & 32) { + $buffer->appendUint8($typeLength); $buffer->append($type); + } + if ($flags & 16) { + $buffer->appendUint8($userIdLength); $buffer->append($userId); + } + if ($flags & 8) { + $buffer->appendUint8($appIdLength); $buffer->append($appId); + } + if ($flags & 4) { + $buffer->appendUint8($clusterIdLength); $buffer->append($clusterId); + } + $buffer->appendUint8(206); + $len1 = $buffer->getLength() - $off1; + } + if (!$c) { + $this->cache[$ck] = [$buffer->read($len0, $off0), $buffer->read($len1, $off1)]; + if (count($this->cache) > 100) { reset($this->cache); unset($this->cache[key($this->cache)]); } + } + for ($payloadMax = $this->client->frameMax - 8 /* frame preface and frame end */, $i = 0, $l = strlen($body); $i < $l; $i += $payloadMax) { + $payloadSize = $l - $i; if ($payloadSize > $payloadMax) { $payloadSize = $payloadMax; } + $buffer->appendUint8(3); + $buffer->appendUint16($channel); + $buffer->appendUint32($payloadSize); + $buffer->append(substr($body, $i, $payloadSize)); + $buffer->appendUint8(206); + } + $this->flushWriteBuffer(); + return false; + } + + public function awaitReturn(int $channel): Protocol\MethodBasicReturnFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicReturnFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function awaitDeliver(int $channel): Protocol\MethodBasicDeliverFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicDeliverFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function get(int $channel, string $queue = '', bool $noAck = false): bool|Protocol\MethodBasicGetOkFrame|Protocol\MethodBasicGetEmptyFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(8 + strlen($queue)); + $buffer->appendUint16(60); + $buffer->appendUint16(70); + $buffer->appendInt16(0); + $buffer->appendUint8(strlen($queue)); $buffer->append($queue); + $this->writer->appendBits([$noAck], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitGetOk($channel); + } + + public function awaitGetOk(int $channel): Protocol\MethodBasicGetOkFrame|Protocol\MethodBasicGetEmptyFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicGetOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodBasicGetEmptyFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function ack(int $channel, int $deliveryTag = 0, bool $multiple = false): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(13); + $buffer->appendUint16(60); + $buffer->appendUint16(80); + $buffer->appendInt64($deliveryTag); + $this->writer->appendBits([$multiple], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitAck(int $channel): Protocol\MethodBasicAckFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicAckFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function reject(int $channel, int $deliveryTag, bool $requeue = true): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(13); + $buffer->appendUint16(60); + $buffer->appendUint16(90); + $buffer->appendInt64($deliveryTag); + $this->writer->appendBits([$requeue], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function recoverAsync(int $channel, bool $requeue = false): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(5); + $buffer->appendUint16(60); + $buffer->appendUint16(100); + $this->writer->appendBits([$requeue], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function recover(int $channel, bool $requeue = false): bool|Protocol\MethodBasicRecoverOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(5); + $buffer->appendUint16(60); + $buffer->appendUint16(110); + $this->writer->appendBits([$requeue], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitRecoverOk($channel); + } + + public function awaitRecoverOk(int $channel): Protocol\MethodBasicRecoverOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicRecoverOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function nack(int $channel, int $deliveryTag = 0, bool $multiple = false, bool $requeue = true): bool + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(13); + $buffer->appendUint16(60); + $buffer->appendUint16(120); + $buffer->appendInt64($deliveryTag); + $this->writer->appendBits([$multiple, $requeue], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return false; + } + + public function awaitNack(int $channel): Protocol\MethodBasicNackFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodBasicNackFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function txSelect(int $channel): bool|Protocol\MethodTxSelectOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(4); + $buffer->appendUint16(90); + $buffer->appendUint16(10); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitTxSelectOk($channel); + } + + public function awaitTxSelectOk(int $channel): Protocol\MethodTxSelectOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodTxSelectOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function txCommit(int $channel): bool|Protocol\MethodTxCommitOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(4); + $buffer->appendUint16(90); + $buffer->appendUint16(20); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitTxCommitOk($channel); + } + + public function awaitTxCommitOk(int $channel): Protocol\MethodTxCommitOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodTxCommitOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function txRollback(int $channel): bool|Protocol\MethodTxRollbackOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(4); + $buffer->appendUint16(90); + $buffer->appendUint16(30); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + return $this->awaitTxRollbackOk($channel); + } + + public function awaitTxRollbackOk(int $channel): Protocol\MethodTxRollbackOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodTxRollbackOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function confirmSelect(int $channel, bool $nowait = false): bool|Protocol\MethodConfirmSelectOkFrame + { + $buffer = $this->writeBuffer; + $buffer->appendUint8(1); + $buffer->appendUint16($channel); + $buffer->appendUint32(5); + $buffer->appendUint16(85); + $buffer->appendUint16(10); + $this->writer->appendBits([$nowait], $buffer); + $buffer->appendUint8(206); + $this->flushWriteBuffer(); + if (!$nowait) { + return $this->awaitConfirmSelectOk($channel); + } + return false; + } + + public function awaitConfirmSelectOk(int $channel): Protocol\MethodConfirmSelectOkFrame + { + $deferred = new Deferred(); + $this->awaitList[] = [ + 'filter' => function (Protocol\AbstractFrame $frame) use ($channel): bool { + if ($frame instanceof Protocol\MethodConfirmSelectOkFrame && $frame->channel === $channel) { + return true; + } elseif ($frame instanceof Protocol\MethodChannelCloseFrame && $frame->channel === $channel) { + $this->channelCloseOk($channel); + throw new ClientException($frame->replyText, $frame->replyCode); + } elseif ($frame instanceof Protocol\MethodConnectionCloseFrame) { + $this->connectionCloseOk(); + throw new ClientException($frame->replyText, $frame->replyCode); + } + return false; + }, + 'promise' => $deferred, + ]; + return await($deferred->promise()); + } + + public function startHeathbeatTimer(): void + { + $this->heartbeatTimer = Loop::addTimer($this->options['heartbeat'], [$this, 'onHeartbeat']); + $this->connection->on('drain', [$this, 'onHeartbeat']); + } + + /** + * Callback when heartbeat timer timed out. + */ + public function onHeartbeat() + { + $now = microtime(true); + $nextHeartbeat = ($this->lastWrite ?: $now) + $this->options['heartbeat']; + + if ($now >= $nextHeartbeat) { + $this->writer->appendFrame(new HeartbeatFrame(), $this->writeBuffer); + $this->flushWriteBuffer(); + + $this->heartbeatTimer = Loop::addTimer($this->options['heartbeat'], [$this, 'onHeartbeat']); + if (is_callable($this->options['heartbeat_callback'] ?? null)) { + $this->options['heartbeat_callback']($this); + } + } else { + $this->heartbeatTimer = Loop::addTimer($nextHeartbeat - $now, [$this, 'onHeartbeat']); + } + } +} diff --git a/src/Bunny/Constants.php b/src/Constants.php similarity index 99% rename from src/Bunny/Constants.php rename to src/Constants.php index 0642339..7f39576 100644 --- a/src/Bunny/Constants.php +++ b/src/Constants.php @@ -1,4 +1,7 @@ + */ +final class Message +{ + /** + * @param array $headers + */ + public function __construct( + public string|null $consumerTag, + public int|null $deliveryTag, + public bool|null $redelivered, + public string $exchange, + public string $routingKey, + public array $headers, + public string $content, + ) + { + } + + /** + * Returns header or default value. + * + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getHeader(string $name, mixed $default = null): mixed + { + if (array_key_exists($name, $this->headers)) { + return $this->headers[$name]; + } else { + return $default; + } + } + + /** + * Returns TRUE if message has given header. + * + * @param string $name + * @return boolean + */ + public function hasHeader(string $name): bool + { + return array_key_exists($name, $this->headers); + } + +} diff --git a/src/Bunny/Protocol/AbstractFrame.php b/src/Protocol/AbstractFrame.php similarity index 95% rename from src/Bunny/Protocol/AbstractFrame.php rename to src/Protocol/AbstractFrame.php index bc32efa..a2b4191 100644 --- a/src/Bunny/Protocol/AbstractFrame.php +++ b/src/Protocol/AbstractFrame.php @@ -1,4 +1,7 @@ + */ + public array $headers = []; /** @var int */ public $deliveryMode; @@ -217,10 +222,6 @@ public function toArray() { $headers = $this->headers; - if ($headers === null) { - $headers = []; - } - if ($this->contentType !== null) { $headers["content-type"] = $this->contentType; } diff --git a/src/Bunny/Protocol/HeartbeatFrame.php b/src/Protocol/HeartbeatFrame.php similarity index 93% rename from src/Bunny/Protocol/HeartbeatFrame.php rename to src/Protocol/HeartbeatFrame.php index 151e9e7..015634e 100644 --- a/src/Bunny/Protocol/HeartbeatFrame.php +++ b/src/Protocol/HeartbeatFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodBasicConsumeOkFrame.php b/src/Protocol/MethodBasicConsumeOkFrame.php similarity index 94% rename from src/Bunny/Protocol/MethodBasicConsumeOkFrame.php rename to src/Protocol/MethodBasicConsumeOkFrame.php index d0d9e49..0a75667 100644 --- a/src/Bunny/Protocol/MethodBasicConsumeOkFrame.php +++ b/src/Protocol/MethodBasicConsumeOkFrame.php @@ -1,4 +1,7 @@ */ public $serverProperties = []; /** @var string */ diff --git a/src/Bunny/Protocol/MethodConnectionStartOkFrame.php b/src/Protocol/MethodConnectionStartOkFrame.php similarity index 92% rename from src/Bunny/Protocol/MethodConnectionStartOkFrame.php rename to src/Protocol/MethodConnectionStartOkFrame.php index a7a2aae..af94d63 100644 --- a/src/Bunny/Protocol/MethodConnectionStartOkFrame.php +++ b/src/Protocol/MethodConnectionStartOkFrame.php @@ -1,4 +1,7 @@ */ public $clientProperties = []; /** @var string */ public $mechanism = 'PLAIN'; - /** @var string */ - public $response; - /** @var string */ public $locale = 'en_US'; diff --git a/src/Bunny/Protocol/MethodConnectionTuneFrame.php b/src/Protocol/MethodConnectionTuneFrame.php similarity index 95% rename from src/Bunny/Protocol/MethodConnectionTuneFrame.php rename to src/Protocol/MethodConnectionTuneFrame.php index 4ca1134..f1943ce 100644 --- a/src/Bunny/Protocol/MethodConnectionTuneFrame.php +++ b/src/Protocol/MethodConnectionTuneFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodExchangeBindOkFrame.php b/src/Protocol/MethodExchangeBindOkFrame.php similarity index 94% rename from src/Bunny/Protocol/MethodExchangeBindOkFrame.php rename to src/Protocol/MethodExchangeBindOkFrame.php index bb9c772..65da502 100644 --- a/src/Bunny/Protocol/MethodExchangeBindOkFrame.php +++ b/src/Protocol/MethodExchangeBindOkFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodExchangeDeclareOkFrame.php b/src/Protocol/MethodExchangeDeclareOkFrame.php similarity index 94% rename from src/Bunny/Protocol/MethodExchangeDeclareOkFrame.php rename to src/Protocol/MethodExchangeDeclareOkFrame.php index 9398959..49e1016 100644 --- a/src/Bunny/Protocol/MethodExchangeDeclareOkFrame.php +++ b/src/Protocol/MethodExchangeDeclareOkFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodExchangeUnbindOkFrame.php b/src/Protocol/MethodExchangeUnbindOkFrame.php similarity index 94% rename from src/Bunny/Protocol/MethodExchangeUnbindOkFrame.php rename to src/Protocol/MethodExchangeUnbindOkFrame.php index e00a686..489827c 100644 --- a/src/Bunny/Protocol/MethodExchangeUnbindOkFrame.php +++ b/src/Protocol/MethodExchangeUnbindOkFrame.php @@ -1,4 +1,7 @@ classId = $classId; diff --git a/src/Bunny/Protocol/MethodQueueBindFrame.php b/src/Protocol/MethodQueueBindFrame.php similarity index 89% rename from src/Bunny/Protocol/MethodQueueBindFrame.php rename to src/Protocol/MethodQueueBindFrame.php index 26b6310..922c74f 100644 --- a/src/Bunny/Protocol/MethodQueueBindFrame.php +++ b/src/Protocol/MethodQueueBindFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodQueueBindOkFrame.php b/src/Protocol/MethodQueueBindOkFrame.php similarity index 94% rename from src/Bunny/Protocol/MethodQueueBindOkFrame.php rename to src/Protocol/MethodQueueBindOkFrame.php index 72aa4e6..f460b2f 100644 --- a/src/Bunny/Protocol/MethodQueueBindOkFrame.php +++ b/src/Protocol/MethodQueueBindOkFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodQueueDeclareOkFrame.php b/src/Protocol/MethodQueueDeclareOkFrame.php similarity index 95% rename from src/Bunny/Protocol/MethodQueueDeclareOkFrame.php rename to src/Protocol/MethodQueueDeclareOkFrame.php index 1a27e8d..3835273 100644 --- a/src/Bunny/Protocol/MethodQueueDeclareOkFrame.php +++ b/src/Protocol/MethodQueueDeclareOkFrame.php @@ -1,4 +1,7 @@ */ public $arguments = []; public function __construct() diff --git a/src/Bunny/Protocol/MethodQueueUnbindOkFrame.php b/src/Protocol/MethodQueueUnbindOkFrame.php similarity index 94% rename from src/Bunny/Protocol/MethodQueueUnbindOkFrame.php rename to src/Protocol/MethodQueueUnbindOkFrame.php index 7dc46e0..f9b7689 100644 --- a/src/Bunny/Protocol/MethodQueueUnbindOkFrame.php +++ b/src/Protocol/MethodQueueUnbindOkFrame.php @@ -1,4 +1,7 @@ getLength() < 7) { @@ -150,10 +150,9 @@ public function consumeFrame(Buffer $buffer) /** * Consumes AMQP table from buffer. * - * @param Buffer $originalBuffer - * @return array + * @return array */ - public function consumeTable(Buffer $originalBuffer) + public function consumeTable(Buffer $originalBuffer): array { $buffer = $originalBuffer->consumeSlice($originalBuffer->consumeUint32()); @@ -168,10 +167,9 @@ public function consumeTable(Buffer $originalBuffer) /** * Consumes AMQP array from buffer. * - * @param Buffer $originalBuffer - * @return array + * @return array */ - public function consumeArray(Buffer $originalBuffer) + public function consumeArray(Buffer $originalBuffer): array { $buffer = $originalBuffer->consumeSlice($originalBuffer->consumeUint32()); $data = []; @@ -183,11 +181,8 @@ public function consumeArray(Buffer $originalBuffer) /** * Consumes AMQP timestamp from buffer. - * - * @param Buffer $buffer - * @return \DateTime */ - public function consumeTimestamp(Buffer $buffer) + public function consumeTimestamp(Buffer $buffer): \DateTime { $d = new \DateTime(); $d->setTimestamp($buffer->consumeUint64()); @@ -197,11 +192,9 @@ public function consumeTimestamp(Buffer $buffer) /** * Consumes packed bits from buffer. * - * @param Buffer $buffer - * @param int $n - * @return array + * @return array */ - public function consumeBits(Buffer $buffer, $n) + public function consumeBits(Buffer $buffer, int $n): array { $bits = []; $value = $buffer->consumeUint8(); @@ -213,11 +206,8 @@ public function consumeBits(Buffer $buffer, $n) /** * Consumes AMQP decimal value. - * - * @param Buffer $buffer - * @return int */ - public function consumeDecimalValue(Buffer $buffer) + public function consumeDecimalValue(Buffer $buffer): int { $scale = $buffer->consumeUint8(); $value = $buffer->consumeUint32(); @@ -226,11 +216,8 @@ public function consumeDecimalValue(Buffer $buffer) /** * Consumes AMQP table/array field value. - * - * @param Buffer $buffer - * @return mixed */ - public function consumeFieldValue(Buffer $buffer) + public function consumeFieldValue(Buffer $buffer): mixed { $fieldType = $buffer->consumeUint8(); @@ -280,5 +267,4 @@ public function consumeFieldValue(Buffer $buffer) ); } } - } diff --git a/src/Bunny/Protocol/ProtocolReaderGenerated.php b/src/Protocol/ProtocolReaderGenerated.php similarity index 98% rename from src/Bunny/Protocol/ProtocolReaderGenerated.php rename to src/Protocol/ProtocolReaderGenerated.php index 303e8ab..ccf9a1f 100644 --- a/src/Bunny/Protocol/ProtocolReaderGenerated.php +++ b/src/Protocol/ProtocolReaderGenerated.php @@ -1,4 +1,7 @@ */ - abstract public function consumeBits(Buffer $buffer, $n); + abstract public function consumeBits(Buffer $buffer, int $n): array; /** * Consumes AMQP method frame. - * - * @param Buffer $buffer - * @return MethodFrame */ - public function consumeMethodFrame(Buffer $buffer) + public function consumeMethodFrame(Buffer $buffer): MethodFrame { $classId = $buffer->consumeUint16(); $methodId = $buffer->consumeUint16(); diff --git a/src/Bunny/Protocol/ProtocolWriter.php b/src/Protocol/ProtocolWriter.php similarity index 93% rename from src/Bunny/Protocol/ProtocolWriter.php rename to src/Protocol/ProtocolWriter.php index b710e13..f4a08da 100644 --- a/src/Bunny/Protocol/ProtocolWriter.php +++ b/src/Protocol/ProtocolWriter.php @@ -1,8 +1,11 @@ payload !== null) { // payload already supplied @@ -134,10 +133,9 @@ public function appendFrame(AbstractFrame $frame, Buffer $buffer) /** * Appends AMQP table to buffer. * - * @param array $table - * @param Buffer $originalBuffer + * @param array $table */ - public function appendTable(array $table, Buffer $originalBuffer) + public function appendTable(array $table, Buffer $originalBuffer): void { $buffer = new Buffer(); @@ -154,10 +152,9 @@ public function appendTable(array $table, Buffer $originalBuffer) /** * Appends AMQP array to buffer. * - * @param array $value - * @param Buffer $originalBuffer + * @param array $value */ - public function appendArray(array $value, Buffer $originalBuffer) + public function appendArray(array $value, Buffer $originalBuffer): void { $buffer = new Buffer(); @@ -171,11 +168,8 @@ public function appendArray(array $value, Buffer $originalBuffer) /** * Appends AMQP timestamp to buffer. - * - * @param \DateTimeInterface $value - * @param Buffer $buffer */ - public function appendTimestamp(\DateTimeInterface $value, Buffer $buffer) + public function appendTimestamp(\DateTimeInterface $value, Buffer $buffer): void { $buffer->appendUint64($value->getTimestamp()); } @@ -183,10 +177,9 @@ public function appendTimestamp(\DateTimeInterface $value, Buffer $buffer) /** * Appends packed bits to buffer. * - * @param array $bits - * @param Buffer $buffer + * @param array $bits */ - public function appendBits(array $bits, Buffer $buffer) + public function appendBits(array $bits, Buffer $buffer): void { $value = 0; foreach ($bits as $n => $bit) { @@ -198,11 +191,8 @@ public function appendBits(array $bits, Buffer $buffer) /** * Appends AMQP table/array field value to buffer. - * - * @param mixed $value - * @param Buffer $buffer */ - public function appendFieldValue($value, Buffer $buffer) + public function appendFieldValue(mixed $value, Buffer $buffer): void { if (is_string($value)) { $buffer->appendUint8(Constants::FIELD_LONG_STRING); diff --git a/src/Bunny/Protocol/ProtocolWriterGenerated.php b/src/Protocol/ProtocolWriterGenerated.php similarity index 99% rename from src/Bunny/Protocol/ProtocolWriterGenerated.php rename to src/Protocol/ProtocolWriterGenerated.php index de904fd..eea9ed6 100644 --- a/src/Bunny/Protocol/ProtocolWriterGenerated.php +++ b/src/Protocol/ProtocolWriterGenerated.php @@ -1,4 +1,7 @@ append('AMQP'); $buffer->appendUint8(0); @@ -50,7 +53,7 @@ public function appendProtocolHeader(Buffer $buffer) * @param MethodFrame $frame * @param Buffer $buffer */ - public function appendMethodFrame(MethodFrame $frame, Buffer $buffer) + public function appendMethodFrame(MethodFrame $frame, Buffer $buffer): void { $buffer->appendUint16($frame->classId); $buffer->appendUint16($frame->methodId); diff --git a/test/Bunny/AsyncClientTest.php b/test/Bunny/AsyncClientTest.php deleted file mode 100644 index 2b3e11e..0000000 --- a/test/Bunny/AsyncClientTest.php +++ /dev/null @@ -1,355 +0,0 @@ -helper = new AsynchronousClientHelper(); - } - - public function testConnect() - { - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $client = $this->helper->createClient($loop); - - $this->assertFalse($client->isConnected()); - - $client->connect()->then(function (Client $client) { - $this->assertTrue($client->isConnected()); - - return $client->disconnect(); - })->then(function (Client $client) use ($loop) { - $this->assertFalse($client->isConnected()); - $loop->stop(); - })->done(); - - $loop->run(); - - $this->assertFalse($client->isConnected()); - } - - public function testConnectFailure() - { - $this->expectException(ClientException::class); - - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $options = $this->helper->getDefaultOptions(); - - $options['vhost'] = 'bogus-vhost'; - - $client = $this->helper->createClient($loop, $options); - - $client->connect()->then(function () use ($loop) { - $this->fail("client should not connect"); - $loop->stop(); - })->done(); - - $loop->run(); - } - - public function testOpenChannel() - { - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $client = $this->helper->createClient($loop); - $client->connect()->then(function (Client $client) { - return $client->channel(); - })->then(function (Channel $ch) { - return $ch->getClient()->disconnect(); - })->then(function () use ($loop) { - $loop->stop(); - })->done(); - - $loop->run(); - - $this->assertTrue(true); - } - - public function testOpenMultipleChannel() - { - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $client = $this->helper->createClient($loop); - $client->connect()->then(function (Client $client) { - return Promise\all([ - $client->channel(), - $client->channel(), - $client->channel(), - ]); - })->then(function (array $chs) { - /** @var Channel[] $chs */ - $this->assertCount(3, $chs); - for ($i = 0, $l = count($chs); $i < $l; ++$i) { - $this->assertInstanceOf(Channel::class, $chs[$i]); - for ($j = 0; $j < $i; ++$j) { - $this->assertNotEquals($chs[$i]->getChannelId(), $chs[$j]->getChannelId()); - } - } - - return $chs[0]->getClient()->disconnect(); - - })->then(function () use ($loop) { - $loop->stop(); - })->done(); - - $loop->run(); - } - - public function testConflictingQueueDeclareRejects() - { - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $client = $this->helper->createClient($loop); - $client->connect()->then(function (Client $client) { - return $client->channel(); - })->then(function (Channel $ch) { - return Promise\all([ - $ch->queueDeclare("conflict", false, false), - $ch->queueDeclare("conflict", false, true), - ]); - })->then(function () use ($loop) { - $this->fail("Promise should get rejected"); - $loop->stop(); - }, function (\Exception $e) use ($loop) { - $this->assertInstanceOf(ClientException::class, $e); - $loop->stop(); - })->done(); - - $loop->run(); - } - - public function testDisconnectWithBufferedMessages() - { - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $processed = 0; - - $client = $this->helper->createClient($loop); - $client->connect()->then(function (Client $client) { - return $client->channel(); - })->then(function (Channel $channel) use ($client, $loop, &$processed) { - return Promise\all([ - $channel->qos(0, 1000), - $channel->queueDeclare("disconnect_test"), - $channel->consume(function (Message $message, Channel $channel) use ($client, $loop, &$processed) { - $channel->ack($message); - - ++$processed; - - $client->disconnect()->done(function () use ($loop) { - $loop->stop(); - }); - - }, "disconnect_test"), - $channel->publish(".", [], "", "disconnect_test"), - $channel->publish(".", [], "", "disconnect_test"), - $channel->publish(".", [], "", "disconnect_test"), - ]); - })->done(); - - $loop->run(); - - // all messages should be processed - $this->assertEquals(1, $processed); - - // Clean-up Queue - $client->connect()->then(function (Client $client) { - return $client->channel(); - })->then(function (Channel $channel) use ($client, $loop, &$processed) { - return Promise\all([ - $channel->queueDelete("disconnect_test"), - $client->disconnect()->done(function () use ($loop) { - $loop->stop(); - }) - ]); - })->done(); - - $loop->run(); - } - - public function testGet() - { - $loop = Factory::create(); - - $loop->addTimer(1, function () { - throw new TimeoutException(); - }); - - $client = $this->helper->createClient($loop); - /** @var Channel $channel */ - $channel = null; - $client->connect()->then(function (Client $client) { - return $client->channel(); - - })->then(function (Channel $ch) use (&$channel) { - $channel = $ch; - - return Promise\all([ - $channel->queueDeclare("get_test"), - $channel->publish(".", [], "", "get_test"), - ]); - - })->then(function () use (&$channel) { - return $channel->get("get_test", true); - - })->then(function (Message $message1 = null) use (&$channel) { - $this->assertNotNull($message1); - $this->assertInstanceOf(Message::class, $message1); - $this->assertEquals($message1->exchange, ""); - $this->assertEquals($message1->content, "."); - - return $channel->get("get_test", true); - - })->then(function (Message $message2 = null) use (&$channel) { - $this->assertNull($message2); - - return $channel->publish("..", [], "", "get_test"); - - })->then(function () use (&$channel) { - return $channel->get("get_test"); - - })->then(function (Message $message3 = null) use (&$channel) { - $this->assertNotNull($message3); - $this->assertInstanceOf(Message::class, $message3); - $this->assertEquals($message3->exchange, ""); - $this->assertEquals($message3->content, ".."); - - $channel->ack($message3); - - return $channel->getClient()->disconnect(); - - })->then(function () use ($loop) { - $loop->stop(); - })->done(); - - $loop->run(); - } - - public function testReturn() - { - $loop = Factory::create(); - - $loop->addTimer(1, function () { - throw new TimeoutException(); - }); - - $client = $this->helper->createClient($loop); - - /** @var Channel $channel */ - $channel = null; - /** @var Message $returnedMessage */ - $returnedMessage = null; - /** @var MethodBasicReturnFrame $returnedFrame */ - $returnedFrame = null; - - $client->connect()->then(function (Client $client) { - return $client->channel(); - - })->then(function (Channel $ch) use ($loop, &$channel, &$returnedMessage, &$returnedFrame) { - $channel = $ch; - - $channel->addReturnListener(function (Message $message, MethodBasicReturnFrame $frame) use ($loop, &$returnedMessage, &$returnedFrame) { - $returnedMessage = $message; - $returnedFrame = $frame; - $loop->stop(); - }); - - return $channel->publish("xxx", [], "", "404", true); - })->done(); - - $loop->run(); - - $this->assertNotNull($returnedMessage); - $this->assertInstanceOf(Message::class, $returnedMessage); - $this->assertEquals("xxx", $returnedMessage->content); - $this->assertEquals("", $returnedMessage->exchange); - $this->assertEquals("404", $returnedMessage->routingKey); - } - - public function testHeartBeatCallback() - { - $loop = Factory::create(); - - $loop->addTimer(3, function () { - throw new TimeoutException(); - }); - - $called = 0; - - $defaultOptions = $this->helper->getDefaultOptions(); - - $client = $this->helper->createClient($loop, array_merge($defaultOptions, [ - 'heartbeat' => 1.0, - 'heartbeat_callback' => function () use (&$called) { - $called += 1; - } - ])); - - $client->connect()->then(function (Client $client) { - sleep(1); - return $client->channel(); - })->then(function (Channel $ch) { - sleep(1); - return $ch->queueDeclare('hello', false, false, false, false)->then(function () use ($ch) { - return $ch; - }); - })->then(function (Channel $ch) { - return $ch->getClient()->disconnect(); - })->then(function () use ($loop) { - $loop->stop(); - })->done(); - - $loop->run(); - - $this->assertEquals(2, $called); - } - -} diff --git a/test/Bunny/ChannelTest.php b/test/ChannelTest.php similarity index 69% rename from test/Bunny/ChannelTest.php rename to test/ChannelTest.php index 7c0b19f..4e7cce6 100644 --- a/test/Bunny/ChannelTest.php +++ b/test/ChannelTest.php @@ -4,13 +4,19 @@ declare(strict_types=1); -namespace Bunny; +namespace Bunny\Test; +use Bunny\Channel; +use Bunny\Client; +use Bunny\Message; use Bunny\Test\Library\SynchronousClientHelper; use PHPUnit\Framework\TestCase; +use WyriHaximus\React\PHPUnit\RunTestsInFibersTrait; class ChannelTest extends TestCase { + use RunTestsInFibersTrait; + /** * @var SynchronousClientHelper */ @@ -27,15 +33,10 @@ public function testClose() { $c = $this->helper->createClient(); $c->connect(); - $promise = $c->channel()->close(); - $this->assertInstanceOf("React\\Promise\\PromiseInterface", $promise); - $promise->done(function () use ($c) { - $c->stop(); - }); - $c->run(); + $c->channel()->close(); $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $c->disconnect(); $this->assertFalse($c->isConnected()); } @@ -44,11 +45,10 @@ public function testExchangeDeclare() $c = $this->helper->createClient(); $ch = $c->connect()->channel(); + $this->assertTrue($c->isConnected()); $ch->exchangeDeclare("test_exchange", "direct", false, false, true); - $c->disconnect(); - $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $c->disconnect(); $this->assertFalse($c->isConnected()); } @@ -57,11 +57,10 @@ public function testQueueDeclare() $c = $this->helper->createClient(); $ch = $c->connect()->channel(); + $this->assertTrue($c->isConnected()); $ch->queueDeclare("test_queue", false, false, false, true); - $c->disconnect(); - $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $c->disconnect(); $this->assertFalse($c->isConnected()); } @@ -70,13 +69,14 @@ public function testQueueBind() $c = $this->helper->createClient(); $ch = $c->connect()->channel(); + $this->assertTrue($c->isConnected()); $ch->exchangeDeclare("test_exchange", "direct", false, false, true); + $this->assertTrue($c->isConnected()); $ch->queueDeclare("test_queue", false, false, false, true); - $ch->queueBind("test_queue", "test_exchange"); - $ch->getClient()->disconnect(); - $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $ch->queueBind("test_exchange", "test_queue"); + $this->assertTrue($c->isConnected()); + $c->disconnect(); $this->assertFalse($c->isConnected()); } @@ -85,11 +85,10 @@ public function testPublish() $c = $this->helper->createClient(); $ch = $c->connect()->channel(); + $this->assertTrue($c->isConnected()); $ch->publish("test publish", []); - $ch->getClient()->disconnect(); - $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $c->disconnect(); $this->assertFalse($c->isConnected()); } @@ -98,35 +97,16 @@ public function testConsume() $c = $this->helper->createClient(); $ch = $c->connect()->channel(); + $this->assertTrue($c->isConnected()); $ch->queueDeclare("test_queue", false, false, false, true); + $this->assertTrue($c->isConnected()); $ch->consume(function (Message $msg, Channel $ch, Client $c) { $this->assertEquals("hi", $msg->content); - $c->stop(); }); + $this->assertTrue($c->isConnected()); $ch->publish("hi", [], "", "test_queue"); - $c->run(); - $c->disconnect(); - $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); - $this->assertFalse($c->isConnected()); - } - - public function testRun() - { - $c = $this->helper->createClient(); - - $ch = $c->connect()->channel(); - $ch->queueDeclare("test_queue", false, false, false, true); - $ch->publish("hi again", [], "", "test_queue"); - $ch->run(function (Message $msg, Channel $ch, Client $c) { - $this->assertEquals("hi again", $msg->content); - $c->stop(); - }); $c->disconnect(); - - $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); $this->assertFalse($c->isConnected()); } @@ -136,17 +116,15 @@ public function testHeaders() $ch = $c->connect()->channel(); $ch->queueDeclare("test_queue", false, false, false, true); - $ch->publish("hi html", ["content-type" => "text/html"], "", "test_queue"); - $ch->run(function (Message $msg, Channel $ch, Client $c) { + $ch->consume(function (Message $msg, Channel $ch, Client $c) { $this->assertTrue($msg->hasHeader("content-type")); $this->assertEquals("text/html", $msg->getHeader("content-type")); $this->assertEquals("hi html", $msg->content); - $c->stop(); }); - $c->disconnect(); + $ch->publish("hi html", ["content-type" => "text/html"], "", "test_queue"); $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $c->disconnect(); $this->assertFalse($c->isConnected()); } @@ -158,15 +136,13 @@ public function testBigMessage() $ch = $c->connect()->channel(); $ch->queueDeclare("test_queue", false, false, false, true); - $ch->publish($body, [], "", "test_queue"); - $ch->run(function (Message $msg, Channel $ch, Client $c) use ($body) { + $ch->consume(function (Message $msg, Channel $ch, Client $c) use ($body) { $this->assertEquals($body, $msg->content); - $c->stop(); }); - $c->disconnect(); + $ch->publish($body, [], "", "test_queue"); $this->assertTrue($c->isConnected()); - $this->helper->disconnectClientWithEventLoop($c); + $c->disconnect(); $this->assertFalse($c->isConnected()); } } diff --git a/test/Bunny/ClientTest.php b/test/ClientTest.php similarity index 73% rename from test/Bunny/ClientTest.php rename to test/ClientTest.php index b3a58c1..ab91b1f 100644 --- a/test/Bunny/ClientTest.php +++ b/test/ClientTest.php @@ -4,23 +4,31 @@ declare(strict_types=1); -namespace Bunny; +namespace Bunny\Test; +use Bunny\Channel; use Bunny\Exception\ChannelException; use Bunny\Exception\ClientException; +use Bunny\Message; use Bunny\Protocol\MethodBasicAckFrame; use Bunny\Protocol\MethodBasicReturnFrame; use Bunny\Test\Library\Environment; use Bunny\Test\Library\Paths; use Bunny\Test\Library\SynchronousClientHelper; use PHPUnit\Framework\TestCase; -use Symfony\Component\Process\Exception\ProcessFailedException; -use Symfony\Component\Process\Process; - +use React\ChildProcess\Process; +use React\EventLoop\Loop; +use React\Promise\Promise; +use WyriHaximus\React\PHPUnit\RunTestsInFibersTrait; +use function React\Async\async; +use function React\Async\await; +use function React\Promise\Stream\buffer; use const SIGINT; class ClientTest extends TestCase { + use RunTestsInFibersTrait; + /** * @var SynchronousClientHelper */ @@ -70,7 +78,7 @@ public function testOpenChannel() $this->assertInstanceOf(Channel::class, $channel); $this->assertTrue($client->isConnected()); - $this->helper->disconnectClientWithEventLoop($client); + $client->disconnect(); $this->assertFalse($client->isConnected()); } @@ -86,20 +94,10 @@ public function testOpenMultipleChannel() $this->assertNotEquals($ch2->getChannelId(), $ch3->getChannelId()); $this->assertTrue($client->isConnected()); - $this->helper->disconnectClientWithEventLoop($client); + $client->disconnect(); $this->assertFalse($client->isConnected()); } - public function testRunMaxSeconds() - { - $client = $this->helper->createClient(); - $client->connect(); - $s = microtime(true); - $client->run(1.0); - $e = microtime(true); - $this->assertLessThan(2.0, $e - $s); - } - public function testDisconnectWithBufferedMessages() { $client = $this->helper->createClient(); @@ -110,27 +108,25 @@ public function testDisconnectWithBufferedMessages() $channel->qos(0, 1000); $channel->queueDeclare("disconnect_test"); - $channel->consume(function (Message $message, Channel $channel) use ($client, &$processed) { + $channel->consume(async(function (Message $message, Channel $channel) use ($client, &$processed) { $channel->ack($message); ++$processed; - $client->disconnect()->done(function () use ($client) { - $client->stop(); - }); - }); + $client->disconnect(); + })); $channel->publish(".", [], "", "disconnect_test"); $channel->publish(".", [], "", "disconnect_test"); $channel->publish(".", [], "", "disconnect_test"); - $client->run(5); + await(\React\Promise\Timer\sleep(5)); $this->assertEquals(1, $processed); $this->assertFalse($client->isConnected()); // Clean-up Queue $client = $this->helper->createClient(); - $client->connect(); $channel = $client->channel(); $channel->queueDelete("disconnect_test"); + $client->disconnect(); } /** @@ -142,24 +138,24 @@ public function testStopConsumerWithSigInt() $path = Paths::getTestsRootPath() . '/scripts/bunny-consumer.php'; - $process = new Process([$path, Environment::getTestRabbitMqConnectionUri(), $queueName, '0']); + $process = new Process($path . ' ' . Environment::getTestRabbitMqConnectionUri() . ' ' .$queueName . ' ' . '0'); - $process->start(); - - $signalSent = false; - $starttime = microtime(true); + Loop::futureTick(static function () use ($process): void { + $process->start(); + }); // Send SIGINT after 1.0 seconds - while ($process->isRunning()) { - if (!$signalSent && microtime(true) > $starttime + 1.0) { - $process->signal(SIGINT); - $signalSent = true; - } + Loop::addTimer(1, static function () use ($process): void { + $process->terminate(SIGINT); + }); - usleep(10000); - } + $termination = new Promise(static function (callable $resolve) use ($process): void { + $process->on('exit', static function ($code) use ($resolve): void { + $resolve($code === 0); + }); + }); - self::assertTrue($process->isSuccessful(), $process->getOutput() . "\n" . $process->getErrorOutput()); + self::assertTrue(await($termination), await(buffer($process->stdout)) . "\n" . await(buffer($process->stderr))); } public function testGet() @@ -183,25 +179,24 @@ public function testGet() $channel->publish("..", [], "", "get_test"); $channel->get("get_test"); - $client->disconnect()->then(function () use ($client) { - $client->connect(); + $client->disconnect(); + + await(\React\Promise\Timer\sleep(5)); - $channel = $client->channel(); - $message3 = $channel->get("get_test"); - $this->assertNotNull($message3); - $this->assertInstanceOf(Message::class, $message3); - $this->assertEquals($message3->exchange, ""); - $this->assertEquals($message3->content, ".."); + $client->connect(); - $channel->ack($message3); + $channel = $client->channel(); + $message3 = $channel->get("get_test"); + $this->assertNotNull($message3); + $this->assertInstanceOf(Message::class, $message3); + $this->assertEquals($message3->exchange, ""); + $this->assertEquals($message3->content, ".."); - return $client->disconnect(); + $channel->ack($message3); - })->then(function () use ($client) { - $client->stop(); - })->done(); + $client->disconnect(); - $client->run(5); + await(\React\Promise\Timer\sleep(5)); $this->assertFalse($client->isConnected()); } @@ -214,24 +209,19 @@ public function testReturn() /** @var Message $returnedMessage */ $returnedMessage = null; - /** @var MethodBasicReturnFrame $returnedFrame */ - $returnedFrame = null; $channel->addReturnListener(function ( Message $message, MethodBasicReturnFrame $frame ) use ( $client, - &$returnedMessage, - &$returnedFrame + &$returnedMessage ) { $returnedMessage = $message; - $returnedFrame = $frame; - $client->stop(); }); $channel->publish("xxx", [], "", "404", true); - $client->run(1); + await(\React\Promise\Timer\sleep(1)); $this->assertNotNull($returnedMessage); $this->assertInstanceOf(Message::class, $returnedMessage); @@ -240,7 +230,7 @@ public function testReturn() $this->assertEquals("404", $returnedMessage->routingKey); $this->assertTrue($client->isConnected()); - $this->helper->disconnectClientWithEventLoop($client); + $client->disconnect(); $this->assertFalse($client->isConnected()); } @@ -267,7 +257,7 @@ public function testTxs() $this->assertNull($nothing); $this->assertTrue($client->isConnected()); - $this->helper->disconnectClientWithEventLoop($client); + $client->disconnect(); $this->assertFalse($client->isConnected()); } @@ -281,6 +271,10 @@ public function testTxSelectCannotBeCalledMultipleTimes() $channel->txSelect(); $channel->txSelect(); + + $this->assertTrue($client->isConnected()); + $client->disconnect(); + $this->assertFalse($client->isConnected()); } public function testConfirmMode() @@ -290,21 +284,19 @@ public function testConfirmMode() $channel = $client->channel(); $deliveryTag = null; - $channel->confirmSelect(function (MethodBasicAckFrame $frame) use (&$deliveryTag, $client) { + $channel->confirmSelect(async(function (MethodBasicAckFrame $frame) use (&$deliveryTag, $client) { if ($frame->deliveryTag === $deliveryTag) { $deliveryTag = null; - $client->stop(); + $client->disconnect(); } - }); + })); - $deliveryTag = $channel->publish("."); + $deliveryTag = $channel->publish("tst_cfm_m"); - $client->run(1); + await(\React\Promise\Timer\sleep(1)); $this->assertNull($deliveryTag); - $this->assertTrue($client->isConnected()); - $this->helper->disconnectClientWithEventLoop($client); $this->assertFalse($client->isConnected()); } @@ -323,22 +315,20 @@ public function testEmptyMessage() $processed = 0; $channel->consume( - function (Message $message, Channel $channel) use ($client, &$processed) { + async(function (Message $message, Channel $channel) use ($client, &$processed) { $this->assertEmpty($message->content); $channel->ack($message); if (++$processed === 2) { - $client->disconnect()->done(function () use ($client) { - $client->stop(); - }); + $client->disconnect(); } - }, + }), "empty_body_message_test" ); $channel->publish("", [], "", "empty_body_message_test"); $channel->publish("", [], "", "empty_body_message_test"); - $client->run(1); + await(\React\Promise\Timer\sleep(0.01)); $this->assertFalse($client->isConnected()); } @@ -349,7 +339,7 @@ public function testHeartBeatCallback() $options = $this->helper->getDefaultOptions(); - $options['heartbeat'] = 1.0; + $options['heartbeat'] = 0.1; $options['heartbeat_callback'] = function () use (&$called) { $called += 1; }; @@ -357,7 +347,9 @@ public function testHeartBeatCallback() $client = $this->helper->createClient($options); $client->connect(); - $client->run(2); + + await(\React\Promise\Timer\sleep(0.2)); + $client->disconnect(); $this->assertGreaterThan(0, $called); diff --git a/test/Bunny/Test/Exception/TimeoutException.php b/test/Exception/TimeoutException.php similarity index 100% rename from test/Bunny/Test/Exception/TimeoutException.php rename to test/Exception/TimeoutException.php diff --git a/test/Library/AsynchronousClientHelper.php b/test/Library/AsynchronousClientHelper.php deleted file mode 100644 index 1adb13a..0000000 --- a/test/Library/AsynchronousClientHelper.php +++ /dev/null @@ -1,36 +0,0 @@ -getDefaultOptions(), $options ?? []); - - return new Client($loop, $options); - } - - /** - * @return array - */ - public function getDefaultOptions(): array - { - $options = []; - - $options = array_merge($options, parseAmqpUri(Environment::getTestRabbitMqConnectionUri())); - - return $options; - } -} diff --git a/test/Library/SynchronousClientHelper.php b/test/Library/SynchronousClientHelper.php index fefdeda..c6fc506 100644 --- a/test/Library/SynchronousClientHelper.php +++ b/test/Library/SynchronousClientHelper.php @@ -21,51 +21,6 @@ public function createClient(array $options = null): Client return new Client($options); } - /** - * Disconnects a synchronous client in a reliable way - * - * Calling just `Client::disconnect` instead of running the code in this - * method does only work as (probably) expected (ie. actually close the - * connection to the broker) if the client does not have any open channels. - * Otherwise, `Client::disconnect` waits for `Channel::close` on open - * channels to complete which is promise-based and needs a running event - * loop (`Client::run`) to be able to fulfill its promise. If there is no - * running event loop after calling `Client::disconnect`, the client will - * stay connected until its destructor is called or the connection is - * closed by the broker (eg. because of missing a heartbeat event). - * - * The code in this method contains the same logic as `Client::__destruct`. - * - * See this discussion for more details: - * - * - https://github.com/jakubkulhan/bunny/issues/93 - * - * @param Client $client - * - * @return void - */ - public function disconnectClientWithEventLoop(Client $client) - { - if (!$client->isConnected()) { - return; - } - - /** @var Promise $disconnectPromise */ - $disconnectPromise = $client->disconnect(); - - $disconnectPromise->done( - function () use ($client) { - $client->stop(); - } - ); - - if (!$client->isConnected()) { - return; - } - - $client->run(); - } - /** * @return array */ diff --git a/test/Bunny/Protocol/BufferTest.php b/test/Protocol/BufferTest.php similarity index 99% rename from test/Bunny/Protocol/BufferTest.php rename to test/Protocol/BufferTest.php index 31ff090..d166136 100644 --- a/test/Bunny/Protocol/BufferTest.php +++ b/test/Protocol/BufferTest.php @@ -1,7 +1,8 @@ helper = new SynchronousClientHelper(); - $this->asyncHelper = new AsynchronousClientHelper(); } public function testConnect() @@ -48,26 +36,6 @@ public function testConnect() $this->assertTrue(true); } - public function testConnectAsync() { - $options = $this->getOptions(); - $loop = Factory::create(); - - $loop->addTimer(5, function () { - throw new TimeoutException(); - }); - - $client = $this->asyncHelper->createClient($loop, $options); - $client->connect()->then(function (AsyncClient $client) { - return $client->disconnect(); - })->then(function () use ($loop) { - $loop->stop(); - })->done(); - - $loop->run(); - - $this->assertTrue(true); - } - public function testConnectWithMissingClientCert() { $options = $this->getOptions(); @@ -121,7 +89,7 @@ protected function getOptions() // checking CA-file $caFile = Environment::getSslCa(); - $testsDir = dirname(__DIR__); + $testsDir = __DIR__; $caFile = $testsDir . '/' . $caFile; if (!file_exists($caFile) || !is_file($caFile)) { $this->fail('Missing CA file: "' . $caFile . '"'); diff --git a/test/scripts/bunny-consumer.php b/test/scripts/bunny-consumer.php index 95995e6..1d50fe2 100755 --- a/test/scripts/bunny-consumer.php +++ b/test/scripts/bunny-consumer.php @@ -10,7 +10,7 @@ use Bunny\Channel; use Bunny\Client; use Bunny\Message; - +use React\EventLoop\Loop; use function Bunny\Test\Library\parseAmqpUri; require __DIR__ . '/../../vendor/autoload.php'; @@ -22,9 +22,7 @@ function app(array $args) $client = new Client($connection); pcntl_signal(SIGINT, function () use ($client) { - $client->disconnect()->done(function () use ($client) { - $client->stop(); - }); + $client->disconnect(); }); $client->connect(); @@ -35,7 +33,9 @@ function app(array $args) $channel->consume(function (Message $message, Channel $channel) use ($client) { $channel->ack($message); }); - $client->run($args['maxSeconds'] > 0 ? $args['maxSeconds'] : null); + Loop::addTimer($args['maxSeconds'], static function () use ($client): void { + $client->disconnect(); + }); } $argv_copy = $argv; diff --git a/tutorial/1-hello-world/receive-async.php b/tutorial/1-hello-world/receive-async.php deleted file mode 100644 index 9081422..0000000 --- a/tutorial/1-hello-world/receive-async.php +++ /dev/null @@ -1,31 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->queueDeclare('hello', false, false, false, false)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - echo " [x] Received ", $message->content, "\n"; - }, - 'hello', - '', - false, - true - ); -}); - -$loop->run(); diff --git a/tutorial/1-hello-world/receive.php b/tutorial/1-hello-world/receive.php index 27841ca..96707fd 100644 --- a/tutorial/1-hello-world/receive.php +++ b/tutorial/1-hello-world/receive.php @@ -4,21 +4,21 @@ use Bunny\Client; use Bunny\Message; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->queueDeclare('hello', false, false, false, false); echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; -$channel->run( - function (Message $message, Channel $channel, Client $client) { +$channel->consume( + function (Message $message, Channel $channel) { echo " [x] Received ", $message->content, "\n"; }, 'hello', '', false, - true + true, ); diff --git a/tutorial/1-hello-world/send-async.php b/tutorial/1-hello-world/send-async.php deleted file mode 100644 index 9cc9263..0000000 --- a/tutorial/1-hello-world/send-async.php +++ /dev/null @@ -1,32 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->queueDeclare('hello', false, false, false, false)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - echo " [x] Sending 'Hello World!'\n"; - return $channel->publish('Hello World!', [], '', 'hello')->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - echo " [x] Sent 'Hello World!'\n"; - $client = $channel->getClient(); - return $channel->close()->then(function () use ($client) { - return $client; - }); -})->then(function (Client $client) { - $client->disconnect(); -}); - -$loop->run(); \ No newline at end of file diff --git a/tutorial/1-hello-world/send.php b/tutorial/1-hello-world/send.php index 699c618..5beea42 100644 --- a/tutorial/1-hello-world/send.php +++ b/tutorial/1-hello-world/send.php @@ -2,11 +2,10 @@ use Bunny\Client; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); - $channel->queueDeclare('hello', false, false, false, false); $channel->publish('Hello World!', [], '', 'hello'); diff --git a/tutorial/2-work-queues/new_task-async.php b/tutorial/2-work-queues/new_task-async.php deleted file mode 100644 index 4bc918b..0000000 --- a/tutorial/2-work-queues/new_task-async.php +++ /dev/null @@ -1,40 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->queueDeclare('task_queue', false, true, false, false)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data) { - echo " [x] Sending '{$data}'\n"; - return $channel->publish( - $data, - [ - 'delivery-mode' => 2 - ], - '', - 'task_queue' - )->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data) { - echo " [x] Sent '{$data}'\n"; - $client = $channel->getClient(); - return $channel->close()->then(function () use ($client) { - return $client; - }); -})->then(function (Client $client) { - $client->disconnect(); -}); - -$loop->run(); diff --git a/tutorial/2-work-queues/new_task.php b/tutorial/2-work-queues/new_task.php index 81ecbc3..16a70c6 100644 --- a/tutorial/2-work-queues/new_task.php +++ b/tutorial/2-work-queues/new_task.php @@ -2,9 +2,9 @@ use Bunny\Client; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->queueDeclare('task_queue', false, true, false, false); diff --git a/tutorial/2-work-queues/worker-async.php b/tutorial/2-work-queues/worker-async.php deleted file mode 100644 index edbda8e..0000000 --- a/tutorial/2-work-queues/worker-async.php +++ /dev/null @@ -1,35 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->qos(0, 1)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - return $channel->queueDeclare('task_queue', false, true, false, false)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - echo " [x] Received ", $message->content, "\n"; - sleep(substr_count($message->content, '.')); - echo " [x] Done", $message->content, "\n"; - $channel->ack($message); - }, - 'task_queue' - ); -}); - -$loop->run(); diff --git a/tutorial/2-work-queues/worker.php b/tutorial/2-work-queues/worker.php index 7db87d4..d90a49f 100644 --- a/tutorial/2-work-queues/worker.php +++ b/tutorial/2-work-queues/worker.php @@ -4,9 +4,9 @@ use Bunny\Client; use Bunny\Message; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->queueDeclare('task_queue', false, true, false, false); @@ -14,7 +14,7 @@ echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; $channel->qos(0, 1); -$channel->run( +$channel->consume( function (Message $message, Channel $channel, Client $client) { echo " [x] Received ", $message->content, "\n"; sleep(substr_count($message->content, '.')); diff --git a/tutorial/3-publish-subscribe/emit_log-async.php b/tutorial/3-publish-subscribe/emit_log-async.php deleted file mode 100644 index af476e1..0000000 --- a/tutorial/3-publish-subscribe/emit_log-async.php +++ /dev/null @@ -1,37 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->exchangeDeclare('logs', 'fanout')->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data) { - echo " [x] Sending '{$data}'\n"; - return $channel->publish($data, [], 'logs')->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data) { - echo " [x] Sent '{$data}'\n"; - $client = $channel->getClient(); - return $channel->close()->then(function () use ($client) { - return $client; - }); -})->then(function (Client $client) { - $client->disconnect(); -}); - -$loop->run(); diff --git a/tutorial/3-publish-subscribe/emit_log.php b/tutorial/3-publish-subscribe/emit_log.php index 64ed8c3..48b4199 100644 --- a/tutorial/3-publish-subscribe/emit_log.php +++ b/tutorial/3-publish-subscribe/emit_log.php @@ -2,9 +2,9 @@ use Bunny\Client; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->exchangeDeclare('logs', 'fanout'); diff --git a/tutorial/3-publish-subscribe/receive_logs-async.php b/tutorial/3-publish-subscribe/receive_logs-async.php deleted file mode 100644 index 81d45b2..0000000 --- a/tutorial/3-publish-subscribe/receive_logs-async.php +++ /dev/null @@ -1,36 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->exchangeDeclare('logs', 'fanout')->then(function () use ($channel) { - return $channel->queueDeclare('', false, false, true, false); - })->then(function (MethodQueueDeclareOkFrame $frame) use ($channel) { - return $channel->queueBind($frame->queue, 'logs')->then(function () use ($frame) { - return $frame; - }); - })->then(function (MethodQueueDeclareOkFrame $frame) use ($channel) { - echo ' [*] Waiting for logs. To exit press CTRL+C', "\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - echo ' [x] ', $message->content, "\n"; - }, - $frame->queue, - '', - false, - true - ); - }); -}); - -$loop->run(); diff --git a/tutorial/3-publish-subscribe/receive_logs.php b/tutorial/3-publish-subscribe/receive_logs.php index d696ac3..6a7ce2a 100644 --- a/tutorial/3-publish-subscribe/receive_logs.php +++ b/tutorial/3-publish-subscribe/receive_logs.php @@ -4,18 +4,18 @@ use Bunny\Client; use Bunny\Message; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->exchangeDeclare('logs', 'fanout'); $queue = $channel->queueDeclare('', false, false, true, false); -$channel->queueBind($queue->queue, 'logs'); +$channel->queueBind('logs', $queue->queue); echo ' [*] Waiting for logs. To exit press CTRL+C', "\n"; -$channel->run( +$channel->consume( function (Message $message, Channel $channel, Client $client) { echo ' [x] ', $message->content, "\n"; }, diff --git a/tutorial/4-routing/emit_log-async.php b/tutorial/4-routing/emit_log-async.php deleted file mode 100644 index 46767aa..0000000 --- a/tutorial/4-routing/emit_log-async.php +++ /dev/null @@ -1,38 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->exchangeDeclare('direct_logs', 'direct')->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data, $severity) { - echo " [x] Sending ",$severity,':',$data," \n"; - return $channel->publish($data, [], 'direct_logs', $severity)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data, $severity) { - echo " [x] Sent ",$severity,':',$data," \n"; - $client = $channel->getClient(); - return $channel->close()->then(function () use ($client) { - return $client; - }); -})->then(function (Client $client) { - $client->disconnect(); -}); - -$loop->run(); diff --git a/tutorial/4-routing/emit_log.php b/tutorial/4-routing/emit_log.php index 7fd9df9..f36f18a 100644 --- a/tutorial/4-routing/emit_log.php +++ b/tutorial/4-routing/emit_log.php @@ -2,9 +2,9 @@ use Bunny\Client; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->exchangeDeclare('direct_logs', 'direct'); diff --git a/tutorial/4-routing/receive_logs-async.php b/tutorial/4-routing/receive_logs-async.php deleted file mode 100644 index 2f0bdf7..0000000 --- a/tutorial/4-routing/receive_logs-async.php +++ /dev/null @@ -1,48 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) use ($severities) { - return $channel->exchangeDeclare('direct_logs', 'direct')->then(function () use ($channel, $severities) { - return $channel->queueDeclare('', false, false, true, false); - })->then(function (MethodQueueDeclareOkFrame $frame) use ($channel, $severities) { - $promises = []; - - foreach($severities as $severity) { - $promises[] = $channel->queueBind($frame->queue, 'direct_logs', $severity); - } - - return \React\Promise\all($promises)->then(function () use ($frame) { - return $frame; - }); - })->then(function (MethodQueueDeclareOkFrame $frame) use ($channel) { - echo ' [*] Waiting for logs. To exit press CTRL+C', "\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - echo ' [x] ', $message->content, "\n"; - }, - $frame->queue, - '', - false, - true - ); - }); -}); - -$loop->run(); diff --git a/tutorial/4-routing/receive_logs.php b/tutorial/4-routing/receive_logs.php index 184df66..4db1396 100644 --- a/tutorial/4-routing/receive_logs.php +++ b/tutorial/4-routing/receive_logs.php @@ -4,9 +4,9 @@ use Bunny\Client; use Bunny\Message; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->exchangeDeclare('direct_logs', 'direct'); @@ -15,16 +15,17 @@ $severities = array_slice($argv, 1); if(empty($severities )) { file_put_contents('php://stderr', "Usage: $argv[0] [info] [warning] [error]\n"); + $client->disconnect(); exit(1); } foreach($severities as $severity) { - $channel->queueBind($queue->queue, 'direct_logs', $severity); + $channel->queueBind('direct_logs', $queue->queue, $severity); } echo ' [*] Waiting for logs. To exit press CTRL+C', "\n"; -$channel->run( +$channel->consume( function (Message $message, Channel $channel, Client $client) { echo ' [x] ', $message->routingKey, ':', $message->content, "\n"; }, diff --git a/tutorial/5-topics/emit_log_topic-async.php b/tutorial/5-topics/emit_log_topic-async.php deleted file mode 100644 index 149b809..0000000 --- a/tutorial/5-topics/emit_log_topic-async.php +++ /dev/null @@ -1,38 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->exchangeDeclare('topic_logs', 'topic')->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data, $routing_key) { - echo " [x] Sending ", $routing_key, ':', $data, " \n"; - return $channel->publish($data, [], 'topic_logs', $routing_key)->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) use ($data, $routing_key) { - echo " [x] Sent ", $routing_key, ':', $data, " \n"; - $client = $channel->getClient(); - return $channel->close()->then(function () use ($client) { - return $client; - }); -})->then(function (Client $client) { - $client->disconnect(); -}); - -$loop->run(); diff --git a/tutorial/5-topics/emit_log_topic.php b/tutorial/5-topics/emit_log_topic.php index 09c3e26..3c08ac1 100644 --- a/tutorial/5-topics/emit_log_topic.php +++ b/tutorial/5-topics/emit_log_topic.php @@ -2,9 +2,9 @@ use Bunny\Client; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->exchangeDeclare('topic_logs', 'topic'); diff --git a/tutorial/5-topics/receive_logs_topic-async.php b/tutorial/5-topics/receive_logs_topic-async.php deleted file mode 100644 index 1e483b9..0000000 --- a/tutorial/5-topics/receive_logs_topic-async.php +++ /dev/null @@ -1,48 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) use ($binding_keys) { - return $channel->exchangeDeclare('topic_logs', 'topic')->then(function () use ($channel, $binding_keys) { - return $channel->queueDeclare('', false, false, true, false); - })->then(function (MethodQueueDeclareOkFrame $frame) use ($channel, $binding_keys) { - $promises = []; - - foreach($binding_keys as $binding_key) { - $promises[] = $channel->queueBind($frame->queue, 'topic_logs', $binding_key); - } - - return \React\Promise\all($promises)->then(function () use ($frame) { - return $frame; - }); - })->then(function (MethodQueueDeclareOkFrame $frame) use ($channel) { - echo ' [*] Waiting for logs. To exit press CTRL+C', "\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - echo ' [x] ', $message->content, "\n"; - }, - $frame->queue, - '', - false, - true - ); - }); -}); - -$loop->run(); diff --git a/tutorial/5-topics/receive_logs_topic.php b/tutorial/5-topics/receive_logs_topic.php index 958f546..ba8f42f 100644 --- a/tutorial/5-topics/receive_logs_topic.php +++ b/tutorial/5-topics/receive_logs_topic.php @@ -4,9 +4,9 @@ use Bunny\Client; use Bunny\Message; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->exchangeDeclare('topic_logs', 'topic'); @@ -15,16 +15,17 @@ $binding_keys = array_slice($argv, 1); if(empty($binding_keys )) { file_put_contents('php://stderr', "Usage: $argv[0] [binding_key]\n"); + $client->disconnect(); exit(1); } foreach($binding_keys as $binding_key) { - $channel->queueBind($queue->queue, 'topic_logs', $binding_key); + $channel->queueBind('topic_logs', $queue->queue, $binding_key); } echo ' [*] Waiting for logs. To exit press CTRL+C', "\n"; -$channel->run( +$channel->consume( function (Message $message, Channel $channel, Client $client) { echo ' [x] ', $message->routingKey, ':', $message->content, "\n"; }, diff --git a/tutorial/6-rpc/rpc_client-async.php b/tutorial/6-rpc/rpc_client-async.php deleted file mode 100644 index feb9173..0000000 --- a/tutorial/6-rpc/rpc_client-async.php +++ /dev/null @@ -1,65 +0,0 @@ -channel = (new Client($loop))->connect()->then(function (Client $client) { - return $client->channel(); - }); - } - - public function call($n) - { - return $this->channel->then(function (Channel $channel) { - return \React\Promise\all([ - $channel->queueDeclare('', false, false, true), - \React\Promise\resolve($channel), - ]); - })->then(function ($values) use ($n) { - list ($responseQueue, $channel) = $values; - $corr_id = uniqid(); - $deferred = new Deferred(); - $channel->consume( - function (Message $message, Channel $channel, Client $client) use ($deferred, $corr_id) { - if ($message->getHeader('correlation_id') != $corr_id) { - return; - } - $deferred->resolve((int)$message->content); - $client->disconnect(); - }, - $responseQueue->queue - ); - $channel->publish( - $n, - [ - 'correlation_id' => $corr_id, - 'reply_to' => $responseQueue->queue, - ], - '', - 'rpc_queue' - ); - return $deferred->promise(); - }); - } -} - -$fibonacci_rpc = new FibonacciRpcClient($loop); -$response = $fibonacci_rpc->call(30)->then(function ($n) { - echo " [.] Got ", $n, "\n"; -}); - -$loop->run(); diff --git a/tutorial/6-rpc/rpc_client.php b/tutorial/6-rpc/rpc_client.php index debb418..5b2b433 100644 --- a/tutorial/6-rpc/rpc_client.php +++ b/tutorial/6-rpc/rpc_client.php @@ -3,8 +3,12 @@ use Bunny\Channel; use Bunny\Client; use Bunny\Message; +use React\EventLoop\Loop; +use React\Promise\Deferred; +use function React\Async\async; +use function React\Async\await; -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; class FibonacciRpcClient { @@ -13,21 +17,27 @@ class FibonacciRpcClient public function __construct() { - $this->client = (new Client())->connect(); + $this->client = new Client(); $this->channel = $this->client->channel(); } + public function close() + { + $this->client->disconnect(); + } + public function call($n) { $corr_id = uniqid(); - $response = null; + $response = new Deferred(); $responseQueue = $this->channel->queueDeclare('', false, false, true); - $this->channel->consume( - function (Message $message, Channel $channel, Client $client) use (&$response, $corr_id) { + $subscription = $this->channel->consume( + function (Message $message, Channel $channel, Client $client) use (&$response, $corr_id, &$subscription) { if ($message->getHeader('correlation_id') != $corr_id) { return; } - $response = $message->content; + $response->resolve($message->content); + $channel->cancel($subscription->consumerTag); }, $responseQueue->queue ); @@ -40,13 +50,12 @@ function (Message $message, Channel $channel, Client $client) use (&$response, $ '', 'rpc_queue' ); - while ($response === null) { - $this->client->run(0.01); - } - return (int) $response; + + return (int) await($response->promise()); } } $fibonacci_rpc = new FibonacciRpcClient(); $response = $fibonacci_rpc->call(30); echo " [.] Got ", $response, "\n"; +$fibonacci_rpc->close(); diff --git a/tutorial/6-rpc/rpc_server-async.php b/tutorial/6-rpc/rpc_server-async.php deleted file mode 100644 index 4d8de0a..0000000 --- a/tutorial/6-rpc/rpc_server-async.php +++ /dev/null @@ -1,48 +0,0 @@ -connect()->then(function (Client $client) { - return $client->channel(); -})->then(function (Channel $channel) { - return $channel->queueDeclare('rpc_queue')->then(function () use ($channel) { - return $channel; - }); -})->then(function (Channel $channel) { - echo " [x] Awaiting RPC requests\n"; - $channel->consume( - function (Message $message, Channel $channel, Client $client) { - $n = intval($message->content); - echo " [.] fib(", $n, ")\n"; - $channel->publish( - (string) fib($n), - [ - 'correlation_id' => $message->getHeader('correlation_id'), - ], - '', - $message->getHeader('reply_to') - )->then(function () use ($channel, $message) { - $channel->ack($message); - }); - }, - 'rpc_queue' - ); -}); - -$loop->run(); diff --git a/tutorial/6-rpc/rpc_server.php b/tutorial/6-rpc/rpc_server.php index 3fb83ce..0828e31 100644 --- a/tutorial/6-rpc/rpc_server.php +++ b/tutorial/6-rpc/rpc_server.php @@ -3,6 +3,8 @@ use Bunny\Channel; use Bunny\Client; use Bunny\Message; +use React\EventLoop\Loop; +use function React\Async\async; function fib($n) { if ($n == 0) @@ -12,21 +14,21 @@ function fib($n) { return fib($n-1) + fib($n-2); } -require '../../vendor/autoload.php'; +require dirname(__DIR__, 2) . '/vendor/autoload.php'; -$client = (new Client())->connect(); +$client = new Client(); $channel = $client->channel(); $channel->queueDeclare('rpc_queue'); echo " [x] Awaiting RPC requests\n"; -$channel->run( +$channel->consume( function (Message $message, Channel $channel, Client $client) { $n = intval($message->content); echo " [.] fib(", $n, ")\n"; $channel->publish( - (string) fib($n), + (string)fib($n), [ 'correlation_id' => $message->getHeader('correlation_id'), ],