Claudiu Persoiu

JavaScript Games

Blog-ul lui Claudiu Persoiu


Overwriting and deactivating Observers in Magento

without comments

Sometimes we need to overwrite an observer. The first way that usually comes in mind is overwriting the model. Usually is named Observer.php, because this is the “best practice”.

And NO, you don’t have to overwrite the model. Observer.php doesn’t extend anything anyway and usually contains all of the module’s observers, so you can’t overwrite the same observer in more than one module.

How it works?
In magento when a new observer is added, it must have an unique identifier. This identifier is the key!

Actually, there is another element: “area”. When Mage::dispatchEvent(…) is performed, events will be dispatched using “area” and “identifier”.

For example, the admin notification system, which is observing “controller_action_predispatch”, will run:

=> "global"(area)
=> "controller_action_predispatch"(event)
=> "adminnotification"(identifier)

then:

=> "adminhtml"(area)
=> "controller_action_predispatch"(event)
=> "adminnotification"(identifier)

If the event was in the frontend area, it would be: “global” then “frontend”.

Overwriting
Overwriting is in fact a observer defined in the same config area as the original event (global, frontend or adminhtml), attached to the same event and with the same identifier as the original observer (e.g. adminnotification).

Let’s say we have to overwrite “adminnotification”. This observer is in Mage/AdminNotification. The unique identifier is defined 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>
...

From the example above we can see:
- area: adminhtml
- event: controller_action_predispatch
- identifier: adminnotification

The module activation file will be: 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>

I’ve added dependencies because without the original module, this module will be useless.

There’s a “best practice” to name a module that is overwritten with the same name as the original module.

The configuration file for this module, will contain practically everything you’ll need for the overwriting: area, event and identifier. The file is located 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>

The observer should contain all the new logic. The file is in app/code/local/CP/AdminNotification/Model/Observer.php, just like you would probably expect from the structure above.

<?php

class CP_AdminNotification_Model_Observer {

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

Disabling
Disabling is preaty similar to overwriting, the difference is in the config and the fact that an observer file is not needed anymore, because there isn’t a new logic.

The new config.xml file is:

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

Written by admin

17 May 2012 at 9:59 PM

Posted in Magento,PHP

Tagged with ,

My pink elePHPant in the room

without comments

happy elePHPants

My elePHPants family!

My elePHPants collection has just doubled, now I have 2!

I wanted an pink elePHPant, to keep company to my old elePHPant.

Also this time I’ve turned to eBay. A quick search found another elePHPants breeder, this time is: Herman J. Radtke III. This time I’ve ordered it directly from his site, without eBay, only because eBay would calculate the shipping fee wrong.

Even with the right shipping fee, the new elePHPant wasn’t exactly cheap:

$ RON
ElePHPant 16.16 55,51
Shipping to RO 16.95 58,23
Postal fee (customs) 0.6 1.95
Total: 33.71 115,69

Not exactly cheap taking into consideration that the price for an elePHPant is about 5 euros in bulk.

But now I have a happy elePHPant family!

Share

Written by admin

17 April 2012 at 10:01 PM

Posted in Diverse,PHP

Tagged with ,

Magento – Create a custom shopping cart price rule

without comments

This is not a tutorial about setting up a Shopping Cart Price Rule in Magento, but rather about implementing a new one.

A new type of rule in Magento needs a couple of things:
- modify the admin area to add the new rule using an observer for adminhtml_block_salesrule_actions_prepareform,
- a way to apply the new rule using an observer for salesrule_validator_process.

Let’s build an example. Let’s say there is a Shopping Cart Price Rule that offers different discounts according to the number of products in the cart. The value that’s going to be used for the discount increment ($step) will be calculated. The first product will not receive a discount, the second product will receive a discount of $step, the third product will have a discount of 2*$step, until the maximum discount value will be reached. The following products will have a maximum discount. Ex:
Discount Amount = 50
Discount Qty = 5
Step = Discount Amount / Discount Qty = 10

Discount outcome:
- 0% prod 1
- 10% prod 2

- 50% prod 6
- 50% prod 7

The first step is the module activation using the file: 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>

The first observer, adminhtml_block_salesrule_actions_prepareform, must be in the “adminhtml” section of the config, because it will involve the admin. This observer will have access to the admin form, in order to modify it.

The second observer, salesrule_validator_process, can be in the “frontend” or “global” section of the config. If it’s in the frontend section, it will only apply to the frontend section. If it’s in the global section it will also apply to backend. Usually, global is necessary when there are actions on the cart in the 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>

As you can see above, there must be an Observer model that will have the two methods which modify the admin and apply the discount.

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

