petitpress/gps-messenger-bundle
Google Pub/Sub transport for Symfony Messenger with zero extra dependencies beyond core Symfony and Pub/Sub libraries. Supports DSN and options config, automatic topic/subscription creation, message ordering via OrderingKeyStamp, dead letter policies, and keep-alive for long-running workers.
First, calibrate your expectations: this is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no config/gps-messenger.php, and no php artisan command that will publish anything useful. If you composer require petitpress/gps-messenger-bundle in a Laravel app, all you'll get is an unused Bundle class and a hard dependency tree of symfony/* packages that will not boot without a Symfony kernel.
So the realistic "day-one" paths for a Laravel developer are:
Transport\PubSubTransport, Transport\PubSubReceiver, Transport\PubSubSender, and the TransportFactory show the exact shape of a Pub/Sub driver (DSN parsing, subscribe/publish, message ack/nack, envelope handling). You will port those ideas to a Laravel Queue\Connector + Queue\Job pair.symfony/messenger inside a Laravel Artisan command (advanced, fragile): composer require symfony/messenger symfony/serializer petitpress/gps-messenger-bundle google/cloud-pubsub, then manually wire the transport factory in a service container you construct yourself inside an Artisan\Command. Only do this if you truly must run the Symfony transport in-process.What to look at first (in order):
composer.json — confirm symfony/messenger and google/cloud-pubsub versions and PHP constraint (PHP 8.1+).DependencyInjection/ and Resources/config/ — how the transport factory is registered and how the DSN is parsed (gps://project/topic?keyFile=...).Transport/ — the wire-level send/receive logic; this is what you'd reimplement for Laravel.auto_setup).First concrete use case (port the pattern): Create a Laravel queue connection that mirrors the DSN, wrap google/cloud-pubsub PubSubClient, publish an envelope-encoded payload from dispatch(), and acknowledge messages in a queue:work loop. The bundle shows you the exact client calls (publish(), pull(), acknowledge()) and payload envelope shape to use.
Pattern 1 — Symfony sidecar + Laravel producer (most pragmatic).
Laravel publishes a normal JSON payload to Pub/Sub (using google/cloud-pubsub directly, or a Job dispatched to a thin adapter). The Symfony service, running this bundle, consumes the topic with messenger:consume. Laravel never imports the bundle. You get the battle-tested receiver/retry behaviour on the consumer side without fighting the framework gap.
Pattern 2 — Steal the transport, keep Laravel's bus.
Build a GooglePubSubConnector implementing Illuminate\Contracts\Queue\Queue and a GooglePubSubJob implementing Illuminate\Contracts\Queue\Job. Mirror the bundle's PubSubSender::send() envelope (Symfony's Envelope → array with body, headers) so a Symfony worker and a Laravel worker can interoperate on the same topic. Use Laravel's Illuminate\Queue\Queue::enqueueUsing() for the standard before/after job hooks so failed jobs, retry_after, and events keep working. Key methods to copy semantics from: PubSubReceiver::get(), PubSubReceiver::ack(), PubSubReceiver::reject() — map reject to ack (Pub/Sub has no nack-with-requeue like RabbitMQ; a nacked message simply returns to the subscription).
Pattern 3 — DSN-driven transport config.
Follow the bundle's DSN convention so ops teams see one grammar across services: gps://{projectId}/{topic}?subscription={sub}&keyFile={path}. Parse via parse_url(), hydrate a config object. This makes .env files portable between Symfony and Laravel deployments.
Pattern 4 — Envelope/middleware parity.
Symfony Messenger envelopes carry MessageBus stamps (Retry, Delay, DelayStamp). Laravel jobs carry tries, backoff, delay. On publish, translate Laravel's delay into a Pub/Sub attribute + client-side sleep before publish, or better, use a Cloud Scheduler / delayed-topic emulation, since native Pub/Sub has no delay. Persist tries/backoff as message attributes; on consume, read them into job properties so Illuminate\Queue\Worker (if you wire it that way) behaves consistently.
Pattern 5 — Worker loop.
The bundle's receiver does a streaming pull. In Laravel, implement pop() to run a single pull with maxMessages + returnImmediately matching your worker's sleep. Run php artisan queue:work --queue=google-pubsub and let Laravel's worker handle backoff between empty pulls.
getFacadeAccessor, no auto-discovery, no publishable config. Any tutorial that suggests adding it to config/app.php providers is misleading. Composer autoloading will happily load the classes, but nothing boots.keyFile from the DSN or a config path. google/cloud-pubsub also honours GOOGLE_APPLICATION_CREDENTIALS and workload identity. In Laravel, standardise on the env var to keep parity with GCP's own SDK and avoid passing a path through DSNs into logs.PUBSUB_EMULATOR_HOST) works with google/cloud-pubsub regardless of Symfony/Laravel. Set it in phpunit.xml and skip this bundle entirely in test environments.Retry middleware pattern is worth copying into a Laravel Middleware that wraps handlers with a dedupe key.FailedMessageTransport needs a separate transport configured — Laravel's failed_jobs table has no direct equivalent; you either write to a second topic or a database.PubSubReceiver, a thrown exception causes the message to be left un-acked (redelivered after ack deadline). In a Laravel port, make sure Job::delete() calls acknowledge() and Job::release() uses modifyAckDeadline() — the naive ->ack() everywhere will silently lose messages.keyFile query param that may be URL-encoded (paths with spaces break it). If you copy the parsing, urldecode() before realpath(), and validate that the file exists at boot, not at publish time.push/pop. Log the Pub/Sub messageId into your Laravel context so php artisan queue:failed rows can be correlated with Cloud Logging.google/cloud-pubsub both move fast; pin both in composer.json even in a ported implementation, because transport interface changes (TransportInterface::send return type, PubSubClient constructor args) will break your fork silently.TransportFactory is tagged via messenger.transport_factory; the receiver/sender are plain services you can decorate. In a Laravel port, expose the same seam: a ConnectorInterface resolved from the driver name in config/queue.php, so future backends (SQS, Redis) are swap-in.app/Queue/GooglePubSub/ namespace so you control upgrade risk.How can I help you explore Laravel packages today?