Extension attributes in Magento 2 is een nieuwe toevoeging die het mogelijk maakt om (complexe) data aan entiteiten toe te voegen als attributen. Ze kunnen via XML gedefinieerd worden voor classes die de ExtensibleDataInterface implementeren, in het bestand extension_attributes.xml. Voor de webshop beheerders bestaat de mogelijkheid om attributen aan producten toe te voegen via de backend, in Magento 2 Enterprise ook voor klanten en adressen, dus wat voegen extension attributes toe wat niet bereikt kan worden met de attributen toegevoegd via de backend of install scripts?

  • Restricties toevoegen op basis van ACL resources;
  • Een complex data type als waarde hebben door middel van een interface;
  • Waardes uit een externe tabel joinen (en hier op kunnen filteren) in collecties via configuratie;
  • Data toevoegen aan core api interfaces.

Voorbeelden

In de Magento DevDocs worden voorbeelden gegeven van deze mogelijkheden door middel van het “stock_item” attribuut. (Magento_CatalogInventory module) Dit attribuut is een extension attribute voor de product data interface met het data-type StockItemInterface. Omdat de precieze voorraad van webshop producten concurrentiegevoelige informatie kan zijn is deze niet standaard beschikbaar via de api, een gebruiker heeft hiervoor toegang nodig tot de Magento_CatalogInventory::cataloginventory resource. De configuratie voor deze restrictie is te zien in de extension_attributes.xml van de CatalogInventory module;

<config> 
  <extension_attributes for="Path\To\Interface"> 
    <attribute code="name_of_attribute" type="datatype"> 
      <resources> 
        <resource ref="permission"/> 
      </resources> 
      <join reference_table="" reference_field="" join_on_field=""> 
        <field>fieldname</field> 
      </join> 
    </attribute> 
  </extension_attributes> 
</config>

Het in een collection joinen van waarden voor een extension attribuut komt niet in de core voor (behalve als voorbeeld). DevDocs gebruikt wel de stock_item als voorbeeld om te laten zien hoe de qty waarden te joinen (uit de cataloginventory_stock_item tabel).

<extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> 
  <attribute code="stock_item" type="Magento\CatalogInventory\Api\Data\StockItemInterface"> 
      <join reference_table="cataloginventory_stock_item" reference_field="product_id" join_on_field="entity_id"> 
        <field>qty</field> 
      </join> 
  </attribute> 
</extension_attributes>

Het opbouwen van deze join conditions gebeurt d.m.v de process functie van de JoinProcessor class (Magento\Framework\Api\ExtensionAttribute\JoinProcessor). Dit wordt echter niet in de collection classes gedaan maar voor specifieke entiteiten in de getItems functie van de service interface (repository).
Voor producten en customers gebeurt dit standaard in de getItems functie, bij de repository classes in de Magento_Sales module bijvoorbeeld niet.

Getten en setten van extension attributes

Het getten en setten van extension attributes gebeurt met de functies getExtensionAttributes en setExtensionAttributes. Voor de classes die extension attributes implementeren zal een gegenereerde interface en “extension class” (waar nodig) worden aangemaakt in var/generation. Deze interface is leeg als er geen attributen gedefinieerd zijn maar bevat anders de get/set functies van de attributen.
Zie bijvoorbeeld var/generation/Magento/Catalog/Api/Data/ProductExtension.php met daarin zoals verwacht de get/setStockItem functies, mits de voorraad aan staat en er een product pagina is bezocht of setup:di:compile is uitgevoerd.

Het setten van waarden van extension attributes gebeurt in de core op verschillende plekken, afhankelijk van de logica wanneer de attribuut data beschikbaar moet zijn. Voor de voorraad is dit bijvoorbeeld het bijvoegen van het volledige stock_item bij het laden van het product.
Dit gebeurt middels een after-plugin op de load functie;

public function afterLoad(\Magento\Catalog\Model\Product $product) 
{ 
   $productExtension = $product->getExtensionAttributes(); 
   if ($productExtension === null) { 
       $productExtension = $this->productExtensionFactory->create(); 
   } 
   // stockItem := \Magento\CatalogInventory\Api\Data\StockItemInterface 
   $productExtension->setStockItem($this->stockRegistry->getStockItem($product->getId())); 
   $product->setExtensionAttributes($productExtension); 
   return $product; 
}

Er dient wel (zoals hier) gecontroleerd te worden of de ExtensionInterface al bestaat om niet bestaande waardes te overschrijven of om setYourExternsionAttribute op null te callen. Een alternatief is om deze logica af te vangen in de vorm van een afterGetExtensionAttributes plugin.

Gebruik in de praktijk – een voorbeeld met meerdere nieuwe technieken

Extension attributes kunnen voor veel situaties gebruikt worden maar zijn misschien niet altijd de meest voor de hand liggende keuze. Verspreid over de core, DevDocs en uit voorbeelden in de sample modules kun je goede ideeën halen.

Een praktijkvoorbeeld waarbij meerdere Magento 2 technieken toegepast worden is de how-to om een veld toe te voegen aan de checkout. Deze zal vervolgens als extension attribute mee gestuurd worden met de verzendinformatie van de klant op het moment van opslaan.

In dit voorbeeld wordt gebruik gemaakt van een “Javascript mixin” in combinatie met de “mage/utils/wrapper” module om, vergelijkbaar met een around-plugin, data toe te voegen aan de extension attributes van het adres voordat de (ongewijzigde) functie zijn werk doet.
Op deze manier kan een niet-standaard veld gecommuniceerd worden vanuit de checkout, zonder dat er functies herschreven hoeven te worden, iets wat later misschien problemen oplevert met andere modules of een update van Magento.

De logica waarmee een veld kan worden toegevoegd via een LayoutProcessor / plugin en het toevoegen van de extra informatie via extension attributes zijn mooie voorbeelden van de beschikbare mogelijkheden voor het ontwikkelen van maatwerk in Magento 2, welke toegepast kunnen worden zonder dat dit ten koste gaat van compatibiliteit met andere modules.