Claudiu Persoiu

Blog-ul lui Claudiu Persoiu


Archive for the ‘observer’ tag

Suprascriere si dezactivare Observere in Magento

with 2 comments

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:

=> "global"(zona)
=> "controller_action_predispatch"(event)
=> "adminnotification"(identificator)

apoi:

=> "adminhtml"(zona)
=> "controller_action_predispatch"(event)
=> "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:

...
  <adminhtml>
...
    <events>
      <controller_action_predispatch>
        <observers>
          <adminnotification>
            <class>adminnotification/observer</class>
            <method>preDispatch</method>
          </adminnotification>
        </observers>
      </controller_action_predispatch>
    </events>
...
  </adminhtml>
...

Din exemplul de mai sus putem vedea:
– zona: adminhtml
– event: controller_action_predispatch
– identificator: adminnotification

Fisierul de activare a modulului va fi: app/etc/modules/CP_AdminNotification.xml

<?xml version="1.0"?>
<config>
  <modules>
    <CP_AdminNotification>
      <active>true</active>
      <codePool>local</codePool>
      <depends>
        <Mage_AdminNotification/>
      </depends>
    </CP_AdminNotification>
  </modules>
</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:

<?xml version="1.0"?>
<config>
  <modules>
    <CP_AdminNotification>
      <version>0.0.1</version>
    </CP_AdminNotification>
  </modules>
  <global>
    <models>
      <cp_adminnotification>
        <class>CP_AdminNotification_Model</class>
      </cp_adminnotification>
    </models>
  </global>
  <adminhtml>
    <events>
      <controller_action_predispatch>
        <observers>
          <adminnotification>
            <class>cp_adminnotification/observer</class>
            <method>overwrittenPreDispatch</method>
          </adminnotification>
        </observers>
      </controller_action_predispatch>
    </events>
  </adminhtml>
</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.

<?php

class CP_AdminNotification_Model_Observer {

  public function overwrittenPreDispatch(Varien_Event_Observer $observer) {
    // noua logica din observer
  }
}

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:

<?xml version="1.0"?>
<config>
...
  <adminhtml>
    <events>
      <controller_action_predispatch>
        <observers>
          <adminnotification>
            <type>disabled</type>
          </adminnotification>
        </observers>
      </controller_action_predispatch>
    </events>
  </adminhtml>
</config>

Written by Claudiu Persoiu

17 May 2012 at 9:59 PM

Posted in Magento,PHP

Tagged with ,

Magento – Create a custom shopping cart price rule

with 2 comments

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 = 10

Discountul rezultat:
– 0% prod 1
– 10% prod 2

– 50% prod 6
– 50% prod 7

Primul pas este activarea modulului cu fisierul: app/etc/modules/CP_ProductNrDiscount.xml

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <CP_ProductNrDiscount>
            <active>true</active>
            <codePool>local</codePool>
        </CP_ProductNrDiscount>
    </modules>
</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.

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <CP_ProductNrDiscount>
            <version>0.0.1</version>
        </CP_ProductNrDiscount>
    </modules>
    <global>
        <models>
            <productnrdiscount>
                <class>CP_ProductNrDiscount_Model</class>
            </productnrdiscount>
        </models>
        <events>
            <salesrule_validator_process>
                <observers>
                    <productnrdiscount>
                        <type>model</type>
                        <class>productnrdiscount/observer</class>
                        <method>salesruleValidatorProcess</method>
                    </productnrdiscount>
                </observers>
            </salesrule_validator_process>
        </events>
    </global>
    <adminhtml>
        <events>
            <adminhtml_block_salesrule_actions_prepareform>
            <observers>
                <productnrdiscount>
                    <type>model</type>
                    <class>productnrdiscount/observer</class>
                <method>adminhtmlBlockSalesruleActionsPrepareform</method>
                </productnrdiscount>
            </observers>
            </adminhtml_block_salesrule_actions_prepareform>
        </events>
    </adminhtml>
</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.

<?php
/**
 * Number of product discount module
 *
 * @author Claudiu Persoiu http://blog.claudiupersoiu.ro
 */
class CP_ProductNrDiscount_Model_Observer {

    // Noul tip de regula
    const PRODUCT_NR_DISCOUNT = 'product_nr_discount';

