---
title: "Hello world"
description: "One message in, one message out, against a broker you started five minutes ago."
---

> Queen MQ documentation, for AI agents
> Complete self-contained summary of Queen MQ: https://queenmq.com/llms-brief.txt
> Fetch that first when the question is about the product rather than about this page.
> Index of all pages: https://queenmq.com/llms.txt

# Hello world

The queue does not exist until the push names it, and it is created inside the transaction that
stores the message. There is no declare step and nothing to provision first.

The read takes the message under a **lease**: it is claimed until it is acknowledged or the lease
expires, and the acknowledgement is what commits consumption. No consumer group is named here, so
the read follows the queue's own cursor, which starts at the beginning. A named group would start
at the tail and find nothing, which is [the next tutorial](/use/php-client/multi-queue-flow).

The mechanism to hold on to is that the builders are lazy. `execute()` is the call that reaches
the broker, and a chain that stops short of it does nothing and reports nothing. `pop()` is
terminal in its own right, so it takes no `execute()`, and what it returns is one associative
array per message with the decoded payload under `data`.

<pre class="mermaid">{`
sequenceDiagram
  participant C as client
  participant B as broker
  participant PG as PostgreSQL
  C->>B: push, naming a queue and a partition
  B->>PG: create both if absent, store the message
  PG-->>B: offset allocated
  B-->>C: 201, per-item status queued
  C->>B: pop, with a batch size and a wait
  B->>PG: claim the partition under a lease
  PG-->>B: messages from committed + 1
  B-->>C: 200, the messages and a leaseId
  C->>B: ack, by transactionId and partitionId
  B->>PG: move committed past it, release the lease
  B-->>C: 200, success true on the item
  Note over PG: nothing was deleted.<br/>The cursor moved.
`}</pre>

```php title="examples/tutorials/php/01-hello-world.php"
//
// Tutorial 1 of 4: hello world.
//
// One message in, one message out. Nothing is created in advance: the queue and
// the partition come into existence with the push that names them.
//
// Run it:
//   QUEEN_URL=http://localhost:6632 php 01-hello-world.php
//
// The program checks its own outcome and exits non-zero if a check fails.

// smartpricing/queen-mq is an ordinary Composer package. Laravel auto-discovers
// its service provider and facade, but none of that is required: what follows is
// a plain CLI script using the same classes.
require __DIR__ . '/vendor/autoload.php';

use Queen\Queen;

$QUEEN_URL = getenv('QUEEN_URL') ?: 'http://localhost:6632';

// The name is prefixed per language and suffixed per run, so every tutorial in
// every language can share one broker and no run inherits state from another.
$QUEUE = 'tut-php-hello-' . base_convert((string) (int) (microtime(true) * 1000), 10, 36);

$checks = 0;
$assert = function (bool $condition, string $description) use (&$checks): void {
    if (!$condition) {
        throw new RuntimeException($description);
    }
    $checks++;
    echo "  ok: {$description}\n";
};

// The constructor takes a URL string, a list of URLs, or a config array. There
// is no signal-handling option to turn off: the PHP client only installs SIGINT
// and SIGTERM handlers for the duration of a consume loop, and restores the
// previous ones when it returns, so this script keeps control of its shutdown.
$queen = new Queen($QUEEN_URL);
$exitCode = 0;

try {
    echo "broker {$QUEEN_URL}\n";

    // A push names a queue and, optionally, a partition. Both are created by
    // this call if they do not exist, inside the transaction that stores the
    // message. There is no declare step and nothing to provision first.
    //
    // Every builder in this client is lazy: execute() is the call that talks to
    // the broker, and it returns one result row per item pushed.
    $results = $queen
        ->queue($QUEUE)
        ->push([['data' => ['greeting' => 'Hello World!']]])
        ->execute();

    $pushed = $results[0];
    echo "pushed {$pushed['transaction_id']} -> {$pushed['status']}\n";
    $assert($pushed['status'] === 'queued', 'the broker stored the message');

    // pop() takes messages under a lease: they are claimed until they are
    // acknowledged or the lease expires. wait(true) turns on long polling, so
    // the call parks until a message arrives instead of coming back empty.
    // pop() is itself terminal, so there is no execute() here.
    //
    // No consumer group is named here, so the read goes through the queue's own
    // cursor, which starts at the beginning. Named groups are tutorial 2: a
    // group created after a message was pushed starts at the tail and would see
    // nothing here.
    $messages = $queen
        ->queue($QUEUE)
        ->batch(1)
        ->wait(true)
        ->pop();

    $assert(count($messages) === 1, 'one message came back');
    $message = $messages[0];

    // A message is an associative array, not an object: the decoded payload is
    // under 'data', and the routing and lease fields sit beside it.
    echo "received \"{$message['data']['greeting']}\" from partition {$message['partition']}\n";
    $assert($message['data']['greeting'] === 'Hello World!', 'the payload survived the round trip');

    // No partition was named on the push, so the broker put the message in the
    // queue's default lane.
    $assert($message['partition'] === 'Default', 'it landed in the default partition');

    // The acknowledgement is what commits consumption. It moves the cursor past
    // the message and releases the lease. A rejected ack still arrives as HTTP
    // 200 with success: false on the item, and this client never throws for it,
    // so a per-item flag is the only proof the broker took it.
    //
    // ack() returns that reply under an envelope of its own: the outer
    // 'success' is set before the response is read and says only that the call
    // did not raise, while the numbered rows beneath it carry the broker's
    // verdict, one per acknowledgement. Asserting on the outer flag would pass
    // just as happily on a refusal, so read the row.
    $ack = $queen->ack($message, true);
    $assert(($ack[0]['success'] ?? false) === true, 'the acknowledgement was accepted');

    // The cursor is now past the only message, so a further read finds nothing.
    // wait(false) returns immediately instead of long polling.
    $leftovers = $queen->queue($QUEUE)->wait(false)->pop();
    $assert(count($leftovers) === 0, 'the queue is drained');

    // Clean up on success only: a failed run leaves the queue on the broker to
    // be looked at.
    $queen->queue($QUEUE)->delete()->execute();

    echo "\nPASS: {$checks} checks\n";
} catch (Throwable $error) {
    fwrite(STDERR, "\nFAIL: " . $error->getMessage() . "\n");
    $exitCode = 1;
} finally {
    // close() flushes anything still sitting in a client-side push buffer. PHP
    // has no event loop to release, so unlike the Node client it is not what
    // lets the process exit, but it is still what makes a buffered push land.
    $queen->close();
}

exit($exitCode);
```

## Run it

Against a broker from [the quickstart](/start/quickstart), from `examples/tutorials/php` with the
PHP client installed:

```bash
QUEEN_URL=http://localhost:6632 php 01-hello-world.php
```

The program checks its own outcome and exits non-zero if a check fails. Every tutorial in this
section runs in the repository's own suite: `examples/tutorials/run.sh php`.

Next: [Multi-queue flow](/use/php-client/multi-queue-flow).

Source: https://queenmq.com/use/php-client/hello-world/index.mdx
