Categorieën
Magento Nieuws

Design Patterns Magento 2

In deze blog wil ik jullie meenemen in een aantal ontwikkeltechnieken die niet iedereen kent, maar waar wij bij Guapa, en als ontwikkelaars in het algemeen, dagelijks mee te maken hebben; Design Patterns.

Design Patterns zijn voorgeschreven, herbruikbare patronen in code die programmeurs gebruiken om problemen tijdens een ontwikkelproces op te lossen. Magento 1 en 2 gebruiken deze design patterns in overvloed, maar ze zijn voor minder ervaren ontwikkelaars qua structuur en werking soms lastig te begrijpen. In Magento worden de design patterns in dezelfde vormen gebruikt als in andere programmeertalen, alleen zijn de implementaties van de patterns hier en daar anders uitgeschreven om ervoor te zorgen dat het framework flexibel en uitbreidbaar blijft. In deze blog leg ik een aantal in Magento gebruikte design patterns uit die vaak voorkomen, verkeerd worden gebruikt of juist vaker gebruikt zouden mogen worden en vertel ik je waar en waarom je de patterns gebruikt.

{{cta(‘b54283a5-a9ee-4bd9-8afe-a36bf12980f4′,’justifycenter’)}}

Observer

Wat als je voor, tijdens of na de uitvoer van code in een functie wil inhaken om data toe te voegen of te manipuleren? Dit kan in Magento 2 via plugins, waar collega Simon-Jan van Til eerder over schreef, maar op selecte plekken ook met een observer. De observer is een van de meest bekende design patterns in Magento. Observers worden gebruikt om code op bepaalde punten uitbreidbaar te maken, acties uit te voeren en het mogelijk maken om data te manipuleren.

Een observer meldt zich aan op een bepaalde actie (event) middels een XML configuratie bestand (events.xml). Zodra er een dispatch voor dat event wordt uitgevoerd, zorgt de controller die alle acties en observers kent ervoor dat de functie van de observer wordt aangeroepen. Omdat elke module in Magento een eigen events.xml kan hebben, kan er dus zonder aanpassingen op de core ingehaakt worden op beschikbare punten. 

Waarom zijn er dan zowel observers als plugins? Design technisch heeft het de voorkeur om plugins te gebruiken boven observers omdat een plugin compleet los staat van het punt waar jouw code inhaakt, terwijl een event op een specifiek punt binnen de code inhaakt. Een plugin kan echter niet op private en protected functies worden geschreven, dit is naast een min, ook een pluspunt omdat je hiermee een andere developer laat weten dat dit de plekken zijn waar hij of zij veilig kan inhaken op jouw code. Om private en protected functies toch uit te laten kunnen breiden zonder dat een andere developer daar een rewrite voor moet schrijven biedt het dispatchen van een event uitkomst.

Singleton

Soms wil je als ontwikkelaar graag dat een instantie van een class tijdens de uitvoer van de code altijd hetzelfde blijft. Omdat je bijvoorbeeld wil dat elke keer wanneer de class aangeroepen wordt, deze dezelfde data bevat. Dit komt in Magento bijvoorbeeld voor bij klant- en winkelwagen sessies en heet een singleton.

De werking van een singleton is eigenlijk heel simpel. Door een private constructor te maken, voorkomt de programmeur dat er een instantie van de class kan worden gemaakt via de “new Class()” methode. In plaats daarvan wordt er een static functie aangemaakt die op zijn beurt een instantie van zichzelf kan maken en opslaat in een static variabele. Wanneer de static functie aangeroepen wordt, controleert deze of de variabele al is geset. Is dit het geval, dan returned de functie de instantie, zo niet, dan maakt de functie een nieuwe instantie aan, slaat deze op in zijn variabele en returned deze vervolgens.

Op deze manier krijg je elke keer dezelfde instantie van de class terug met dezelfde gedeelde data.

Magento zelf doet dit op een iets andere manier, de ObjectManager (die zelf weer een singleton is) regelt of je een singleton krijgt of een nieuwe instantie van het object.

Standaard krijg je een gedeelde instantie (een singleton), wil je dit niet, dan geef je dit aan via je di.xml door shared op “false” te zetten.

<type name=""MagentoWebapiControllerRestRouterRoute"" shared=""false"" />

Het is dus voor Magento niet nodig om zelf een singleton te schrijven, maar wel belangrijk om te weten hoe het pattern werkt.

Factory pattern

Stel; als programmeur wil je een storelocator bouwen, waar zowel fysieke- als online stores beschikbaar zijn. Beide objecten zijn stores en hebben gedeelde en daarnaast ook verschillende eigenschappen. Zowel een online store als een fysieke store kunnen bijvoorbeeld een adres hebben, terwijl een fysieke store openingstijden heeft die een online store mogelijk niet heeft.

