Skip to content

Commit b680bb3

Browse files
committed
Add BabelQueue Symfony Messenger serializer and workflows
0 parents  commit b680bb3

17 files changed

Lines changed: 864 additions & 0 deletions

.gitattributes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Keep the published (Packagist) package slim — exclude dev-only files from dist.
2+
/.github export-ignore
3+
/tests export-ignore
4+
/.gitattributes export-ignore
5+
/.gitignore export-ignore
6+
/phpunit.xml export-ignore
7+
8+
* text=auto eol=lf

.github/workflows/ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
env:
12+
# Composer 2.9+ excludes versions with open security advisories during
13+
# resolution; a flagged Symfony patch can otherwise break a leg. We test
14+
# compatibility across the supported range here (CI, not a deployment).
15+
COMPOSER_NO_SECURITY_BLOCKING: "1"
16+
17+
jobs:
18+
test:
19+
name: PHP ${{ matrix.php }} · ${{ matrix.deps }} deps
20+
runs-on: ubuntu-latest
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
php: ['8.2', '8.3', '8.4']
25+
deps: ['highest', 'lowest']
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Setup PHP
30+
uses: shivammathur/setup-php@v2
31+
with:
32+
php-version: ${{ matrix.php }}
33+
extensions: json
34+
coverage: none
35+
tools: composer:v2
36+
37+
- name: Install dependencies
38+
run: |
39+
if [ "${{ matrix.deps }}" = "lowest" ]; then
40+
composer update --prefer-lowest --prefer-dist --no-interaction --no-progress
41+
else
42+
composer update --prefer-dist --no-interaction --no-progress
43+
fi
44+
45+
- name: Run tests
46+
run: vendor/bin/phpunit

