Nette DI integration for Symfony Messenger. Library and DI extension, not an application. Provides message buses, async transports, handler auto-discovery, retry strategies, and failure transport routing — all configured via Nette DI extension and compiled through ordered passes.
- PHP >=8.2, Symfony 7.x/8.x, Nette DI ^3.1
- PHPStan level 9 (phpVersion 80200)
- Nette Tester (
.phptfiles) - Contributte code style (
ruleset.xml)
src/DI/— main entry point; extension delegates to ordered passes inPass/, each handling one concern (serializers, transports, routing, handlers, events, logging, console, buses, debug)src/Bus/— bus wrappers (message, command, query) and registrysrc/Container/— PSR-11 adapters for Nette DIsrc/Handler/— runtime handler locator with wildcard/interface/parent matchingsrc/Logger/— dual HTTP/console logger bridgetests/Cases/— tests grouped by concern (DI/, Bus/, E2E/)tests/Mocks/— simple DTOs and handlers used as test doublestests/Toolkit/— container builder and test helpers
- Extension delegates to ordered passes via load -> beforeCompile -> afterCompile hooks, each handling one concern
- Pass priority: serializers/transports -> routing/handlers -> events/logging/console -> buses/debug
- Modify the responsible pass, not the extension
- Config under
messenger:key — schema defined in extension class, update schema first, then wire into the pass - Config values can be class names,
@servicereferences, or DI statements; routing/failure transports validated at compile time - Tag names defined as extension constants; service names follow
messenger.bus.<name>.*,messenger.transport.<name>,messenger.serializer.<name>— preserve these - Handlers registered via DI tag or
#[AsMessageHandler], message type inferred from first parameter type-hint (__invoke) - Union/intersection types rejected; handlers grouped per bus, sorted by priority
- Runtime handler matching: concrete class, parents, interfaces, namespace wildcards,
* - Default middleware order: bus name stamp -> dispatch after current bus -> failed message processing -> [custom] -> send -> handle — preserve this order
- Transport factories registered only when corresponding Symfony bridge class exists
- Retry defaults to multiplier; event pass wires retry/failure listeners, reuses existing event dispatcher
<?php declare(strict_types = 1);on one line in every file- Indentation with tabs
- Contributte/Nette formatting enforced via
contributte/qaruleset (Slevomat Coding Standard) - Type name must match file name
- Root namespaces:
Contributte\Messengerforsrc/,Testsfortests/ - One
useper line, alphabetically ordered, no unused imports - No superfluous
Interfacesuffix in production code (allowed in tests) - Exception messages must be explicit — tests assert on them
- Avoid comments unless the logic is genuinely non-obvious
- Prefer small, focused changes inside the responsible pass or utility
- Run
make csfto auto-fix before committing
Nette Tester, not PHPUnit.
make tests # run all tests
make phpstan # static analysis
make csf # fix code style
make qa # phpstan + csAlways run make cs phpstan tests and fix all errors.
.phptfiles with multiple test cases per file- Containers built via toolkit:
Container::of()->withDefaults()->withCompiler(...)->build() - Inline NEON config via
Helpers::neon(<<<'NEON' ... NEON) - Assertions:
Assert::type(),Assert::count(),Assert::equal(),Assert::exception() - DI tests verify service registration, tags, and config validation errors
- E2E tests extend
TestCasefor full dispatch->handle workflows - File naming:
MessengerExtension.{feature}.phpt - Mocks are simple DTOs/handlers with public properties for assertions
- Compiled containers written to
tests/tmpfor debugging