phalcon/cphalcon · error · Phalcon\Acl\Exceptions\InvalidSnapshot

Incompatible ACL snapshot version

Error message

Incompatible ACL snapshot version '{version}'; expected '{SNAPSHOT_VERSION}'

What it means

QueueDestinationGuard::assertQueue() enforces a single rule shared by producers (send) and contexts (createConsumer): the destination must implement Phalcon\Contracts\Queue\Queue. Any other DestinationInterface implementation — notably a Topic — triggers InvalidDestinationException, with the {action} slot in 'This transport can only {action} a Queue destination' telling you whether the send path or the consume path rejected it.

Solutions

  1. Create the destination with createQueue('name') instead of createTopic('name') for this transport.
  2. If you genuinely need publish/subscribe semantics, switch to a transport that implements topics.
  3. Type-check the destination before calling send()/createConsumer() when destinations are dynamic.

Example fix

// before
$destination = $context->createTopic('orders');
$producer->send($destination, $message); // InvalidDestinationException

// after
$destination = $context->createQueue('orders');
$producer->send($destination, $message);
Defensive patterns

Strategy: type-guard

Type guard

function isQueueDestination(\Phalcon\Contracts\Queue\Destination $destination): bool
{
    return $destination instanceof \Phalcon\Contracts\Queue\Queue;
}

// usage
if (!isQueueDestination($destination)) {
    throw new \InvalidArgumentException(
        'Queue-only transport: destination must be created with createQueue()'
    );
}
$producer->send($destination, $message);

Try / catch

try {
    $producer->send($destination, $message);
} catch (\Phalcon\Queue\Exceptions\InvalidDestinationException $e) {
    // message names the action: 'send to' or 'consume from'
    // rebuild the destination with createQueue() and retry
    $producer->send($context->createQueue($destination->getName()), $message);
}

Prevention

When it happens

Trigger: $producer->send($context->createTopic('news'), $message) on a queue-only transport, or $context->createConsumer($topic) — the topic object passes the DestinationInterface type hint but fails the instanceof Queue check.

Common situations: Code written against queue-interop expecting topic support (RabbitMQ-style pub/sub) reused on a queue-only backend; variable destinations routed from config where 'topic' was selected; refactoring from an AMQP transport.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/e244432e9de0be11. Report an issue: GitHub.

Appendix: source

Thrown at phalcon/Acl/Adapter/Storage.zep:92

        let data = this->storage->get(this->key);

        if typeof data === "object" {
            let data = this->normalizeToArray(data);
        }

        if typeof data !== "array" {
            return false;
        }

        if !isset data["version"] {
            return false;
        }

        let version = data["version"];

        if version != self::SNAPSHOT_VERSION {
            throw new InvalidSnapshot(
                "Incompatible ACL snapshot version '" . version .
                "'; expected '" . self::SNAPSHOT_VERSION . "'"
            );
        }

        if unlikely (
            typeof data["access"] !== "array" ||
            typeof data["accessList"] !== "array" ||
            typeof data["components"] !== "array" ||
            typeof data["componentsNames"] !== "array" ||
            typeof data["roles"] !== "array" ||
            typeof data["roleInherits"] !== "array"
        ) {
            throw new InvalidSnapshot("Malformed ACL snapshot structure");
        }

        let rebuiltRoles = [];
        for name, description in data["roles"] {

View on GitHub (pinned to b7419de9cd)