Asynchronous API call to self in symfony 3 / php / guzzle
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Making an asynchronous HTTP call from a Symfony 3 application back to the same application is technically possible with Guzzle, but it is often the wrong abstraction. A self-call still consumes another web worker, still crosses HTTP, and may not behave the way people expect if the original PHP request ends before the promise is driven to completion. In many cases, the better answer is “do not call yourself over HTTP; call a service directly or push work to a queue.”
What Guzzle Async Actually Means
With Guzzle, “async” means the request returns a promise immediately instead of blocking right away.
That promise does not magically create a background job system. In ordinary PHP request-response execution, you still need the process to stay alive long enough for the request to be sent and settled.
If you immediately call wait(), you lose most of the benefit:
At that point, the code is effectively waiting for the result before continuing.
Why Self-Calls Are Often a Smell
If the target endpoint lives in the same Symfony app, an HTTP call to self is usually extra overhead:
- another HTTP request must be parsed and routed
- another PHP worker or process must execute it
- auth, headers, serialization, and networking all happen again
If the goal is simply to reuse business logic, move that logic into a service class and call it directly:
Then in your controller:
This is simpler, faster, and easier to debug than issuing an HTTP request back into the same codebase.
When You Actually Need Background Work
Sometimes the real requirement is not “async HTTP” but “do this later without blocking the user response.” In Symfony 3, that usually means one of these:
- queue a job into RabbitMQ, Redis, or another worker system
- trigger a CLI command from a supervisor-managed worker
- use
kernel.terminatefor small post-response work
A queue is the robust answer when the task is expensive, retryable, or business-critical.
For lighter post-response work, a terminate listener can help:
That keeps the work inside the app without bouncing through HTTP.
If You Still Want a Self-Call
There are cases where a self-call is acceptable, such as hitting an internal webhook endpoint or intentionally reusing an HTTP contract. If you do this, be explicit about the tradeoff.
Example:
The final Utils::queue()->run() matters because a plain promise object does not guarantee work will finish before the PHP request ends.
Even then, remember that this is still just another HTTP request to your app. It is not a durable job queue.
Server Capacity Still Matters
Self-calls can create odd behavior under load. If your app has a limited number of PHP-FPM workers and each incoming request creates more internal requests, the system can starve itself or deadlock under pressure.
That is one reason queue-based designs age better. They separate user-facing request handling from background work capacity.
Common Pitfalls
The biggest pitfall is assuming Guzzle async means “fire and forget” in the same way as a real background worker. In a normal PHP request lifecycle, promises still need to be executed before the script ends.
Another common mistake is calling wait() immediately and expecting a performance win. That turns the code back into a blocking request.
Security is also easy to overlook. Internal endpoints still need authentication or another trust boundary, because “internal” routes are often reachable in more places than expected.
Finally, if the only reason for the self-call is code reuse, move the shared logic into a Symfony service instead of routing HTTP through your own app.
Summary
- A Guzzle async self-call is possible, but it is often not the best design.
- If you only need shared logic, call a service directly instead of making HTTP calls to yourself.
- If you need real background processing, use a queue or another worker mechanism.
- A self-call still consumes web-server capacity and can behave badly under load.
- If you do use
requestAsync, be explicit about promise execution and error handling.