    // The new rule type
    const PRODUCT_NR_DISCOUNT = 'product_nr_discount';

    /**
     * Add the new rule type to the admin menu
     *
     * @param Varien_Event_Observer $observer
     */
    public function adminhtmlBlockSalesruleActionsPrepareform
              (Varien_Event_Observer $observer) {
        // Extract the form field
        $field = $observer->getForm()->getElement('simple_action');
        // Extract the field values
        $options = $field->getValues();
        // Add the new value
        $options[] = array(
            'value' => self::PRODUCT_NR_DISCOUNT,
            'label' => 'Product Number Discount'
        );
        // Set the field
        $field->setValues($options);
    }

    /**
     * Apply the discount
     * The discount will be applied for at least 2 products increasing
     * with a "step" for each product, where "step" is
     * maximum discount / number of products.
     *
     * @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();

        // Number of products
        $qty = $item->getQty();

        // We must check the rule type in order to isolate our rule type
        if($rule->getSimpleAction() == self::PRODUCT_NR_DISCOUNT && $qty > 1) {

            // Extract rule details
            $discountAmount = $rule->getDiscountAmount();
            $discountQty = $rule->getDiscountQty();

            // Discount step
            $step = $discountAmount/$discountQty;

            // Discount calculation
            $discount = 0;
            for($i = 1; $i < $qty; $i++) {
                $itemDiscount = $i * $step;
                // If the discount is bigger then the maximum discount
                // then the maximum discount is used
                if($itemDiscount > $discountAmount) {
                    $itemDiscount = $discountAmount;
                }

                $discount += $itemDiscount;
            }
            // Effective discount
            $totalDiscountAmount = ($item->getPrice() * $discount)/100;

            // Discount in percent for each item
            $item->setDiscountPercent($discount / $qty);

            // Setting up the effective discount, basically this is the discount value
            $result = $observer->getResult();
            $result->setDiscountAmount($totalDiscountAmount);
            $result->setBaseDiscountAmount($totalDiscountAmount);

        }
    }

}

This observer will run at each request if there are items in cart that for which the rule is applicable. If the discount should be applied only for specific products, there can be filtered using the rule’s “Conditions” tab, just as you would normally do.

Share

Written by admin

8 March 2012 at 9:37 PM

PHP 5.4 was released!

without comments

PHP 5.4 was released!

Even though is already yesterday news… literally, yesterday 1 March was released.

The complete list of changes is available on php.net.

I’m sorry that we still don’t have scalar type hinting in this version. The only change to type hinting was the “callable” word was added, about which I’ve talked in the closure in PHP 5.4  blog.

Another interesting thing is that this time register_globals and magic_quotes_gpc were really removed, so the old PHP 4 apps don’t get to be compatible anymore with the help of a couple of flags in php.ini.

Also the hex2bin() function was added, of course is not that important, but is interesting that the  bin2hex() function existed since PHP 4. :)

Share

Written by admin

2 March 2012 at 9:43 PM

Posted in PHP

Tagged with , ,

Magento native stack trace

with 4 comments

There are moments when you need to see the stack trace, to know how a certain point was reached. There are two native functions for that in PHP: debug_backtrace() si debug_print_backtrace. The first one returns an array and the second will print the stack trace to the screen.

The problem is that this functions must be customized for Magento, because it is very possible that when you’re running debug_backtrace()  you can run out of memory before you can send the output to a log file.

Magento has a native function for that purpose: Varien_Debug::backtrace([bool $return = false], [bool $html = true], [bool $withArgs = true]). In order to send the resulting stacktrace to a log file you simply all it with:

Mage::log(Varien_Debug::backtrace(true, false));

This technique is very useful when you need to see where an certain object is initialized, and what methods were executed up to that point.

Share

Written by admin

25 February 2012 at 11:48 AM

Posted in Magento,PHP

Tagged with , , ,

PHP 5.4 – Closures the right way!

with one comment

The concept of closure was introduced in PHP 5.3, with the new “more traditional” syntax for anonymous functions.

PHP 5.3

In PHP 5.3, a closure will rely on the term “use”, which was passing the variables to the anonymous function, making it a closure.

The problem is that the anonymous function will only be able to access the variables that have been passed with “use”. When it comes to objects, there are passed by reference by default, but scalar variables (int, string, etc.) are passed by value, as this is the default behavior in PHP 5+:

$scalar = 5;

$closure = function () use ($scalar) {
     return 'Scalar: ' . $scalar . PHP_EOL;
};

echo $closure(); // Scalar: 5

$scalar = 7;

echo $closure(); // Scalar: 5

Another problem is that you cannot pass $this when the anonymous function is declared inside an object, so only the public method and properties can be accessed inside the closure.

PHP 5.4

In PHP 5.4 the keyword “use” is no longer necessary, and the entire environment where the function was created is available inside the function.

The advantage is that when the anonymous function is created inside another function or method, the anonymous function has access to the environment where it was created, even after the execution of the environment is over. The objects from this environment will be unset, only after the last reference to the closure will be unset:

class testClass {

        private $changeableVar = 1;
        private $bigVar;

        public function __construct() {
                // Allocate a big variable so we can see the changes in memory
                $this->bigVar = str_repeat("BigWord", 5000);
        }

        /**
         * A method that returns the closure
         */
        public function closure() {

                return function () {
                        // Display the value of a private property of the object
                        echo 'Private property: ' . $this->changeableVar.PHP_EOL;

                        // Change the value of a private property of the object
                        $this->changeableVar = 2;
                };
        }