    /**
     * Adaugare nou tip de regula in meniul de administrare
     *
     * @param Varien_Event_Observer $observer
     */
    public function adminhtmlBlockSalesruleActionsPrepareform
              (Varien_Event_Observer $observer) {
        // Extragem campul din formular
    	$field = $observer->getForm()->getElement('simple_action');
        // Extragem valorile campului
        $options = $field->getValues();
        // Adaugam noua valoare
        $options[] = array(
            'value' => self::PRODUCT_NR_DISCOUNT,
            'label' => 'Product Number Discount'
        );
        // Setare camp
        $field->setValues($options);
    }

    /**
     * Aplicare discount
     * Discountul se va aplica la minim 2 produse progresiv cate un "step" pentru
     * fiecare produs, unde un "step" este discountul maxim / numarul de produse
     * pe care se aplica.
     *
     * @param Varien_Event_Observer $observer
     */
    public function salesruleValidatorProcess(Varien_Event_Observer $observer) {

        // $item typeof Mage_Sales_Model_Quote_Item
        $item = $observer->getEvent()->getItem();
        // $rule typeof Mage_SalesRule_Model_Rule
        $rule = $observer->getEvent()->getRule();

        // Numarul de produse de acest fel
        $qty = $item->getQty();

        // Trebuie verificat ce tip de regula este, pentru a izola tipul
        // nostu de regula
        if($rule->getSimpleAction() == self::PRODUCT_NR_DISCOUNT && $qty > 1) {

            // Detalii regula
            $discountAmount = $rule->getDiscountAmount();
            $discountQty = $rule->getDiscountQty();

            // Step de discount
            $step = $discountAmount/$discountQty;

            // Calcul discount
            $discount = 0;
            for($i = 1; $i < $qty; $i++) {
			    $itemDiscount = $i * $step;
                // Daca discountul este mai mare decat discountul maxim
                // atunci se foloseste discount maxim
                if($itemDiscount > $discountAmount) {
                    $itemDiscount = $discountAmount;
                }

                $discount += $itemDiscount;
            }

            // Discountul propriuzis
            $totalDiscountAmount = ($item->getPrice() * $discount)/100;

            // Discount in procente pentru fiecare quote item
            $item->setDiscountPercent($discount / $qty);

            // Setare discount efectiv, practic aceasta este valoarea de discount
            $result = $observer->getResult();
            $result->setDiscountAmount($totalDiscountAmount);
            $result->setBaseDiscountAmount($totalDiscountAmount);

        }
    }

}

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.

Written by Claudiu Persoiu

8 March 2012 at 9:37 PM

PHP observer pattern si SPL

without comments

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:

class Actiune {
    private $val;
    function __construrct() {
        // ceva cod in constructor
    }

    function change($val) {
        $this->val = $val;
    }
}

De fiecare data cand se face modifica $val vrem sa se apeleze o metoda a unui obiect “observator”:

class Actiune {
    private $val;
    function __construrct() {
        // ceva cod in constructor
    }

    function change($val, $observator) {
        $this->val = $val;
        $observator->update($this);
    }
}

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:

diagrama

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:

/**
 * clasa care trebuie urmarita
 */
class Actiune implements SplSubject {
    private $observatori = array();
    private $val;

    /**
     * metoda atasare obiect observator
     *
     * @param SplObserver $observator
     */
    function attach(SplObserver $observator) {
        $this->observatori[] = $observator;
    }

    /**
     * metoda deatasare obiect observator
     *
     * @param SplObserver $observator
     */
    function detach(SplObserver $observator) {
        $observatori = array();
        foreach($this->observatori as $observatorul) {
            if($observatorul != $observator) $observatori[] = $observatorul;
        }
        $this->observatori = $observatori;
    }

    /**
     * metoda care notifica obiectele de tip observator
     */
    function notify() {
        foreach($this->observatori as $observator) {
            $observator->update($this);
        }
    }

    /**
     * metoda care face modificarea in clasa
     *
     * @param int $val
     */
    function update($val) {
        echo 'facem update...
';
        $this->val = $val;
        $this->notify();
    }

    /**
     * metoda publica care intoarce statusul obiectului
     *
     * @return int
     */
    function getStatus() {
        return $this->val;
    }
}

/**
 * o clasa observator
 */
class Observator implements SplObserver {
    function update(SplSubject $subiect) {
        echo $subiect->getStatus();
    }
}

// instanta observator
$observator = new Observator();

// instanta subiect
$subiect = new Actiune();

// atasare observator la subiect
$subiect->attach($observator);

// update subiect
$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.

Written by Claudiu Persoiu

4 August 2009 at 6:52 PM