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
228: Symfony Event Dispatcher
I've seen this pattern a dozen times in legacy codebases: the "God Service." It usually starts innocently. You have an OrderService with a placeOrder() method. At first, it just saves the order to the database. Then, you need to send a confirmation email, so you inject the MailerService. Then the warehouse needs a notification, so you add the ShippingService. Suddenly, you're adding a loyalty points update, a Slack alert for high-value orders, and an analytics ping.
Before you know it, your placeOrder() method is 200 lines long and requires seven different dependencies in the constructor. It's a nightmare to test because to test the core logic of "saving an order," you have to mock half the application. This is the naive way—tightly coupling every side effect to the primary action.
The cost of hard-coded dependencies
// The "Naive" Way
public function placeOrder(Order $order)
{
$this->repository->save($order);
// Every new requirement adds a line here
$this->mailer->sendConfirmation($order);
$this->shipping->notifyWarehouse($order);
$this->loyaltyPoints->addPoints($order->getUser(), $order->getTotal());
$this->slack->sendAlert("New order #{$order->getId()} placed!");
}
The problem here isn't just that the method is long; it's that the OrderService now knows way too much. It knows that the warehouse exists, it knows how loyalty points work, and it knows about Slack. If the loyalty points system crashes or requires a breaking change, your order placement logic—the most critical part of your business—is now at risk. You've essentially glued your core business logic to a bunch of optional side effects.
Decoupling with the Event Dispatcher
This is where the Symfony Event Dispatcher comes in. Instead of the OrderService commanding other services to act, it simply announces that something happened. I like to think of it as a radio broadcast. The OrderService shouts, "An order was just placed!" into the void. It doesn't care who is listening, and it certainly doesn't care what they do with that information.
To do this, you create a specific Event class (which is just a data carrier) and use the EventDispatcherInterface to dispatch it. Here is how that looks in practice:
// 1. The Event Class
class OrderPlacedEvent extends Event
{
public const NAME = 'order.placed';
public function __construct(public Order $order) {}
}
// 2. The Refactored Service
public function placeOrder(Order $order)
{
$this->repository->save($order);
// We just dispatch the event and forget about it
$event = new OrderPlacedEvent($order);
$this->dispatcher->dispatch($event, OrderPlacedEvent::NAME);
}
Now, you create "Listeners" or "Subscribers." These are small, isolated classes that wait for order.placed. One handles the email, one handles the shipping, and one handles the Slack alert. If you want to stop sending Slack alerts, you don't touch the OrderService`; you just remove the listener.
The trade-off: Traceability vs. Flexibility
I'll be honest with you: this approach isn't a free lunch. The biggest downside is that you lose "static traceability." In the naive example, you can Ctrl+Click on sendConfirmation() and go straight to the code. With events, clicking on dispatch() only takes you to the Symfony core. You have to look at your service configuration or search the codebase for who is listening to order.placed to figure out what actually happens.
However, for any professional application, this is a trade-off I'll take every single time. The flexibility is immense. You can add a "Welcome Discount" email for first-time buyers by simply adding a new listener that checks the user's order count. You haven't touched a single line of the core order processing code, meaning you haven't introduced any risk of breaking the checkout flow. You've successfully separated the primary intent (placing an order) from the secondary effects (notifications, logs, points).
📋 Practical Task
Implement a User Registration Notification System
You are tasked with decoupling a user registration process. Currently, the UserRegistrationService handles password hashing, saving the user, sending a welcome email, and creating a default profile folder on the server. This is too bloated.
Your goal: Refactor this into an event-driven system using the Symfony Event Dispatcher.
- Create a
UserRegisteredEventclass that carries theUserobject. - Modify the
UserRegistrationServiceto remove theMailerandFileSystemdependencies and instead dispatch theUserRegisteredEvent. - Create two separate listeners:
WelcomeEmailListener: Handles the logic for sending the email.ProfileFolderListener: Handles the logic for creating the directory.
- Ensure that if the
ProfileFolderListeneris removed or fails, the user is still successfully registered and the email is still sent.
There are no comments for now.