Area-Specific Observer Configuration
Observers can be restricted to specific areas (frontend, adminhtml, crontab, etc.).
Area-specific events.xml:
<!-- etc/events.xml (global - all areas) -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_save_after">
<observer name="vendor_product_sync" instance="Vendor\Module\Observer\ProductSync"/>
</event>
</config>
<!-- etc/frontend/events.xml (frontend only) -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_save_after">
<observer name="vendor_product_frontend" instance="Vendor\Module\Observer\ProductFrontend"/>
</event>
</config>
<!-- etc/adminhtml/events.xml (admin only) -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_save_after">
<observer name="vendor_product_admin" instance="Vendor\Module\Observer\ProductAdmin"/>
</event>
</config>
<!-- etc/crontab/events.xml (cron only) -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="cron_schedule_run">
<observer name="vendor_cron_handler" instance="Vendor\Module\Observer\CronHandler"/>
</event>
</config>
Available areas for events.xml:
etc/events.xml— Global (all areas)etc/frontend/events.xml— Frontendetc/adminhtml/events.xml— Adminetc/crontab/events.xml— Cronetc/webapi_rest/events.xml— REST APIetc/webapi_graphql/events.xml— GraphQL
Disabling Observers
Observes from other modules can be disabled without removing their code.
Disable an observer:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_save_after">
<!-- Disable the third-party observer -->
<observer name="thirdparty_product_sync" disabled="true"/>
</event>
</config>
Why disable observers:
- Third-party module observer conflicts
- Performance: skip unnecessary processing
- Debugging: isolate observer issues
- Feature toggle: disable functionality
Disable with condition:
<event name="checkout_cart_product_add_after">
<!-- Only disable in admin area -->
<observer name="thirdparty_cart_handler" disabled="true"/>
</event>
Identifying observer names:
# List all observers for an event
php bin/magento dev:events:list catalog_product_save_after
# Show all registered observers
php bin/magento dev:events:list --all
# Or grep events.xml files
grep -r "catalog_product_save_after" app/code/ vendor/
Common observers to disable:
# Third-party analytics (if not needed)
thirdparty_analytics_product_view
# Unused integrations
thirdparty_social_share
# Conflicting inventory observers
thirdparty_inventory_sync
Shared vs Non-Shared Observers
Observer instances can be shared (singleton) or non-shared (new instance each time).
Default behavior — Shared:
<!-- Observer is instantiated once, reused across dispatches -->
<event name="catalog_product_save_after">
<observer name="vendor_product_sync" instance="Vendor\Module\Observer\ProductSync"/>
</event>
Non-shared observer:
<!-- New instance created for each event dispatch -->
<event name="catalog_product_save_after">
<observer name="vendor_product_sync"
instance="Vendor\Module\Observer\ProductSync"
shared="false"/>
</event>
When to use non-shared:
- Observer maintains state that shouldn't persist
- Observer has mutable properties
- Avoiding memory leaks with heavy observers
- Thread safety in concurrent scenarios
Shared observer concerns:
// BAD: State leaks between dispatches
class ProductSync implements ObserverInterface
{
private $products = []; // Accumulates across dispatches!
public function execute(Observer $observer)
{
$this->products[] = $observer->getEvent()->getProduct();
// $products keeps growing!
}
}
// GOOD: Stateless observer
class ProductSync implements ObserverInterface
{
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
$this->syncProduct($product); // Process immediately
}
}
Advanced Event Patterns
Advanced event patterns for complex scenarios.
Event with custom data:
// Dispatching custom event
$this->eventManager->dispatch(
'vendor_order_custom_processed',
[
'order' => $order,
'status' => 'completed',
'items' => $order->getAllItems(),
'custom_data' => ['key' => 'value']
]
);
// Observer receiving custom data
class OrderProcessedObserver implements ObserverInterface
{
public function execute(Observer $observer)
{
$order = $observer->getEvent()->getOrder();
$status = $observer->getEvent()->getStatus();
$items = $observer->getEvent()->getItems();
$customData = $observer->getEvent()->getCustomData();
}
}
Conditional observer execution:
class SmartObserver implements ObserverInterface
{
public function __construct(
private \Magento\Framework\App\Config\ScopeConfigInterface $config
) {}
public function execute(Observer $observer)
{
if (!$this->config->getValue('vendor_module/general/enabled')) {
return; // Skip if disabled
}
// Process event
}
}
Event propagation control:
public function execute(Observer $observer)
{
$transport = $observer->getEvent()->getTransport();
// Modify data passed to next observer
$transport->setData('processed', true);
// Stop event propagation (not recommended)
// $observer->getEvent()->stopPropagation();
}
Events.xml with priority (sortOrder):
<event name="catalog_product_save_after">
<observer name="vendor_first" instance="...\First" sortOrder="10"/>
<observer name="vendor_second" instance="...\Second" sortOrder="20"/>
<observer name="vendor_third" instance="...\Third" sortOrder="30"/>
</event>
Note: Magento doesn't officially support sortOrder on observers, but some implementations handle it. The default execution order depends on module load order.
Quiz
1. How do you restrict an observer to the frontend area only?
2. How do you disable a third-party observer?
3. What is the default sharing behavior of observers?
4. Where should events.xml be placed for admin-only observers?
Flashcards
Question
What directories can contain events.xml?
Click to reveal answer
Answer
etc/, etc/frontend/, etc/adminhtml/, etc/crontab/, etc/webapi_rest/, etc/webapi_graphql/
Question
How do you disable an observer?
Click to reveal answer
Answer
Add disabled="true" attribute to the <observer> element
Question
What is a shared observer?
Click to reveal answer
Answer
An observer instance that is reused (singleton) across all event dispatches
Question
How do you check which observers listen to an event?
Click to reveal answer
Answer
php bin/magento dev:events:list event_name
Question
What is the default observer sharing behavior?
Click to reveal answer
Answer
Shared (singleton) — set shared="false" for new instance per dispatch
Revision Notes
Key Takeaways
- 1. events.xml can be placed in area-specific directories
- 2. Observers can be disabled without removing module code
- 3. Default observer behavior is shared (singleton)
- 4. Set shared="false" for new instance per dispatch
- 5. Use dev:events:list to find observer names
- 6. Custom data can be passed via event dispatch array
Interview Tips
- • Explain the difference between global and area-specific events.xml
- • Describe when to use shared vs non-shared observers
- • Discuss how to disable problematic third-party observers
- • Know how to pass custom data to observers
Cheat Sheet
events.xml Cheat Sheet
Area-specific directories:
- etc/events.xml (global)
- etc/frontend/events.xml
- etc/adminhtml/events.xml
- etc/crontab/events.xml
- etc/webapi_rest/events.xml
- etc/webapi_graphql/events.xml
Observer attributes:
- name: Unique identifier
- instance: Observer class
- disabled: true/false
- shared: true/false (default: true)
Commands:
- dev:events:list
— List observers - dev:events:list --all — All registered observers
Best practices:
- Keep observers stateless
- Use non-shared for mutable state
- Disable unused observers for performance