PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
229: Symfony Messenger Component for Async Messaging
A few years ago, I was reviewing a PR for a colleague who had built a sleek new user onboarding flow. Everything looked great until I actually tested it. I clicked "Complete Registration" and the browser just... sat there. For about six seconds, the page hung. I eventually found out that in the controller, he was triggering a welcome email, hitting an external CRM API to create a lead, and generating a starter PDF guide—all synchronously. If the CRM API was slow or the mail server lagged, the user was stuck staring at a white screen, wondering if the site had crashed. He even had a bug where users would click "Submit" three times because of the lag, resulting in three welcome emails and three CRM entries.
This is exactly why the Symfony Messenger component exists. You shouldn't make your user wait for tasks that don't need to happen right now to provide a successful response. By moving these "side effects" into an asynchronous queue, you can tell the user "Success!" in 50 milliseconds, while the heavy lifting happens in the background.
Decoupling the Intent from the Execution
At its core, Messenger uses a "Bus" pattern. Instead of calling a service method directly in your controller, you dispatch a Message. A message is just a simple PHP class—a Data Transfer Object (DTO)—that describes what needs to happen. It doesn't contain any logic; it just holds the data. For example, instead of calling $emailService->sendWelcomeEmail($user), you dispatch a SendWelcomeEmailMessage($userId).
Then, you write a MessageHandler. This is where the actual work lives. The handler is a service that "subscribes" to that specific message type. When the Messenger bus sees a SendWelcomeEmailMessage, it looks for the corresponding handler and executes its __invoke method. This separation is powerful because the controller no longer cares how the email is sent or when it happens; it only cares that the intent to send it has been recorded.
// The Message (DTO)
readonly class SendWelcomeEmailMessage {
public function __construct(public int $userId) {}
}
// The Handler
class SendWelcomeEmailHandler implements MessageHandlerInterface {
public function __invoke(SendWelcomeEmailMessage $message) {
// I'd inject the Mailer here and do the actual sending
$user = $this->userRepository->find($message->userId);
$this->mailer->sendWelcome($user);
}
}
Routing Messages to the Background
By default, Symfony handles messages "synchronously," meaning it just calls the handler immediately. To get that performance boost I mentioned, we need to route the message to a Transport. This is where your queue lives—be it RabbitMQ, Redis, or even a database table via Doctrine.
In your messenger.yaml, you define these transports and then map your messages to them. I usually prefer the Doctrine transport for smaller projects because it doesn't require installing extra software, though I'll move to Redis the second the volume gets high enough to cause database lock contention.
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: 'doctrine://default'
routing:
'App\Message\SendWelcomeEmailMessage': async
Once this is configured, dispatching the message merely inserts a row into a database table (the "queue"). The request finishes instantly. But now, the work isn't actually being done—it's just sitting there. To process the queue, you need to run a worker process via the CLI. I always recommend using a process manager like Supervisor in production to ensure this command stays running:
php bin/console messenger:consume async
One thing to watch out for: since the handler runs in a separate process, it doesn't share the same memory or state as the original web request. Always pass the ID of an object (like userId) in your message rather than the object itself. This ensures the handler fetches the freshest data from the database when it actually starts processing.
📋 Practical Task
Implement an Asynchronous PDF Invoice Generator
You are working on an e-commerce platform. Currently, when a user completes a purchase, the system generates a PDF invoice and uploads it to S3 before redirecting the user to the "Thank You" page. This is taking 4-7 seconds per order, which is unacceptable.
Your Task: Refactor this process using the Symfony Messenger component to make it asynchronous.
- Create a
GenerateInvoiceMessageDTO that accepts anorderId. - Create a
GenerateInvoiceHandlerthat simulates the heavy lifting (usesleep(5)to mimic PDF generation and adump()or log entry to confirm it finished). - Configure
messenger.yamlto route this specific message to anasynctransport using thedoctrine://defaulttransport. - Dispatch the message from a dummy controller action.
- Verify the implementation by triggering the controller action and observing that the page loads instantly, and then running
php bin/console messenger:consume asyncto see the "PDF generation" complete in the terminal.
There are no comments for now.