Door middel van polymorfisme kun je aangeven dat de twee objecten van hetzelfde type zijn met dezelfde functies. Dit doe je door middel van een interface, een soort blauwdruk van eigenschappen methodes die elke class moet implementeren. Daarnaast kan elke class zijn eigen functies hebben.

Maar hoe zorg je er dan voor dat bij een fysieke store je een instantie krijgt van de class “Physical” en bij een online store een instantie van de class “Online”? Dat is waar de factory pattern om de hoek komt kijken.

De factory bepaald aan de hand van de di.xml of een eigen oplossing welke instantie moet worden teruggegeven wanneer je de factory vraagt om een instantie.

Mits je geen specifieke logica in je factory gebruikt, hoef je in Magento 2 niet zelf een factory class te schrijven. De objectmanager zorgt ervoor dat wanneer je een class als factory injecteert, deze een factory voor je genereert.

In Magento 2 worden factories daarnaast vooral gebruikt voor instanties van database-entities (models). Wil je bijvoorbeeld een nieuw product aanmaken, dan injecteer je Magento/Catalog/Model/ProductFactory in je class en roep je de “create()” functie van de factory aan. Je krijgt daarmee een nieuwe instantie van het bijbehorende model terug.

Proxy

Een nuttig, maar wat minder bekend pattern is de proxy. Het doel van de proxy is om een instantie te geven van een class, met toegang tot alle bijbehorende functies, zonder dat deze al compleet is ingeladen. Een vertraagde instantie totdat deze dus daadwerkelijk gebruikt moet worden. Dit kan handig zijn wanneer je gebruik maakt van classes die zware

dependencies hebben tijdens het inladen van de class in de constructor, bijvoorbeeld een repository die een connectie naar de database opzet.

In de UML hiernaast zie je dat de proxy class erft van de class: “Class”. De proxy class wordt gebruikt als instantie. Een implementatie van classFunction() zorgt ervoor dat er een instantie van Class gemaakt wordt, indien deze er nog niet is via _getSubject(). Deze roept vervolgens classFunction() van Class aan.

Net als factories kunnen de proxy-classes automatisch gegenereerd worden wanneer ze gebruikt worden als referentie. Het enige wat je feitelijk nodig hebt is een verwijzing in een di.xml bestand. Dependency Injection zorgt voor de rest.

<type name=""MagentoFrameworkCacheConfigData"">
<arguments>
<argument name=""cacheId"" xsi_type=""string"">config_cache</argument>
<argument name=""reader"" xsi_type=""object"">MagentoFrameworkCacheConfigReaderProxy</argument>
</arguments>
</type>
protected function _getSubject()
{
if (!$this->_subject) {
$this->_subject = true === $this->_isShared
? $this->_objectManager->get($this->_instanceName)
: $this->_objectManager->create($this->_instanceName);
}
return $this->_subject;
}
public function read($scope = null)
{
return $this->_getSubject()->read($scope);
}

Decorator

In de design pattern van de factory had ik het al even over inheritance (overerving). Handig wanneer je niet zo heel veel classes hebt. Wanneer je meer classes krijgt, kan dit echter al snel uitgroeien tot een spaghetti van code en is de uitwerking statisch, omdat je buiten de classes die onder de inheritance vallen, geen functies meer aan kunt toevoegen door bijvoorbeeld een maatwerk module zonder de core of een module aan te passen. Dat is juist iets wat we niet willen, daarom is het decorator pattern zo handig. In Magento wordt dit bijvoorbeeld al geïmplementeerd in de cache laag, zodat zonder dat de werking van de core code van de cache wordt aangetast, een eigen cache-type geschreven kan worden. Het decorator pattern zorgt ervoor dat je classes kan uitbreiden, zonder dat je daarvoor de class zelf hoeft te wijzigen.

{{cta(‘b0655b8c-76f3-41ac-a730-e843d11de197′,’justifycenter’)}}

Hoe werkt dat precies? In dit voorbeeld krijgt de StoreDecorator class een instantie mee van Store. Omdat elke andere class afstamt van StoreDecorator en de functies bevat van Store, kun je via de decorator zowel de functies van de Store class als de functies van de class die je op dat moment hebt geïnstantieerd aanroepen.

Hierdoor hoeven er bij uitbreiding geen functionaliteiten te worden aangepast en zijn de classes toch uitbreidbaar, met zoveel classes en functies als er maar nodig zijn.

Nieuwe technieken

Hoewel een aantal van de in deze blog benoemde patterns ook al aanwezig waren in Magento 1, is het met de toevoeging van de object manager en de extra patterns in Magento 2 eenvoudiger geworden om gestructureerde en modernere code te schrijven, een belangrijke reden om een overstap naar Magento 2 te overwegen.