-
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.
-
PHP 5.4 a fost lansat!
Chiar daca acuma este yesterday news… literalmente, ieri 1 martie a fost lansat.
Lista de schimbari este disponibila pe php.net.
Ce regret este ca nici in aceasta versiune nu exista scalar type hinting. Singura modificare la type hinting a fost adaugarea cuvantului “callable”, despre care am mai vorbit cand a fost vorba de closures in PHP 5.4.
Un alt lucru interesant este ca de data asta chiar au fost scoase register_globals si magic_quotes_gpc, deci vechile aplicatii de PHP 4 nu mai au posibilitatea de a deveni compatibile cu ajutorul unor setari in php.ini.
De asemenea a fost adaugata functia hex2bin(), evident nu este foarte importanta,dar este interesant ca bin2hex() exista de la PHP 4. 🙂
-
Exista momente cand ai nevoie sa vezi stack trace-ul, sa stii cum ai ajuns pana la un anumit punct. PHP are doua functii native pentru a realiza acest lucru: debug_backtrace() si debug_print_backtrace. Prima intoarce un array iar a doua afisaza stacktrace-ul pe ecran.
Problema in sine este ca acestea trebuie customizate pentru Magento, pentru ca este foarte posibil cand rulezi debug_backtrace() sa ramai fara memorie inainte sa poti trimite rezultatul catre un fisier de log.
Magento are o functie nativa pentru acest lucru: Varien_Debug::backtrace([bool $return = false], [bool $html = true], [bool $withArgs = true]). Pentru a trimite catre log stacktrace-ul se apeleaza pur si siplu:
1Mage::log(Varien_Debug::backtrace(true, false));
Aceasta tehnica este foarte utila in momentul in care vrei sa vezi de unde se initializeaza anumite obiecte si ce metode se ruleaza pana in acel moment.
-
Notiunea de Closure, a fost introdusa in PHP 5.3, o data cu o noua sintaxa, “mai traditionala” pentru functiile anonime.
PHP 5.3
In 5.3, un closure se baza pe termenul “use”, care transmite anumite variabile functiei anonime, transformand-o intr-un closure.
Problema este ca functia anonima nu va avea acces decat la variabilele trimise folosind “use”. In cazul obiectelor ele sunt trimise prin referinta, dar in cazul variabilelor scalare (int, string, etc.) acestea se transmit prin valoare, asa cum face implicit in PHP 5+:
1$scalar = 5; 2 3$closure = function () use ($scalar) { 4 return 'Scalar: ' . $scalar . PHP_EOL; 5}; 6 7echo $closure(); // Scalar: 5 8 9$scalar = 7; 10 11echo $closure(); // Scalar: 5
O alta problema este ca nu se pot trimite $this in cadrul obiectelor, deci doar proprietatile sau metodele care sunt publice nu pot fi accesate de closure.
PHP 5.4
In PHP 5.4 folosirea cuvantului “use” este optionala, iar tot mediul in care functia anonima a fost creata este disponibil in interiorul functiei.
Avantajul este ca atunci cand functia anonima este generata in interiorul unei alte functii, sau a unei metode, functia anonima va putea accesa mediul in care aceasta a fost creata chiar si dupa terminarea executiei acestuia. Obiectele din mediul creat se vor dezaloca de abea dupa ce se ultima referinta catre closure se va sterge:
1class testClass { 2 3 private $changeableVar = 1; 4 private $bigVar; 5 6 public function __construct() { 7 // Allocate a big variable so we can see the changes in memory 8 $this->bigVar = str_repeat("BigWord", 5000); 9 } 10 11 /** 12 * O metoda care intoarce un closure 13 */ 14 public function closure() { 15 16 return function () { 17 // Display the value of a private property of the object 18 echo 'Private property: ' . $this->changeableVar.PHP_EOL; 19 20 // Change the value of a private property of the object 21 $this->changeableVar = 2; 22 }; 23 } 24 25 /** 26 * O metoda care afisaza o variabila privata 27 */ 28 public function showChangeableVar() { 29 echo 'Private property in method: ' . $this->changeableVar.PHP_EOL; 30 } 31 32} 33 34// Memoria inainte sa fie alocat obiectul 35echo "Memory: " . memory_get_usage() . PHP_EOL; // Memory: 229896 36 37// Creare obiect 38$testObj = new testClass(); 39 40// Creare closure 41$closure = $testObj->closure(); 42 43// Executie closure 44$closure(); // Private property: 1 45 46// Afisare valoare curenta a proprietatii private 47$testObj->showChangeableVar(); // Private property in method: 2 48 49// Memoria inainte sa fie desalocat obiectul 50echo "Memory: ". memory_get_usage() . PHP_EOL; // Memory: 266240 51 52// Dezalocare obiect 53unset($testObj); 54 55// Memoria dupa ce a fost dezalocat obiectul, nu este o diferenta mare in memorie 56echo "Memory: ". memory_get_usage() . PHP_EOL; // Memory: 266152 57 58// Executie closure dupa ce obiectul in care a fost creata a fost dezalocat 59echo $closure(); // Private property: 2 60 61// Dezalocat closure si o data cu el mediul in care a fost generat 62unset($closure); 63 64// Memoria dupa ce a fost stearsa ultima referinta catre obiect 65echo "Memory: " . memory_get_usage() . PHP_EOL; // Memory: 230416
Callable type hinting
Inca un nou lucru introdus legat de closures introdus in PHP 5.4 este un nou “type hint”: “callable”. De fapt “callable” se refera la orice functie anonima, chiar si la un nou mod de a apela o metoda a unui obiect:
1<?php 2// O functie care foloseste type hinting 3function typeHinting(callable $a) { 4 echo $a() . PHP_EOL; 5} 6 7// Un closure 8$closure = function () { 9 return __FUNCTION__; 10}; 11 12// Apelare functie cu type hinting cu un closure ca argument 13typeHinting($closure); // {closure} 14 15class testClass { 16 public function testMethod() { 17 return __METHOD__; 18 } 19} 20 21// Un obiect de test 22$testObj = new testClass(); 23 24// Noua forma de apelare a unei metode dintr-un obiect 25$objCallable = array($testObj, 'testMethod'); 26 27// Apelare functie type hinting cu noua forma de apelare ca argument 28typeHinting($objCallable); // testClass::testMethod
Cred ca de abea acum este momentul sa spunem ca PHP suporta closures cu adevarat!
-
Intr-una din aventurile mele prin codul Magento, m-am lovit de urmatoarea problema: trebuia sa adaug un link la breadcrumb.
Cum documentatia nu e foarte bogata, dupa putin debug-ing (nu foarte mult), am ajuns in core la Mage_Page_Block_Html_Breadcrumbs.
Metoda este foarte auto-explicativa: addCrumb($crumbName, $crumbInfo, $after = false). Daca tot eram acolo am zis sa arunc un ochi in ea:
1function addCrumb($crumbName, $crumbInfo, $after = false) 2{ 3 $this->_prepareArray($crumbInfo, array('label', 'title', 'link', 'first', 'last', 'readonly')); 4 if ((!isset($this->_crumbs[$crumbName])) || (!$this->_crumbs[$crumbName]['readonly'])) { 5 $this->_crumbs[$crumbName] = $crumbInfo; 6 } 7 return $this; 8}
Ce este interesant este parametul $after, dupa cum se poate observa, desi are chiar si o valoare default, nu se foloseste nicaieri. In rest functia functioneaza asa cum este de asteptat, probabil de asta nu se plange lumea atat de des.