https://github.com/thesis-php/pgmq
A non-blocking php client for Postgres Message Queue (PGMQ)
https://github.com/thesis-php/pgmq
async php8 postgresql queue
Last synced: 5 months ago
JSON representation
A non-blocking php client for Postgres Message Queue (PGMQ)
- Host: GitHub
- URL: https://github.com/thesis-php/pgmq
- Owner: thesis-php
- License: mit
- Created: 2025-11-16T12:49:48.000Z (7 months ago)
- Default Branch: 0.1.x
- Last Pushed: 2025-12-21T14:41:01.000Z (5 months ago)
- Last Synced: 2025-12-23T05:08:14.352Z (5 months ago)
- Topics: async, php8, postgresql, queue
- Language: PHP
- Homepage:
- Size: 141 KB
- Stars: 3
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# pgmq
Non-blocking php client for [pgmq](https://github.com/pgmq/pgmq). See the extension [installation guide](https://github.com/pgmq/pgmq/blob/main/INSTALLATION.md).
## Installation
```shell
composer require thesis/pgmq
```
## Why is almost all the API functional?
Since you most likely expect exactly-once semantics from a database-based queue, all requests — sending or processing business logic with message acknowledgments — must be transactional.
And the transaction object is short-lived: it cannot be used after `rollback()` or `commit()`, so it cannot be made a dependency.
That's why all the API is built on functions that take `Amp\Postgres\PostgresLink` as their first parameter, which can be either a transaction object or just a connection.
And only the consumer accepts `Amp\Postgres\PostgresConnection`, because it itself opens transactions for reading and acknowledging messages transactionally.
## Contents
- [Create queue](#create-queue)
- [Create unlogged queue](#create-unlogged-queue)
- [Create partitioned queue](#create-partitioned-queue)
- [List queues](#list-queues)
- [List queue metrics](#list-queue-metrics)
- [List queue metadata](#list-queue-metadata)
- [Drop queue](#drop-queue)
- [Purge queue](#purge-queue)
- [Send message](#send-message)
- [Send message with relative delay](#send-message-with-relative-delay)
- [Send message with absolute delay](#send-message-with-absolute-delay)
- [Send batch](#send-batch)
- [Send batch with relative delay](#send-batch-with-relative-delay)
- [Send batch with absolute delay](#send-batch-with-absolute-delay)
- [Read message](#read-message)
- [Read batch](#read-batch)
- [Pop message](#pop-message)
- [Read batch with poll](#read-batch-with-poll)
- [Set visibility timeout](#set-visibility-timeout)
- [Archive message](#archive-message)
- [Archive batch](#archive-batch)
- [Delete message](#delete-message)
- [Delete batch](#delete-batch)
- [Enable notify insert](#enable-notify-insert)
- [Disable notify insert](#disable-notify-insert)
- [Consume messages](#consume-messages)
### Create queue
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
```
### Create unlogged queue
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createUnloggedQueue($pg, 'events');
```
### Create partitioned queue
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createPartitionedQueue(
pg: $pg,
queue: 'events',
partitionInterval: 10000,
retentionInterval: 100000,
);
```
### List queues
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
foreach (Pgmq\listQueues($pg) as $queue) {
$md = $queue->metadata();
var_dump($md);
}
```
### List queue metrics
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
foreach (Pgmq\metrics($pg) as $metrics) {
var_dump($metrics);
}
```
### List queue metadata
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
foreach (Pgmq\listQueueMetadata($pg) as $md) {
var_dump($md);
}
```
### Drop queue
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$queue->drop();
```
### Purge queue
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
var_dump($queue->purge());
```
### Send message
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messageId = $queue->send(new Pgmq\SendMessage('{"id": 1}', '{"x-header": "x-value"}'));
```
### Send message with relative delay
```php
use Thesis\Pgmq;
use Amp\Postgres;
use Thesis\Time\TimeSpan;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messageId = $queue->send(
new Pgmq\SendMessage('{"id": 1}', '{"x-header": "x-value"}'),
TimeSpan::fromSeconds(5),
);
```
### Send message with absolute delay
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messageId = $queue->send(
new Pgmq\SendMessage('{"id": 1}', '{"x-header": "x-value"}'),
new \DateTimeImmutable('+5 seconds'),
);
```
### Send batch
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messageIds = $queue->sendBatch([
new Pgmq\SendMessage('{"id": 1}', '{"x-header": "x-value"}'),
new Pgmq\SendMessage('{"id": 2}', '{"x-header": "x-value"}'),
]);
```
### Send batch with relative delay
```php
use Thesis\Pgmq;
use Amp\Postgres;
use Thesis\Time\TimeSpan;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messageIds = $queue->sendBatch(
[
new Pgmq\SendMessage('{"id": 1}', '{"x-header": "x-value"}'),
new Pgmq\SendMessage('{"id": 2}', '{"x-header": "x-value"}'),
],
TimeSpan::fromSeconds(5),
);
```
### Send batch with absolute delay
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messageIds = $queue->sendBatch(
[
new Pgmq\SendMessage('{"id": 1}', '{"x-header": "x-value"}'),
new Pgmq\SendMessage('{"id": 2}', '{"x-header": "x-value"}'),
],
new \DateTimeImmutable('+5 seconds'),
);
```
### Read message
```php
use Thesis\Pgmq;
use Amp\Postgres;
use Thesis\Time\TimeSpan;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$message = $queue->read(TimeSpan::fromSeconds(20));
```
### Read batch
```php
use Thesis\Pgmq;
use Amp\Postgres;
use Thesis\Time\TimeSpan;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$message = $queue->readBatch(10, TimeSpan::fromSeconds(20));
```
### Pop message
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$message = $queue->pop();
```
### Read batch with poll
```php
use Thesis\Pgmq;
use Amp\Postgres;
use Thesis\Time\TimeSpan;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messages = $queue->readPoll(
batch: 10,
maxPoll: TimeSpan::fromSeconds(5),
pollInterval: TimeSpan::fromMilliseconds(250),
);
```
### Set visibility timeout
```php
use Thesis\Pgmq;
use Amp\Postgres;
use Thesis\Time\TimeSpan;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$message = $queue->read();
if ($message !== null) {
// handle the message
$queue->setVisibilityTimeout($message->id, TimeSpan::fromSeconds(10));
}
```
### Archive message
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$message = $queue->read();
if ($message !== null) {
$queue->archive($message->id);
}
```
### Archive batch
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messages = [...$queue->readBatch(5)];
if ($messages !== []) {
$queue->archiveBatch(array_map(
static fn(Pgmq\Message $message): int => $messages->id),
$messages,
);
}
```
### Delete message
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$message = $queue->read();
if ($message !== null) {
$queue->delete($message->id);
}
```
### Delete batch
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$messages = [...$queue->readBatch(5)];
if ($messages !== []) {
$queue->deleteBatch(array_map(
static fn(Pgmq\Message $message): int => $messages->id),
$messages,
);
}
```
### Enable notify insert
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$channel = $queue->enableNotifyInsert(); // postgres channel to listen is returned
```
### Disable notify insert
```php
use Thesis\Pgmq;
use Amp\Postgres;
$pg = new Postgres\PostgresConnectionPool(Postgres\PostgresConfig::fromString(''));
$queue = Pgmq\createQueue($pg, 'events');
$queue->disableNotifyInsert();
```
### Consume messages
This functionality is not a standard feature of the **pgmq** extension, but is provided by the library as an add-on for reliable and correct processing of message batches from the queue, with the ability to `ack`, `nack` (with delay) and archive (`term`) messages from the queue.
1. First of all, create the extension if it doesn't exist yet:
```php
use Thesis\Pgmq;
Pgmq\createExtension($pg);
```
2. Then create a queue:
```php
use Thesis\Pgmq;
Pgmq\createExtension($pg);
Pgmq\createQueue($pg, 'events');
```
3. Next, create the consumer object:
```php
use Thesis\Pgmq;
Pgmq\createExtension($pg);
Pgmq\createQueue($pg, 'events');
$consumer = Pgmq\createConsumer($pg);
```
4. Now we can proceed to configure the queue consumer handler:
```php
use Thesis\Pgmq;
Pgmq\createExtension($pg);
Pgmq\createQueue($pg, 'events');
$consumer = Pgmq\createConsumer($pg);
$context = $consumer->consume(
static function (array $messages, Pgmq\ConsumeController $ctrl): void {
var_dump($messages);
$ctrl->ack($messages);
},
new Pgmq\ConsumeConfig(
queue: 'events',
),
);
```
Through `Pgmq\ConsumeConfig` you can configure:
- the `batch` size of received messages;
- the message visibility timeout;
- enable monitoring for queue inserts via the LISTEN/NOTIFY mechanism;
- and set the polling interval.
At least one of these settings — `listenForInserts` or `pollTimeout` — must be specified.
Through the `Pgmq\ConsumeController`, you can:
- ack messages, causing them to be deleted from the queue;
- nack messages with a delay, setting a visibility timeout for them;
- terminate processing (when a message can no longer be retried), resulting in them being archived;
- stop the consumer.
Since receiving messages and `acking/nacking` them occur within the same transaction, for your own database queries you must use the `ConsumeController::$tx` object to ensure exactly-once semantics for message processing.
```php
use Thesis\Pgmq;
Pgmq\createExtension($pg);
Pgmq\createQueue($pg, 'events');
$consumer = Pgmq\createConsumer($pg);
$context = $consumer->consume(
static function (array $messages, Pgmq\ConsumeController $ctrl): void {
$ctrl->tx->execute('...some business logic');
$ctrl->ack($messages);
},
new Pgmq\ConsumeConfig(
queue: 'events',
),
);
```
Using `ConsumeContext`, you can gracefully stop the consumer, waiting for the current batch to finish processing.
```php
use Thesis\Pgmq;
use function Amp\trapSignal;
Pgmq\createExtension($pg);
Pgmq\createQueue($pg, 'events');
$consumer = Pgmq\createConsumer($pg);
$context = $consumer->consume(
static function (array $messages, Pgmq\ConsumeController $ctrl): void {
$ctrl->tx->execute('...some business logic');
$ctrl->ack($messages);
},
new Pgmq\ConsumeConfig(
queue: 'events',
),
);
trapSignal([\SIGINT, \SIGTERM])
$context->stop();
$context->awaitCompletion();
```
Or stop all current consumers using `$consumer->stop()`:
```php
use Thesis\Pgmq;
use function Amp\trapSignal;
Pgmq\createExtension($pg);
Pgmq\createQueue($pg, 'events');
$consumer = Pgmq\createConsumer($pg);
$context = $consumer->consume(...);
trapSignal([\SIGINT, \SIGTERM])
$consumer->stop();
$context->awaitCompletion();
```
## License
The MIT License (MIT). Please see [License File](LICENSE) for more information.