{"id":36981736,"url":"https://github.com/thesis-php/pgmq","last_synced_at":"2026-01-13T22:51:36.402Z","repository":{"id":324749493,"uuid":"1097573299","full_name":"thesis-php/pgmq","owner":"thesis-php","description":"A non-blocking php client for Postgres Message Queue (PGMQ)","archived":false,"fork":false,"pushed_at":"2025-12-21T14:41:01.000Z","size":144,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"0.1.x","last_synced_at":"2025-12-23T05:08:14.352Z","etag":null,"topics":["async","php8","postgresql","queue"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thesis-php.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://www.tinkoff.ru/cf/5MqZQas2dk7"]}},"created_at":"2025-11-16T12:49:48.000Z","updated_at":"2025-12-21T14:40:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thesis-php/pgmq","commit_stats":null,"previous_names":["thesis-php/pgmq"],"tags_count":5,"template":false,"template_full_name":"thesis-php/template","purl":"pkg:github/thesis-php/pgmq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesis-php%2Fpgmq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesis-php%2Fpgmq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesis-php%2Fpgmq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesis-php%2Fpgmq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thesis-php","download_url":"https://codeload.github.com/thesis-php/pgmq/tar.gz/refs/heads/0.1.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thesis-php%2Fpgmq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28402160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","php8","postgresql","queue"],"created_at":"2026-01-13T22:51:35.579Z","updated_at":"2026-01-13T22:51:36.388Z","avatar_url":"https://github.com/thesis-php.png","language":"PHP","funding_links":["https://www.tinkoff.ru/cf/5MqZQas2dk7"],"categories":[],"sub_categories":[],"readme":"# pgmq\n\nNon-blocking php client for [pgmq](https://github.com/pgmq/pgmq). See the extension [installation guide](https://github.com/pgmq/pgmq/blob/main/INSTALLATION.md).\n\n## Installation\n\n```shell\ncomposer require thesis/pgmq\n```\n\n## Why is almost all the API functional?\n\nSince 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.\nAnd the transaction object is short-lived: it cannot be used after `rollback()` or `commit()`, so it cannot be made a dependency.\nThat'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.\nAnd only the consumer accepts `Amp\\Postgres\\PostgresConnection`, because it itself opens transactions for reading and acknowledging messages transactionally.\n\n## Contents\n - [Create queue](#create-queue)\n - [Create unlogged queue](#create-unlogged-queue)\n - [Create partitioned queue](#create-partitioned-queue)\n - [List queues](#list-queues)\n - [List queue metrics](#list-queue-metrics)\n - [List queue metadata](#list-queue-metadata)\n - [Drop queue](#drop-queue)\n - [Purge queue](#purge-queue)\n - [Send message](#send-message)\n - [Send message with relative delay](#send-message-with-relative-delay)\n - [Send message with absolute delay](#send-message-with-absolute-delay)\n - [Send batch](#send-batch)\n - [Send batch with relative delay](#send-batch-with-relative-delay)\n - [Send batch with absolute delay](#send-batch-with-absolute-delay)\n - [Read message](#read-message)\n - [Read batch](#read-batch)\n - [Pop message](#pop-message)\n - [Read batch with poll](#read-batch-with-poll)\n - [Set visibility timeout](#set-visibility-timeout)\n - [Archive message](#archive-message)\n - [Archive batch](#archive-batch)\n - [Delete message](#delete-message)\n - [Delete batch](#delete-batch)\n - [Enable notify insert](#enable-notify-insert)\n - [Disable notify insert](#disable-notify-insert)\n - [Consume messages](#consume-messages)\n\n### Create queue\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n```\n\n### Create unlogged queue\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createUnloggedQueue($pg, 'events');\n```\n\n### Create partitioned queue\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createPartitionedQueue(\n    pg: $pg,\n    queue: 'events',\n    partitionInterval: 10000,\n    retentionInterval: 100000,\n);\n```\n\n### List queues\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\nforeach (Pgmq\\listQueues($pg) as $queue) {\n    $md = $queue-\u003emetadata();\n    var_dump($md);\n}\n```\n\n### List queue metrics\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\nforeach (Pgmq\\metrics($pg) as $metrics) {\n    var_dump($metrics);\n}\n```\n\n### List queue metadata\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\nforeach (Pgmq\\listQueueMetadata($pg) as $md) {\n    var_dump($md);\n}\n```\n\n### Drop queue\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$queue-\u003edrop();\n```\n\n### Purge queue\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\nvar_dump($queue-\u003epurge());\n```\n\n### Send message\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messageId = $queue-\u003esend(new Pgmq\\SendMessage('{\"id\": 1}', '{\"x-header\": \"x-value\"}'));\n```\n\n### Send message with relative delay\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\nuse Thesis\\Time\\TimeSpan;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messageId = $queue-\u003esend(\n    new Pgmq\\SendMessage('{\"id\": 1}', '{\"x-header\": \"x-value\"}'),\n    TimeSpan::fromSeconds(5),\n);\n```\n\n### Send message with absolute delay\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messageId = $queue-\u003esend(\n    new Pgmq\\SendMessage('{\"id\": 1}', '{\"x-header\": \"x-value\"}'),\n    new \\DateTimeImmutable('+5 seconds'),\n);\n```\n\n### Send batch\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messageIds = $queue-\u003esendBatch([\n    new Pgmq\\SendMessage('{\"id\": 1}', '{\"x-header\": \"x-value\"}'),\n    new Pgmq\\SendMessage('{\"id\": 2}', '{\"x-header\": \"x-value\"}'),\n]);\n```\n\n### Send batch with relative delay\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\nuse Thesis\\Time\\TimeSpan;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messageIds = $queue-\u003esendBatch(\n    [\n        new Pgmq\\SendMessage('{\"id\": 1}', '{\"x-header\": \"x-value\"}'),\n        new Pgmq\\SendMessage('{\"id\": 2}', '{\"x-header\": \"x-value\"}'),\n    ],\n    TimeSpan::fromSeconds(5),\n);\n```\n\n### Send batch with absolute delay\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messageIds = $queue-\u003esendBatch(\n    [\n        new Pgmq\\SendMessage('{\"id\": 1}', '{\"x-header\": \"x-value\"}'),\n        new Pgmq\\SendMessage('{\"id\": 2}', '{\"x-header\": \"x-value\"}'),\n    ],\n    new \\DateTimeImmutable('+5 seconds'),\n);\n```\n\n### Read message\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\nuse Thesis\\Time\\TimeSpan;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$message = $queue-\u003eread(TimeSpan::fromSeconds(20));\n```\n\n### Read batch\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\nuse Thesis\\Time\\TimeSpan;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$message = $queue-\u003ereadBatch(10, TimeSpan::fromSeconds(20));\n```\n\n### Pop message\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$message = $queue-\u003epop();\n```\n\n### Read batch with poll\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\nuse Thesis\\Time\\TimeSpan;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messages = $queue-\u003ereadPoll(\n    batch: 10,\n    maxPoll: TimeSpan::fromSeconds(5),\n    pollInterval: TimeSpan::fromMilliseconds(250),\n);\n```\n\n### Set visibility timeout\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\nuse Thesis\\Time\\TimeSpan;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$message = $queue-\u003eread();\n\nif ($message !== null) {\n    // handle the message\n\n    $queue-\u003esetVisibilityTimeout($message-\u003eid, TimeSpan::fromSeconds(10));\n}\n```\n\n### Archive message\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$message = $queue-\u003eread();\n\nif ($message !== null) {\n    $queue-\u003earchive($message-\u003eid);\n}\n```\n\n### Archive batch\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messages = [...$queue-\u003ereadBatch(5)];\n\nif ($messages !== []) {\n    $queue-\u003earchiveBatch(array_map(\n        static fn(Pgmq\\Message $message): int =\u003e $messages-\u003eid),\n        $messages,\n    );\n}\n```\n\n### Delete message\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$message = $queue-\u003eread();\n\nif ($message !== null) {\n    $queue-\u003edelete($message-\u003eid);\n}\n```\n\n### Delete batch\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$messages = [...$queue-\u003ereadBatch(5)];\n\nif ($messages !== []) {\n    $queue-\u003edeleteBatch(array_map(\n        static fn(Pgmq\\Message $message): int =\u003e $messages-\u003eid),\n        $messages,\n    );\n}\n```\n\n### Enable notify insert\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$channel = $queue-\u003eenableNotifyInsert(); // postgres channel to listen is returned\n```\n\n### Disable notify insert\n\n```php\nuse Thesis\\Pgmq;\nuse Amp\\Postgres;\n\n$pg = new Postgres\\PostgresConnectionPool(Postgres\\PostgresConfig::fromString(''));\n\n$queue = Pgmq\\createQueue($pg, 'events');\n$queue-\u003edisableNotifyInsert();\n```\n\n### Consume messages\n\nThis 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.\n\n1. First of all, create the extension if it doesn't exist yet:\n\n```php\nuse Thesis\\Pgmq;\n\nPgmq\\createExtension($pg);\n```\n\n2. Then create a queue:\n\n```php\nuse Thesis\\Pgmq;\n\nPgmq\\createExtension($pg);\nPgmq\\createQueue($pg, 'events');\n```\n\n3. Next, create the consumer object:\n\n```php\nuse Thesis\\Pgmq;\n\nPgmq\\createExtension($pg);\nPgmq\\createQueue($pg, 'events');\n\n$consumer = Pgmq\\createConsumer($pg);\n```\n\n4. Now we can proceed to configure the queue consumer handler:\n\n```php\nuse Thesis\\Pgmq;\n\nPgmq\\createExtension($pg);\nPgmq\\createQueue($pg, 'events');\n\n$consumer = Pgmq\\createConsumer($pg);\n\n$context = $consumer-\u003econsume(\n    static function (array $messages, Pgmq\\ConsumeController $ctrl): void {\n        var_dump($messages);\n        $ctrl-\u003eack($messages);\n    },\n    new Pgmq\\ConsumeConfig(\n        queue: 'events',\n    ),\n);\n```\n\nThrough `Pgmq\\ConsumeConfig` you can configure:\n\n- the `batch` size of received messages;\n- the message visibility timeout;\n- enable monitoring for queue inserts via the LISTEN/NOTIFY mechanism;\n- and set the polling interval.\n\nAt least one of these settings — `listenForInserts` or `pollTimeout` — must be specified.\n\nThrough the `Pgmq\\ConsumeController`, you can:\n- ack messages, causing them to be deleted from the queue;\n- nack messages with a delay, setting a visibility timeout for them;\n- terminate processing (when a message can no longer be retried), resulting in them being archived;\n- stop the consumer.\n\nSince 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.\n\n```php\nuse Thesis\\Pgmq;\n\nPgmq\\createExtension($pg);\nPgmq\\createQueue($pg, 'events');\n\n$consumer = Pgmq\\createConsumer($pg);\n\n$context = $consumer-\u003econsume(\n    static function (array $messages, Pgmq\\ConsumeController $ctrl): void {\n        $ctrl-\u003etx-\u003eexecute('...some business logic');\n        $ctrl-\u003eack($messages);\n    },\n    new Pgmq\\ConsumeConfig(\n        queue: 'events',\n    ),\n);\n```\n\nUsing `ConsumeContext`, you can gracefully stop the consumer, waiting for the current batch to finish processing.\n\n\n```php\nuse Thesis\\Pgmq;\nuse function Amp\\trapSignal;\n\nPgmq\\createExtension($pg);\nPgmq\\createQueue($pg, 'events');\n\n$consumer = Pgmq\\createConsumer($pg);\n\n$context = $consumer-\u003econsume(\n    static function (array $messages, Pgmq\\ConsumeController $ctrl): void {\n        $ctrl-\u003etx-\u003eexecute('...some business logic');\n        $ctrl-\u003eack($messages);\n    },\n    new Pgmq\\ConsumeConfig(\n        queue: 'events',\n    ),\n);\n\ntrapSignal([\\SIGINT, \\SIGTERM])\n\n$context-\u003estop();\n$context-\u003eawaitCompletion();\n```\n\nOr stop all current consumers using `$consumer-\u003estop()`:\n\n```php\nuse Thesis\\Pgmq;\nuse function Amp\\trapSignal;\n\nPgmq\\createExtension($pg);\nPgmq\\createQueue($pg, 'events');\n\n$consumer = Pgmq\\createConsumer($pg);\n\n$context = $consumer-\u003econsume(...);\n\ntrapSignal([\\SIGINT, \\SIGTERM])\n\n$consumer-\u003estop();\n$context-\u003eawaitCompletion();\n```\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthesis-php%2Fpgmq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthesis-php%2Fpgmq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthesis-php%2Fpgmq/lists"}