Magento fundamentals: perché non serve usare (quasi mai) l'Object Manager
Lettura 4 minutiQuesto articolo inaugura una serie ispirata ad alcune conoscenze di base per lo sviluppo su Magento 2.
Potrebbero sembrare argomenti scontati dopo più di cinque anni dal rilascio della versione 2.0 e tanta documentazione disponibile ma purtroppo ci imbattiamo quotidianamente in codice di terze parti che dimostra che in realtà argomenti come questi sono tutt’altro che assodati.
Spoiler alert: se vi candidate come senior backend developer, aspettatevi domande tratte da questa serie di articoli durante il colloquio.
Cos’è l’Object Manager
L’Object Manager è un oggetto utilizzato internamente dal framework Magento per istanziare oggetti e passarli al costruttore di altre classi.
Il termine passare si può tradurre in inglese con inject e per questo motivo gli oggetti passati dall’Object Manager sono detti injectable object.
Gli oggetti passati ad un altro oggetto attraverso il suo costruttore costituiscono le cosiddette dipendenze dell’oggetto, per questo motivo si parla di dependency injection, comunemente abbreviata con l’acronimo DI.
Per approfondire: https://it.wikipedia.org/wiki/Dependency_injection
Vediamo un esempio.
Per permettere alla classe TheMostUselessClass
di accedere alla configurazione di sistema, dovremo dichiarare la dipendenza da ScopeConfigInterface
nel suo costruttore, come mostrato di seguito:
<?php declare(strict_types=1);
use \Magento\Framework\App\Config\ScopeConfigInterface;
class TheMostUselessClass
{
protected $config;
public function __construct(ScopeConfigInterface $config)
{
$this->config = $config
}
}
Si noti che ScopeConfigInterface
è una interfaccia e sappiamo che non è possibile istanziare interfacce.
L’Object Manager dovrà quindi istanziare una classe che implementa l’interfaccia ScopeConfigInterface
e per farlo si baserà su una mappa ricavata dai file di.xml
presenti a sistema.
Il principio per cui si preferisce dipendere da interfacce (o astrazioni) e non da classi concrete è chiamato dependency inversion principle e porta a scrivere codice maggiormente disaccoppiato.
Per approfondire: https://it.wikipedia.org/wiki/Principio_di_inversione_delle_dipendenze.
Non tutti gli oggetti sono injectable
Gli injectable object che l’Object Manager passa come dipendenze dovrebbero sempre essere oggetti di servizio, che utilizziamo per eseguire logica applicativa ma di cui difficilmente ci interessa modificare lo stato (con qualche eccezione) né tantomeno persisterlo.
Un esempio di oggetto di servizio è appunto l’istanza di una classe che implementa ScopeConfigInterface
, che ci consente di recuperare un valore di configurazione.
Un altro esempio comune è rappresentato dai repository, il cui scopo è quello di recuperare o persistere entità.
Proprio per la loro natura stateless, di default gli injectable object sono istanziati come singleton dall’Object Manager, vale a dire che la stessa istanza è passata a tutti oggetti che la richiedono come dipendenza.
Gli oggetti che invece devono essere istanziati come nuovi tutte le volte che li dobbiamo usare prendono il nome di newable object e l’esempio più classico è quello di una entità memorizzata nel database.
Questi oggetti non devono essere istanziati dall’Object Manager perché non costituiscono delle vere e proprie dipendenze. Per istanziarli si utilizzano degli oggetti di servizio chiamati factory.
I factory object sono oggetti di servizio e come tali possono essere istanziati dall’Object Manager, dichiarando una dipendenza nel costruttore.
Ecco un esempio:
<?php declare(strict_types=1);
use \Magento\Catalog\Api\Data\ProductInterfaceFactory;
class CreateFabulousProduct
{
protected $productFactory;
public function __construct(
ProductInterfaceFactory $productFactory
) {
$this->productFactory = $productFactory; // injectable object
}
public function execute(/* ... */): ProductInterface
{
$product = $this->productFactory->create(); // newable object
// ...initialize the properties of a fabulous product
return $product;
}
}
In Magento c’è una particolarità legata ai factory object: non occorre implementare le rispettive classi perché queste sono generate dal framework stesso (automaticamente se è impostato il developer mode o attraverso il comando /bin/magento setup:di:compile
se è impostato il production mode).
Quando usare l’Object Manager
Vediamo, infine, quali sono alcune eccezioni che giustificano il “quasi mai” nel titolo dell’articolo:
- se dobbiamo implementare un nostro factory (abbastanza raro ma può succedere), siamo costretti ad utilizzare l’Object Manager, passandolo una dipendenza da
\Magento\Framework\ObjectManagerInterface
al costruttore della nostra classe factory; - se scriviamo test automatici può servire utilizzare l’Object Manager; in questo caso solitamente ne ricaviamo una istanza con
\Magento\TestFramework\Helper\Bootstrap::getObjectManager()
; - se contribuiamo al core di Magento e dobbiamo aggiungere un parametro ad un costruttore, per non creare incompatibilità a ritroso, indichiamo i parametri come opzionali e, se non sono passati al costruttore, li inizializziamo con l’ObjectManager; in questo caso ne ricaviamo una istanza con
\Magento\Framework\App\ObjectManager::getInstance()
; - infine, una pratica tollerata è quella di utilizzare l’Object Manager in un frammento di codice temporaneo al fine di istanziare velocemente un service object. Qui trovate uno snippet che potete riutilizzare.
Conclusioni
Abbiamo dunque visto che in Magento non è quasi mai necessario utilizzare l’Object Manager perché:
- se l’istanza di cui abbiamo bisogno è un injectable object, possiamo ottenerla dichiarando una dipendenza nel costruttore;
- se l’istanza di cui abbiamo bisogno è un newable object, possiamo ottenerla utilizzando il rispettivo factory object.
I casi in cui occorre realmente utilizzare l’Object Manager sono rari: possiamo a questo punto analizzare del codice in cui sappiamo di averlo utilizzato e applicare i principi visti in questo articolo.
Risorse utili
- Magento 2 Developer Documentation - Dependency injection
- Magento 2 Developer Documentation - ObjectManager
- Magento Stack Exchange - How can I inject data into a class using di.xml?
- Magento 2 Contribution Guide - Adding a constructor parameter
Articolo scritto da
Alessandro Ronchi☝ Ti piace quello che facciamo? Unisciti a noi!