        /**
         * Method that displays a private property
         */
        public function showChangeableVar() {
                echo 'Private property in method: ' . $this->changeableVar.PHP_EOL;
        }

}

// Memory befor the objects is created
echo "Memory: " . memory_get_usage() . PHP_EOL; // Memory: 229896

// Create object
$testObj = new testClass();

// Create closure
$closure = $testObj->closure();

// Execute closure
$closure(); // Private property: 1

// Displaying the current value of the private property
$testObj->showChangeableVar(); // Private property in method: 2

// Memory befor object will be unset
echo "Memory: ". memory_get_usage() . PHP_EOL; // Memory: 266240

// Unset the object
unset($testObj);

// Memory after the object was distroyed, there is no big difference in memory
echo "Memory: ". memory_get_usage() . PHP_EOL; // Memory: 266152

// Run closure after the object in which it was created was unset
echo $closure(); // Private property: 2

// Unset closure and with it the object environment
unset($closure);

// Memotry after the las reference to the object (closure) is unset
echo "Memory: " . memory_get_usage() . PHP_EOL; // Memory: 230416

Callable type hinting

Another new feature introduced in PHP 5.4 regarding closures is the new “type hint”: “callable”. Actually callable is referring to any anonymous function, and even to a new way of calling a method of an object:

<?php

// A function that uses type hinting
function typeHinting(callable $a) {
     echo $a() . PHP_EOL;
}

// A closure
$closure = function () {
     return __FUNCTION__;
};

// Call the type hinting function with the closure
typeHinting($closure); // {closure}

class testClass {
     public function testMethod() {
          return __METHOD__;
     }
}

// A mock object
$testObj = new testClass();

// The new way of calling object methods
$objCallable = array($testObj, 'testMethod');

// Call type hinting function with the new method calling way
typeHinting($objCallable); // testClass::testMethod

I believe that only now we can really say that PHP supports closures, the right way!

Share

Written by admin

11 February 2012 at 5:10 PM

Posted in PHP

Tagged with , , , ,

Magento dead end – Breadcrumbs

without comments

In one of my adventures in the Magento code. I’ve encountered the following problem: I had to add a link to the breadcrumb.

As the documentation is not so great, after a little debugging (not a lot), I’ve got in to the core Mage_Page_Block_Html_Breadcrumbs.

The method is quite self-explanatory: addCrumb($crumbName, $crumbInfo, $after = false). Since I was there, I took a look inside:

function addCrumb($crumbName, $crumbInfo, $after = false)
{
  $this->_prepareArray($crumbInfo, array('label', 'title', 'link', 'first', 'last', 'readonly'));
  if ((!isset($this->_crumbs[$crumbName])) || (!$this->_crumbs[$crumbName]['readonly'])) {
    $this->_crumbs[$crumbName] = $crumbInfo;
  }
  return $this;
}

What’s interesting is the $after parameter, as you can see, even though it has a default value, is not used anywhere. The rest work’s as expected, probably this is why people don’t complain so much about it.

Share

Written by admin

3 February 2012 at 10:03 PM

Posted in Magento,PHP

Tagged with ,

Another year has passed an PHP 6 remains a myth – 2011 in review

without comments

It became a tradition for me to begin my annual review on this subject.

PHP 6 is as close to be released as it was last year, or two years ago, which is without perspective. This year PHP 5.4 reached RC4 and a final version will probably be released soon, this means that work on PHP 6 will not be resumed soon. But more about PHP 5.4 with another occasion, is on my “TODO” list to see what got into RC4.

