In Magento un observer è una classe che “ascolta” un evento del sistema ed esegue una logica quando quell’evento viene dispatchato. È un meccanismo fondamentale per estendere Magento senza modificare il core, ed è particolarmente utile quando vuoi reagire a un’azione già avvenuta (o che sta per avvenire) nel flusso applicativo: aggiunta prodotto al carrello, creazione ordine, login cliente, salvataggio entità, calcoli di totali e molto altro.
Eventi e observer: come funzionano
Magento usa un event manager (basato su Magento\Framework\Event\ManagerInterface) per dispatchare eventi in vari punti del codice. Quando un evento viene dispatchato, Magento costruisce un oggetto evento che contiene un “payload” (dati contestuali), e richiama tutti gli observer registrati per quell’evento.
In pratica:
- Il core o un modulo dispatcha un evento, ad esempio
sales_order_place_after. - Il tuo modulo registra un observer per quell’evento in
events.xml. - Magento invoca il metodo
execute()del tuo observer e passa unObserverche incapsula l’evento e i dati.
Quando usare un observer (e quando no)
Gli observer sono ideali per logiche reattive, soprattutto quando non hai bisogno di modificare direttamente il valore di ritorno di un metodo.
- Usa un observer per: logging, aggiornamenti “a valle”, invio email/integrazioni, arricchimento dati prima del salvataggio, invalidazione cache, sincronizzazioni, metriche.
- Valuta un plugin (
before/around/after) se devi intercettare un metodo preciso e modificarne input/output. - Valuta una preference solo come ultima risorsa, perché aumenta il rischio di conflitti e rende gli upgrade più delicati.
Struttura minima di un modulo per un observer
Per creare un observer serve un modulo Magento 2. La struttura tipica (nome di esempio: Vendor/Module) è:
app/code/Vendor/Module/registration.phpapp/code/Vendor/Module/etc/module.xmlapp/code/Vendor/Module/etc/events.xml(oppureetc/frontend/events.xml,etc/adminhtml/events.xml)app/code/Vendor/Module/Observer/...
1) registration.php
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Vendor_Module',
__DIR__
);
2) etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Vendor_Module" setup_version="1.0.0">
<sequence>
<module name="Magento_Sales"/>
</sequence>
</module>
</config>
La sezione sequence è opzionale e serve quando il tuo modulo dipende in modo esplicito da un altro modulo (ad esempio se osservi eventi di Magento_Sales e vuoi garantire che sia caricato prima).
Registrare l’observer in events.xml
La registrazione avviene tramite un file events.xml. Puoi posizionarlo in:
etc/events.xmlper eventi validi in tutte le areeetc/frontend/events.xmlper la sola area frontendetc/adminhtml/events.xmlper la sola area backendetc/webapi_rest/events.xmloetc/webapi_soap/events.xmlper specifiche aree API, se necessario
Esempio: reagire alla creazione di un ordine con l’evento sales_order_place_after.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="sales_order_place_after">
<observer name="vendor_module_after_order_place"
instance="Vendor\Module\Observer\AfterOrderPlace" />
</event>
</config>
Note importanti:
namedell’observer deve essere univoco all’interno dell’evento.instanceè la classe PHP dell’observer (con namespace completo).- Un evento può avere più observer; verranno eseguiti tutti.
Creare la classe Observer
Un observer deve implementare Magento\Framework\Event\ObserverInterface e definire execute().
Esempio completo: AfterOrderPlace
<?php
declare(strict_types=1);
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Psr\Log\LoggerInterface;
class AfterOrderPlace implements ObserverInterface
{
public function __construct(
private readonly LoggerInterface $logger
) {
}
public function execute(Observer $observer): void
{
/** @var OrderInterface|null $order */
$order = $observer->getEvent()->getOrder();
if (!$order) {
// Alcuni eventi possono non avere sempre la chiave attesa
$this->logger->warning('sales_order_place_after: order non presente nel payload evento');
return;
}
$this->logger->info('Ordine piazzato', [
'increment_id' => $order->getIncrementId(),
'entity_id' => $order->getEntityId(),
'grand_total' => (float) $order->getGrandTotal(),
'customer_id' => $order->getCustomerId(),
]);
// Qui inserisci la tua logica:
// - invio verso un ERP
// - aggiornamento di tabelle custom
// - pubblicazione su coda (RabbitMQ) tramite publisher
// - etc.
}
}
In questo esempio:
- Usiamo la Dependency Injection nel costruttore (logger PSR-3).
- Accediamo ai dati dell’evento con
$observer->getEvent()->getOrder(). - Gestiamo il caso in cui il dato non sia presente.
Capire il payload dell’evento
Ogni evento espone un set di dati specifico. Per capire quali chiavi sono disponibili, hai varie strategie:
- Cercare nel core dove l’evento viene dispatchato e leggere i parametri passati (di solito un array associativo).
- Usare debugging: loggare
array_keys($observer->getEvent()->getData()). - Consultare riferimenti della community o documentazione del modulo, se disponibile.
Esempio di logging delle chiavi:
$event = $observer->getEvent();
$this->logger->debug('Chiavi evento', [
'event_name' => $event->getName(),
'keys' => array_keys($event->getData())
]);
Observer per aree diverse: frontend e adminhtml
Spesso un evento viene dispatchato sia in frontend sia in backend. Se la logica deve scattare solo in un’area, crea il file nel path corretto:
etc/frontend/events.xmlper eseguire solo nel negozio.etc/adminhtml/events.xmlper eseguire solo nel pannello admin.
Questo è utile per evitare overhead inutile o effetti collaterali (ad esempio logiche che devono essere eseguite solo quando un admin salva un’entità).
Esempio pratico: aggiungere un attributo al prodotto quando viene aggiunto al carrello
Supponiamo di voler registrare un’informazione custom ogni volta che un prodotto viene aggiunto al carrello. Un evento tipico è checkout_cart_product_add_after (la disponibilità esatta può dipendere dal flusso che stai usando). L’idea è:
- Registrare l’observer.
- Leggere dal payload il
quote_itemo informazioni analoghe. - Scrivere un dato sul quote item (ad esempio un campo custom) e lasciar gestire il salvataggio al flusso standard.
events.xml (frontend):
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="checkout_cart_product_add_after">
<observer name="vendor_module_after_add_to_cart"
instance="Vendor\Module\Observer\AfterAddToCart" />
</event>
</config>
Observer (nota: la presenza delle chiavi dipende dal punto in cui l’evento viene dispatchato; adatta i getter al payload reale):
<?php
declare(strict_types=1);
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
class AfterAddToCart implements ObserverInterface
{
public function __construct(
private readonly LoggerInterface $logger
) {
}
public function execute(Observer $observer): void
{
$event = $observer->getEvent();
// Possibili chiavi: quote_item, product, quote, etc.
$quoteItem = $event->getQuoteItem();
if (!$quoteItem) {
$this->logger->debug('checkout_cart_product_add_after: quote_item non presente');
return;
}
// Esempio: aggiungo un flag custom sul quote item
$quoteItem->setData('vendor_custom_flag', 1);
// In genere il salvataggio del quote avviene dopo; evita save() se non necessario
$this->logger->info('Item aggiunto al carrello', [
'sku' => $quoteItem->getSku(),
'qty' => (float) $quoteItem->getQty(),
]);
}
}
Attenzione: salvare esplicitamente il quote o il quote item dentro un observer può creare overhead o comportamenti inattesi (loop, transazioni, race condition). Spesso è meglio limitarsi a impostare dati sul modello e lasciare che il flusso di Magento gestisca il salvataggio.
Configurazioni avanzate e buone pratiche
1) Evita logiche pesanti nel request thread
Se l’observer fa chiamate HTTP lente (ERP, CRM, servizi esterni), valuta di pubblicare un messaggio su coda (RabbitMQ) o usare un flusso asincrono. Un observer che blocca la pagina checkout può degradare conversion rate e UX.
2) Mantieni l’observer “thin”
Una buona pratica è ridurre la logica dentro execute() e delegare a servizi dedicati. Questo migliora testabilità e manutenzione.
public function execute(Observer $observer): void
{
$order = $observer->getEvent()->getOrder();
if (!$order) {
return;
}
$this->myService->handleOrderPlaced($order);
}
3) Gestione errori e idempotenza
Se l’observer esegue side-effect (invio dati, aggiornamenti esterni), progetta la logica per essere idempotente o con meccanismi di retry sicuri. In alcuni scenari possono verificarsi ripetizioni (ad esempio processi che riprovano o flussi che richiamano più volte una stessa azione).
4) Conflitti e ordine di esecuzione
Magento non garantisce un ordine semplice e universale quando ci sono molti observer sullo stesso evento. Se hai dipendenze di ordine, considera di:
- ridurre al minimo i casi in cui l’ordine è rilevante
- spostare la logica in un servizio unico chiamato da un singolo observer
- oppure usare un plugin su un metodo preciso quando l’ordine è critico
5) Scope e aree: non registrare ovunque se non serve
Se un observer serve solo nel frontend, mettilo in etc/frontend/events.xml. Riduce il carico e previene esecuzioni indesiderate nel backend o in cron/API.
Attivazione modulo e deploy
Dopo aver creato i file, abilita il modulo e aggiorna Magento:
bin/magento module:enable Vendor_Module
bin/magento setup:upgrade
bin/magento cache:flush
In modalità produzione o con compilazione DI abilitata, ricorda anche:
bin/magento setup:di:compile
bin/magento setup:static-content:deploy
Debug e troubleshooting
- L’observer non parte: verifica il nome dell’evento, la posizione del file
events.xml(area corretta), e che il modulo sia abilitato. - Errori di classe non trovata: controlla namespace, autoload (path corretto), e maiuscole/minuscole su filesystem case-sensitive.
- Dati mancanti nel payload: l’evento può variare; logga le chiavi dell’evento e controlla dove viene dispatchato.
- Performance lente: evita chiamate esterne sincrone, riduci I/O e log eccessivi, sposta su coda dove possibile.
Checklist finale
- Modulo registrato (
registration.php) - Modulo dichiarato (
etc/module.xml) - Observer registrato su evento corretto (
events.xmlnell’area corretta) - Classe observer implementa
ObserverInterfacee gestisce payload nullo - Cache e upgrade eseguiti, DI compilata se necessario
Con questa struttura puoi sviluppare observer robusti, testabili e coerenti con le best practice Magento 2, riducendo il rischio di conflitti e mantenendo performance e manutenibilità.