.github/workflows/release.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
7+
permissions:
8+
contents: write
9+
10+
env:
11+
COMPOSER_NO_SECURITY_BLOCKING: "1"
12+
13+
jobs:
14+
validate:
15+
name: Validate (tests green before publishing)
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Setup PHP
21+
uses: shivammathur/setup-php@v2
22+
with:
23+
php-version: '8.3'
24+
extensions: json
25+
coverage: none
26+
tools: composer:v2
27+
28+
- name: Install dependencies
29+
run: composer update --prefer-dist --no-interaction --no-progress
30+
31+
- name: Run tests
32+
run: vendor/bin/phpunit
33+
34+
release:
35+
name: Publish
36+
needs: validate
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- name: Create GitHub release
42+
uses: softprops/action-gh-release@v2
43+
with:
44+
generate_release_notes: true
45+
46+
# Packagist normally auto-updates via its GitHub webhook/App. If you set
47+
# PACKAGIST_USERNAME / PACKAGIST_TOKEN secrets, this also pings its API.
48+
- name: Notify Packagist (optional)
49+
env:
50+
PACKAGIST_USERNAME: ${{ secrets.PACKAGIST_USERNAME }}
51+
PACKAGIST_TOKEN: ${{ secrets.PACKAGIST_TOKEN }}
52+
run: |
53+
if [ -n "$PACKAGIST_TOKEN" ]; then
54+
curl -sf -XPOST -H 'content-type:application/json' \
55+
"https://packagist.org/api/update-package?username=${PACKAGIST_USERNAME}&apiToken=${PACKAGIST_TOKEN}" \
56+
-d '{"repository":{"url":"https://github.com/BabelQueue/symfony"}}'
57+
else
58+
echo "Packagist secrets not set — relying on the Packagist GitHub webhook."
59+
fi

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/vendor/
2+
composer.lock
3+
.phpunit.result.cache
4+
.phpunit.cache/

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Changelog
2+
3+
All notable changes to `babelqueue/symfony` are documented here.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
The envelope wire format is versioned separately by `meta.schema_version`
8+
(currently **1**) — see the contract at [babelqueue.com](https://babelqueue.com).
9+
10+
## [Unreleased]
11+
12+
### Added
13+
- `Messenger\BabelQueueSerializer` — a Symfony Messenger transport serializer that
14+
encodes/decodes the canonical BabelQueue envelope via the shared core codec
15+
(`babelqueue/php-sdk`), so Symfony interoperates with the other SDKs.
16+
- `Contracts\PolyglotMessage` — message contract (`getBabelUrn()`, `toPayload()`,
17+
`fromBabelPayload()`).
18+
- `Messenger\MessageRegistry` — URN → message-class map for decoding.
19+
- `Messenger\Stamp\BabelTraceStamp` — carries `trace_id` through the pipeline;
20+
attached on decode, honoured on encode (trace continuation).
21+
- Redelivery ↔ `attempts` bridge (Messenger `RedeliveryStamp` ⇄ envelope `attempts`).
22+
- `BabelQueueBundle` + DI: registers the serializer as
23+
`babelqueue.messenger.serializer`, configured under the `babelqueue` key.
24+
25+
### Notes
26+
- Pre-1.0: the public API may change before the `1.0.0` tag.
27+
- Requires PHP `^8.2`, `babelqueue/php-sdk ^0.1`, and Symfony `^6.4 | ^7.0`.
28+
- Routing/worker/retry remain Messenger's responsibility; this package only owns
29+
the wire format. Automatic `trace_id` propagation across re-dispatches (a
30+
middleware) is planned.
31+
32+
[Unreleased]: https://github.com/BabelQueue/symfony/commits/main

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Muhammet Şafak
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# BabelQueue for Symfony
2+
3+
[![CI](https://github.com/BabelQueue/symfony/actions/workflows/ci.yml/badge.svg)](https://github.com/BabelQueue/symfony/actions/workflows/ci.yml)
4+
[![Packagist](https://img.shields.io/packagist/v/babelqueue/symfony.svg)](https://packagist.org/packages/babelqueue/symfony)
5+
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6+
7+
> **Polyglot Queues, Simplified.** A Symfony Messenger serializer that speaks the
8+
> canonical BabelQueue envelope — so your Symfony services exchange messages with
9+
> Laravel, Go, Python, .NET and Node over one strict JSON format, on the broker
10+
> you already run.
11+
12+
This is the Symfony adapter. It plugs into **Symfony Messenger**: you keep
13+
Messenger's transports, handlers, worker and retry — BabelQueue only changes the
14+
**wire format** to the language-agnostic envelope (built by the shared core,
15+
[`babelqueue/php-sdk`](https://packagist.org/packages/babelqueue/php-sdk)). The
16+
full standard is documented at **[babelqueue.com](https://babelqueue.com)**.
17+
18+
## Requirements
19+
20+
- PHP `^8.2`
21+
- Symfony `^6.4 | ^7.0` (Messenger)
22+
- A broker Messenger supports (AMQP/RabbitMQ, Redis, …)
23+
24+
## Installation
25+
26+
```bash
27+
composer require babelqueue/symfony
28+
```
29+
30+
Enable the bundle (if you don't use Symfony Flex) in `config/bundles.php`:
31+
32+
```php
33+
return [
34+
// ...
35+
BabelQueue\Symfony\BabelQueueBundle::class => ['all' => true],
36+
];
37+
```
38+
39+
## Configuration
40+
41+
Point a Messenger transport at the BabelQueue serializer, and map inbound URNs to
42+
message classes:
43+
44+
```yaml
45+
# config/packages/messenger.yaml
46+
framework:
47+
messenger:
48+
transports:
49+
babel:
50+
dsn: '%env(MESSENGER_TRANSPORT_DSN)%' # e.g. amqp:// or redis://
51+
serializer: 'babelqueue.messenger.serializer'
52+
routing:
53+
'App\Message\OrderCreated': babel
54+
```
55+
56+
```yaml
57+
# config/packages/babelqueue.yaml
58+
babelqueue:
59+
queue: 'orders' # written to the envelope meta.queue
60+
messages: # urn => message class (needed to consume)
61+
'urn:babel:orders:created': 'App\Message\OrderCreated'
62+
```
63+
64+
## A message
65+
66+
Implement `BabelQueue\Symfony\Contracts\PolyglotMessage`:
67+
68+
```php
69+
use BabelQueue\Symfony\Contracts\PolyglotMessage;
70+
71+
final class OrderCreated implements PolyglotMessage
72+
{
73+
public function __construct(public int $orderId) {}
74+
75+
public function getBabelUrn(): string
76+
{
77+
return 'urn:babel:orders:created';
78+
}
79+
80+
public function toPayload(): array
81+
{
82+
return ['order_id' => $this->orderId];
83+
}
84+
85+
public static function fromBabelPayload(array $data): static
86+
{
87+
return new self((int) $data['order_id']);
88+
}
89+
}
90+
```
91+
92+
## Produce & consume
93+
94+
```php
95+
// produce — a normal Messenger dispatch
96+
$bus->dispatch(new OrderCreated(1042));
97+
```
98+
99+
On the wire it becomes the canonical envelope, readable by every BabelQueue SDK:
100+
101+
```json
102+
{
103+
"job": "urn:babel:orders:created",
104+
"trace_id": "…",
105+
"data": { "order_id": 1042 },
106+
"meta": { "id": "…", "queue": "orders", "lang": "php", "schema_version": 1, "created_at": 1749132727000 },
107+
"attempts": 0
108+
}
109+
```
110+
111+
```php
112+
// consume — a normal Messenger handler, routed by message class
113+
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
114+
115+
#[AsMessageHandler]
116+
final class OnOrderCreated
117+
{
118+
public function __invoke(OrderCreated $message): void
119+
{
120+
// ...
121+
}
122+
}
123+
```
124+
125+
Run the worker as usual: `php bin/console messenger:consume babel`.
126+
127+
## How it maps to Messenger
128+
129+
- **Routing** is Messenger's job: it routes the decoded message class to a handler.
130+
- **Retry** bridges both ways — Messenger's `RedeliveryStamp` ⇄ the envelope's
131+
top-level `attempts`.
132+
- **Tracing** — the inbound `trace_id` is attached as a `BabelTraceStamp`; re-emit
133+
it on a downstream message (with the stamp) to continue the trace.
134+
- **Unknown URN** — a message whose URN isn't mapped throws
135+
`MessageDecodingFailedException`, so Messenger routes it to your failure
136+
transport (the idiomatic Symfony behavior).
137+
138+
## Testing
139+
140+
```bash
141+
composer install
142+
vendor/bin/phpunit
143+
```
144+
145+
## License
146+
147+
MIT © Muhammet Şafak. See [LICENSE](LICENSE).

composer.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "babelqueue/symfony",
3+
"description": "Symfony adapter for BabelQueue: a Messenger serializer that speaks the canonical polyglot envelope, so Symfony services interoperate with Laravel, Go, Python, .NET and Node over one wire format. Built on babelqueue/php-sdk.",
4+
"keywords": [
5+
"symfony",
6+
"messenger",
7+
"queue",
8+
"polyglot",
9+
"microservices",
10+
"json"
11+
],
12+
"type": "symfony-bundle",
13+
"license": "MIT",
14+
"authors": [
15+
{
16+
"name": "Muhammet Şafak",
17+
"email": "info@muhammetsafak.com.tr"
18+
}
19+
],
20+
"homepage": "https://babelqueue.com",
21+
"support": {
22+
"issues": "https://github.com/BabelQueue/symfony/issues",
23+
"source": "https://github.com/BabelQueue/symfony"
24+
},
25+
"require": {
26+
"php": "^8.2",
27+
"babelqueue/php-sdk": "^0.1",
28+
"symfony/messenger": "^6.4|^7.0",
29+
"symfony/config": "^6.4|^7.0",
30+
"symfony/dependency-injection": "^6.4|^7.0",
31+
"symfony/http-kernel": "^6.4|^7.0"
32+
},
33+
"require-dev": {
34+
"phpunit/phpunit": "^10.5|^11.0"
35+
},
36+
"autoload": {
37+
"psr-4": {
38+
"BabelQueue\\Symfony\\": "src/"
39+
}
40+
},
41+
"autoload-dev": {
42+
"psr-4": {
43+
"BabelQueue\\Symfony\\Tests\\": "tests/"
44+
}
45+
},
46+
"config": {
47+
"sort-packages": true
48+
},
49+
"minimum-stability": "stable",
50+
"prefer-stable": true
51+
}

phpunit.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
failOnWarning="true"
7+
failOnRisky="true">
8+
<testsuites>
9+
<testsuite name="BabelQueue Symfony">
10+
<directory>tests</directory>
11+
</testsuite>
12+
</testsuites>
13+
<source>
14+
<include>
15+
<directory>src</directory>
16+
</include>
17+
</source>
18+
</phpunit>

0 commit comments

Comments
 (0)