Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony Aop Async Bundle Laravel Package

tourze/symfony-aop-async-bundle

A Symfony bundle that adds AOP-driven asynchronous execution, letting you offload selected service methods to run in the background via simple attributes instead of hand-written messaging code. Integrates with Symfony's DI container for minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle (tourze/symfony-aop-async-bundle), not a Laravel package — there is no service provider, no facade, and no config/aop-async.php. Treat it as a reference implementation / dependency you pull into a Symfony app, and read it as a pattern before adopting it in Laravel.

  1. Install and register. composer require tourze/symfony-aop-async-bundle, then add the bundle to config/bundles.php (Symfony Flex will usually do this). It sits on top of the Tourze AOP core bundle plus a Messenger transport, so verify those are installed and that framework.messenger has at least one transport configured — without a transport the interceptor has nowhere to hand work off.
  2. Where to look first. Open src/ and find, in this order: the attribute class (the thing you put on a method), the interceptor/aspect class (what actually intercepts the call), and config/services.php (how the interceptor is tagged and wired). Those three files tell you the real API in about five minutes; the README of Tourze bundles is typically thin.
  3. First use case. Pick a slow, fire-and-forget side effect on a service that is injected, not instantiated with new — sending a notification, calling a webhook, generating a thumbnail, writing an audit row. Annotate that one method and confirm the HTTP response returns before the work completes (check the transport table / log, not just the response time).

Concretely, the shape is roughly:

#[Async] // exact attribute name lives in src/, verify before use
public function sendReceipt(OrderId $orderId): void
{
    // slow I/O here
}

Callers keep calling $service->sendReceipt($id) and get an immediate return.

Implementation Patterns

  • Proxy-based interception. The interceptor only fires when the call passes through the DI container's proxy. Inject the service and call it from another class; $this->sendReceipt() from inside the same class, new Service(), or a static call all bypass it silently.
  • Pass identifiers, not entities. Whatever the interceptor serialises goes onto the transport. An Order entity with lazy relations and an open EntityManager will not survive the round trip. Redesign the signature to take scalars or small readonly DTOs, and re-fetch inside the async handler.
  • Return void (or a handle). There is no result to give back — the call has already returned by the time the work runs. Any method whose caller depends on the return value is a bad candidate. If you need "started" semantics, return a correlation ID you generate yourself before dispatch.
  • Pair with transport config, not with the attribute. Retries, failure_transport, delays, and queue names belong in messenger.yaml. The attribute should stay a one-liner; if you find yourself adding options to it, you probably want a Messenger middleware instead.
  • Guard against retries with idempotency. Messenger redelivers. A webhook POST or a "send email" step will fire twice on a transient failure. Key the handler on the correlation ID or use an idempotency table.
  • Testing. In test env, route the transport to in-memory:// (or sync:// where you want deterministic assertions) and assert on the dispatched message rather than on side effects. $this->assertCount(1, $transport->getSent()) is the fast, reliable assertion; sleeping to wait for a worker is not.
  • Adoption path in Laravel. If you want the same behaviour in a Laravel codebase, don't fight this bundle — the pattern maps cleanly onto a DispatchAfterResponse job or a queued listener plus a small decorator. Copy the idea (attribute + interceptor + bus dispatch), not the wiring.

Gotchas and Tips

  • 1.0.1 carries no changelog content. The release notes are a bare compare link between 1.0.0 and 1.0.1. That is consistent with a patch bump, but it is not evidence of "no behaviour change." Before bumping, run git diff 1.0.0..1.0.1 -- src/ yourself — Tourze ships fast and often, and patch releases have historically included quiet signature tweaks. Pin to 1.0.0 if you need stability and diff before moving.
  • Final/private/static methods don't get intercepted. Proxy-based AOP cannot override final classes or methods, and never sees private or static calls. This is the single most common "the attribute does nothing" cause — there's no error, the method just runs synchronously.
  • Self-invocation is silent. Covered above, but worth repeating because it fails loudly in production (slow requests) and never in dev (small payloads).
  • Transaction boundary. The async work runs in a different process, outside the request's DB transaction. Dispatch after commit — use Messenger's after_commit/DispatchAfterCurrentBusMiddleware, or dispatch from an event fired post-commit. Otherwise the handler races the INSERT and finds nothing.
  • Debugging. messenger:failed:show and messenger:failed:retry are your first stop; check the messenger_messages table to confirm the message was even enqueued. If it was never enqueued, the problem is interception/serialisation, not the worker. The Symfony profiler's Messenger panel shows dispatched messages per request.
  • Serialisation failures are deferred. An un-serialisable argument often only explodes inside the worker, not at dispatch time, so a green test suite proves nothing about the transport. Add one integration test that actually runs a worker against a real transport.
  • Extension points. The interceptor is a normal tagged service — you can decorate it to add logging, metrics, or a feature flag gate without forking. Transport-level behaviour (retry strategy, dead-lettering) belongs in messenger.yaml. If you need conditional async (async only above a payload size), write a thin wrapper method that decides, rather than adding branching to the attribute.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core