As the main keyword for me in PHP 5.3 were namespaces, Anonymous functions, closures and garbage collector, in PHP 5.4 it seems that those keywords are going to be traits, the new closures and scalar type hinting, next to many other new features.

When I’ve wrote my first annual review blog about PHP 6, I was mainly working on Romanian websites, hence my desire for a version that will natively support this language and any other without any changes. Back then I was mainly working directly with the language, without using a framework most of the time. But since then a lot of time has passed and many things have changed, now I’m using almost exclusively frameworks and other platforms that are taking me further away from the language, offering me a different architectural perspective.

After more then an year with NCH, I’ve decided that is time for a change. This is also a company from the states with a branch in Romania, and this time is Optaros. Although I wasn’t trying to change my work place, I’ve responded to an invitation to an interview, and long story short, I left. For a long time I’ve wanted to work again for external clients, after working at NCH where all the projects were internal, I’ve wanted a change.

Again the projects are even bigger, with other scalability issues. But I think that makes web development so interesting, the bigger the scalability issues, the bigger the project.

Last year the main keywords were Linux si Symfony framework. For this year that is just ending the main keywords probably were: Magento and Drupal.

After a short period of working with Magento, I can say that it seems incredible how a platform so big has so little documentation and a lot of the time so inconsistent. It is a very complex platform and a lot of things can be done with it, but when it comes to documentation, it seems like the usual approach is to just analyze the core. Coming from the Symfony world, where there are literary books for documentation, available for free, it seems incredible how little and disorganized is the Magento documentation. But this is also a subject for another blog. A think that the Optaros team played an important role in helping me understand how to approach the issues.

Another major event for me this year was the Yahoo! Open Hack Day, event that this year was also held in Romania. I don’t think I’ve ever seen so much enthusiasm and energy in a single place, in a single day. For me as a developer it was an unforgettable experience, one of those moments that remind me why I’ve chosen this profession.

Also this year I’ve passed my PHP 5.3 certification exam, at the beginning of the year. The exam wasn’t as difficult as I’ve expected, even though the tension remains the same. The fact that it wasn’t my first certification exam helped, it’s incredible how much you remember when you start the reading the documentation again. Last year I’ve decided that I have to take at least an certification exam every year, so I have to get started on preparing for the next one.

As a conclusion, 2011 was a good year, full of challenges and accomplishments, even though I haven’t checked a lot of entries on my last year’s resolution, I’ve done quite a few that were not on that list. But now is time for another new year’s resolution.

And now I wish you an 2012 full of achievements! Happy new year!

Share

Written by admin

3 January 2012 at 4:45 PM

Posted in Diverse

Tagged with , , , ,

A new JavaScript game – Minesweeper

without comments

It’s time to publish another JavaScript game, this time is Minesweeper.

The first version of the game was made about an year and a half ago, but meanwhile I’ve completely rewritten it because of performance issues.

I believe this will be the last game that I didn’t make using canvas for compatibility reasons.

Unlike the other games that were made literally over the week-end (the exception was Puzzle Gd) this game proved to be a little more complicated.

For the design I must thank (again) to Cătălinei Radu.

Enjoy!

Share

Written by admin

16 October 2011 at 9:47 AM

Posted in Diverse,JavaScript

Tagged with , ,

Internet Explorer, select tag and onload

without comments

Another reason why I hate Internet Explorer.

All new browsers tend to cache form values. Nothing unusual up to here, a little annoying, but not unusual.

We have the following example:

<select id="select">
 <option value=a>a</option>
 <option value=b>b</option>
 <option value=c>c</option>
</select>
<script>

var checkSelected = function () {
 var element = document.getElementById('select');
 alert(element[element.selectedIndex].value);
}

// run after onload
window.onload = checkSelected;

// run before onload
checkSelected();

</script>

Load the page, select the third value and then refresh. Because no form was submitted the first impression is that the result will always be “a”. It seems it’s not really like that:

  • FireFox: c c
  • Google Chrome: a a
  • Internet Explorer: a c

I can understand why FireFox choose to cache the values even when no form was submitted.

I can understand Google Chrome for not caching the page if the form was not submitted.

But Internet Explorer caches the values and them loads them only after the page was loaded? This is confusing to me! I mean you don’t have the option of not using onload? Not even if the form was not submitted?

This test was made on Internet Explorer 9 and compatibility view to versions 7 and 8.

Share

Written by admin

9 October 2011 at 2:09 PM