-
Uneori trebuie sa suprascriem un observator (observer). Prima metoda care vine in general in minte este suprascrierea modelului. In general se numeste Observer.php, pentru ca aceasta este “best practice”.
Dar NU, nu trebuie suprascris modelul. Oricum Observer.php nu extinde nimic si in general contine toti observatorii pentru modul, deci nu poti suprascrie in mai multe module acelasi observer.
Cum functioneaza?
In magento cand se adauga un nou observer, acesta trebuie sa aiba un identificator unic. Acest identificator este cheia!De fapt, mai este un element: “zona”. Cand se face Mage::dispatchEvent(…) se vor rula evenimentele dupa “zona” si dupa “identificator”.
De exemplu sistemul de notificari din admin, care se leaga de evenimentul “controller_action_predispatch”, va rula:
1=> "global"(zona) 2=> "controller_action_predispatch"(event) 3=> "adminnotification"(identificator)
apoi:
1=> "adminhtml"(zona) 2=> "controller_action_predispatch"(event) 3=> "adminnotification"(identificator)
Daca era vorba de un event in frontend, ar fi fost: “global” apoi “frontend”.
Suprascrierea
Suprascrierea, de fapt, consta intr-un observer definit in aceeasi zona de config ca evenimentul initial(global, frontend sau adminhtml), atasat la acelasi eveniment si cu acelasi identificator cu cel initial (ex: adminnotification).Sa zicem ca trebuie sa suprascriem “adminnotification”. Acest observer se afla in modulul Mage/AdminNotification. Identificatorul unic este in etc/config.xml:
1... 2 <adminhtml> 3... 4 <events> 5 <controller_action_predispatch> 6 <observers> 7 <adminnotification> 8 <class>adminnotification/observer</class> 9 <method>preDispatch</method> 10 </adminnotification> 11 </observers> 12 </controller_action_predispatch> 13 </events> 14... 15 </adminhtml> 16...
Din exemplul de mai sus putem vedea:
– zona: adminhtml
– event: controller_action_predispatch
– identificator: adminnotificationFisierul de activare a modulului va fi: app/etc/modules/CP_AdminNotification.xml
1<?xml version="1.0"?> 2<config> 3 <modules> 4 <CP_AdminNotification> 5 <active>true</active> 6 <codePool>local</codePool> 7 <depends> 8 <Mage_AdminNotification/> 9 </depends> 10 </CP_AdminNotification> 11 </modules> 12</config>
Am adaugat si dependinte pentru ca fara acel modul, modulul de fata este iuntil.
Este “best practice” sa denumesti un modul suprascris la fel ca modulul initial.
Fisierul de configurare pentru modul va contine practic tot ce avem nevoie pentru suprascriere: zona, event si identificator. Fisierul se afla in app/code/local/CP/AdminNotification/etc/config.xml:
1<?xml version="1.0"?> 2<config> 3 <modules> 4 <CP_AdminNotification> 5 <version>0.0.1</version> 6 </CP_AdminNotification> 7 </modules> 8 <global> 9 <models> 10 <cp_adminnotification> 11 <class>CP_AdminNotification_Model</class> 12 </cp_adminnotification> 13 </models> 14 </global> 15 <adminhtml> 16 <events> 17 <controller_action_predispatch> 18 <observers> 19 <adminnotification> 20 <class>cp_adminnotification/observer</class> 21 <method>overwrittenPreDispatch</method> 22 </adminnotification> 23 </observers> 24 </controller_action_predispatch> 25 </events> 26 </adminhtml> 27</config>
Observerul ar trebui sa contina noua logica. Fisierul este in app/ code/local/CP/AdminNotification/Model/Observer.php, asa cum era si evident, dupa structura de mai sus.
1<?php 2 3class CP_AdminNotification_Model_Observer { 4 5 public function overwrittenPreDispatch(Varien_Event_Observer $observer) { 6 // noua logica din observer 7 } 8}
Dezactivarea
Dezactivarea este destul de similara cu suprascrierea, diferenta este in config si faptul ca nu mai este nevoie de un fisier observer, pentru ca nu mai exista o noua logica.Noul fisier de config.xml este:
1<?xml version="1.0"?> 2<config> 3... 4 <adminhtml> 5 <events> 6 <controller_action_predispatch> 7 <observers> 8 <adminnotification> 9 <type>disabled</type> 10 </adminnotification> 11 </observers> 12 </controller_action_predispatch> 13 </events> 14 </adminhtml> 15</config>
-
Acesta nu este un tutorial despre cum se seteaza o regula de tip Shopping Cart Price Rule in Magento, dar despre cum se implementeaza una noua.
Un tip nou de regula in Magento presupune doua lucuri:
– modificarea admin-ului pt a adauga noua regula folosind un observer pentru adminhtml_block_salesrule_actions_prepareform,
– un mod de aplicare pentru noua regula folosind un observer pentru salesrule_validator_process.Sa luam un exemplu. Sa zicem ca exista o regula de tip Shopping Cart Price Rule care ofera discount diferit in functie de numarul de produse din cos. Se va calcula o valoare de incrementare pentru fiecare pas ($step). Primul produs nu va primi nici un discount, al doilea produs va primi un discount de $step, al doilea produs un discount de 2*$step, pana se ajunge la valoarea maxima de discount. Urmatoarele produse vor avea discount maxim. Ex:
Discount Amount = 50
Discount Qty = 5
Step = Discount Amount / Discount Qty = 10Discountul rezultat:
– 0% prod 1
– 10% prod 2
…
– 50% prod 6
– 50% prod 7Primul pas este activarea modulului cu fisierul: app/etc/modules/CP_ProductNrDiscount.xml
1<?xml version="1.0" encoding="UTF-8"?> 2<config> 3 <modules> 4 <CP_ProductNrDiscount> 5 <active>true</active> 6 <codePool>local</codePool> 7 </CP_ProductNrDiscount> 8 </modules> 9</config>
Primul observer, adminhtml_block_salesrule_actions_prepareform, trebuie sa fie in config in zona “adminhtml”, pentru ca se va aplica admin-ul. Acest observer are acces la formularul din admin, permitand modificarea acestuia.
Al doilea observer, salesrule_validator_process, poate sa fie in zona de “frontend” sau “global” din config. Daca este in “frontend” se va aplica doar in partea de frontend, daca este in global acesta se va aplica si in backend daca este nevoie. In general global este necesar atunci cand se fac operatiuni pe cart din backend.
1<?xml version="1.0" encoding="UTF-8"?> 2<config> 3 <modules> 4 <CP_ProductNrDiscount> 5 <version>0.0.1</version> 6 </CP_ProductNrDiscount> 7 </modules> 8 <global> 9 <models> 10 <productnrdiscount> 11 <class>CP_ProductNrDiscount_Model</class> 12 </productnrdiscount> 13 </models> 14 <events> 15 <salesrule_validator_process> 16 <observers> 17 <productnrdiscount> 18 <type>model</type> 19 <class>productnrdiscount/observer</class> 20 <method>salesruleValidatorProcess</method> 21 </productnrdiscount> 22 </observers> 23 </salesrule_validator_process> 24 </events> 25 </global> 26 <adminhtml> 27 <events> 28 <adminhtml_block_salesrule_actions_prepareform> 29 <observers> 30 <productnrdiscount> 31 <type>model</type> 32 <class>productnrdiscount/observer</class> 33 <method>adminhtmlBlockSalesruleActionsPrepareform</method> 34 </productnrdiscount> 35 </observers> 36 </adminhtml_block_salesrule_actions_prepareform> 37 </events> 38 </adminhtml> 39</config>
Al doilea observer trebuie sa fie atasat la “frontend” sau “global”, daca trebuie sa ruleze doar in frontend sau si in backend.
Asa cum se poate vedea mai sus, trebuie facut un model Observer care sa aiba cele doua metode care modifica admin-ul si aplica discountul.
1<?php 2/** 3 * Number of product discount module 4 * 5 * @author Claudiu Persoiu https://blog.claudiupersoiu.ro 6 */ 7class CP_ProductNrDiscount_Model_Observer { 8 9 // Noul tip de regula 10 const PRODUCT_NR_DISCOUNT = 'product_nr_discount'; 11 12 /** 13 * Adaugare nou tip de regula in meniul de administrare 14 * 15 * @param Varien_Event_Observer $observer 16 */ 17 public function adminhtmlBlockSalesruleActionsPrepareform 18 (Varien_Event_Observer $observer) { 19 // Extragem campul din formular 20 $field = $observer->getForm()->getElement('simple_action'); 21 // Extragem valorile campului 22 $options = $field->getValues(); 23 // Adaugam noua valoare 24 $options[] = array( 25 'value' => self::PRODUCT_NR_DISCOUNT, 26 'label' => 'Product Number Discount' 27 ); 28 // Setare camp 29 $field->setValues($options); 30 } 31 32 /** 33 * Aplicare discount 34 * Discountul se va aplica la minim 2 produse progresiv cate un "step" pentru 35 * fiecare produs, unde un "step" este discountul maxim / numarul de produse 36 * pe care se aplica. 37 * 38 * @param Varien_Event_Observer $observer 39 */ 40 public function salesruleValidatorProcess(Varien_Event_Observer $observer) { 41 42 // $item typeof Mage_Sales_Model_Quote_Item 43 $item = $observer->getEvent()->getItem(); 44 // $rule typeof Mage_SalesRule_Model_Rule 45 $rule = $observer->getEvent()->getRule(); 46 47 // Numarul de produse de acest fel 48 $qty = $item->getQty(); 49 50 // Trebuie verificat ce tip de regula este, pentru a izola tipul 51 // nostu de regula 52 if($rule->getSimpleAction() == self::PRODUCT_NR_DISCOUNT && $qty > 1) { 53 54 // Detalii regula 55 $discountAmount = $rule->getDiscountAmount(); 56 $discountQty = $rule->getDiscountQty(); 57 58 // Step de discount 59 $step = $discountAmount/$discountQty; 60 61 // Calcul discount 62 $discount = 0; 63 for($i = 1; $i < $qty; $i++) { 64 $itemDiscount = $i * $step; 65 // Daca discountul este mai mare decat discountul maxim 66 // atunci se foloseste discount maxim 67 if($itemDiscount > $discountAmount) { 68 $itemDiscount = $discountAmount; 69 } 70 71 $discount += $itemDiscount; 72 } 73 74 // Discountul propriuzis 75 $totalDiscountAmount = ($item->getPrice() * $discount)/100; 76 77 // Discount in procente pentru fiecare quote item 78 $item->setDiscountPercent($discount / $qty); 79 80 // Setare discount efectiv, practic aceasta este valoarea de discount 81 $result = $observer->getResult(); 82 $result->setDiscountAmount($totalDiscountAmount); 83 $result->setBaseDiscountAmount($totalDiscountAmount); 84 85 } 86 } 87 88}
Acest observer se va aplica la fiecare request cand exista module in cart pentru care se aplica regula. Daca discountul trebuie sa se aplice doar pentru anumite produse acestea se pot filtra folosind sesiunea de “Conditions” a regulii definite, asa cum este normal.
-
Observer pattern se refera la un obiect “subiect” care are asociata o lista de obiecte dependente, numite observatori, pe care le apeleaza automat de fiecare data cand se intampla o actiune.
Un mic exemplu de ce se foloseste:
– sa zicem ca avem o clasa pe care se fac niste modificari:
1class Actiune { 2 private $val; 3 function __construrct() { 4 // ceva cod in constructor 5 } 6 7 function change($val) { 8 $this->val = $val; 9 } 10}
De fiecare data cand se face modifica $val vrem sa se apeleze o metoda a unui obiect “observator”:
1class Actiune { 2 private $val; 3 function __construrct() { 4 // ceva cod in constructor 5 } 6 7 function change($val, $observator) { 8 $this->val = $val; 9 $observator->update($this); 10 } 11}
Teoretic nu suna rau, dar cu cat sunt mai multe metode cu atat exista o dependenta mai mare si de fiecare data cand se adauga un obiect nou de tip observator trebuie modificata clasa, avand toate sansele sa rezulte intr-un haos aproape imposibil de portat.
Acum observator pattern arata cam asa:
SPL (Standard PHP Library), care este bine cunoscut pentru iteratorii definiti, vine cu interfetele SplSubject si SplObserver, pentru subiect respectiv observator.
O implementare arata cam asta:
1/** 2 * clasa care trebuie urmarita 3 */ 4class Actiune implements SplSubject { 5 private $observatori = array(); 6 private $val; 7 8 /** 9 * metoda atasare obiect observator 10 * 11 * @param SplObserver $observator 12 */ 13 function attach(SplObserver $observator) { 14 $this->observatori[] = $observator; 15 } 16 17 /** 18 * metoda deatasare obiect observator 19 * 20 * @param SplObserver $observator 21 */ 22 function detach(SplObserver $observator) { 23 $observatori = array(); 24 foreach($this->observatori as $observatorul) { 25 if($observatorul != $observator) $observatori[] = $observatorul; 26 } 27 $this->observatori = $observatori; 28 } 29 30 /** 31 * metoda care notifica obiectele de tip observator 32 */ 33 function notify() { 34 foreach($this->observatori as $observator) { 35 $observator->update($this); 36 } 37 } 38 39 /** 40 * metoda care face modificarea in clasa 41 * 42 * @param int $val 43 */ 44 function update($val) { 45 echo 'facem update... 46'; 47 $this->val = $val; 48 $this->notify(); 49 } 50 51 /** 52 * metoda publica care intoarce statusul obiectului 53 * 54 * @return int 55 */ 56 function getStatus() { 57 return $this->val; 58 } 59} 60 61/** 62 * o clasa observator 63 */ 64class Observator implements SplObserver { 65 function update(SplSubject $subiect) { 66 echo $subiect->getStatus(); 67 } 68} 69 70// instanta observator 71$observator = new Observator(); 72 73// instanta subiect 74$subiect = new Actiune(); 75 76// atasare observator la subiect 77$subiect->attach($observator); 78 79// update subiect 80$subiect->update(5);
Ce mi se pare mie ciudat este ca nu exista o documentatie pentru aceste interfetele din SPL. Chiar pe site-ul zend exista un articol PHP Patterns: The Observer Pattern care nu foloseste SPL, iar asta in masura in care exista documentatie pentru namespaces chiar inainte sa apara PHP 5.3.