Magento create observers on custom entities

If you need to create an observer on your custom entities, just follow these few steps:

In this case, we will create an observer on the before save event of the tarif entity.

On your module config.xml file, declare your observer, this will bind your hook event to your class/method

<config>
   <global>
      <events>
            <tarif_save_before>
                <observers>
                    <adin_marketplace>
                        <type>singleton</type>
                        <class>adin_marketplace/observer</class>
                        <method>adinMarketplaceTarifSaveBefore</method>
                    </adin_marketplace>
                </observers>
            </tarif_save_before>
       </event>
   </global>
/config>

tarif_save_before means your observer will trigger on the before save event of the tarif entity.
To find out which entity to use, you can add (temporary) a mage::log() on this class : app/code/core/Mage/Core/Model/Abstract.php

 protected function _beforeSave()
    {
        if (!$this->getId()) {
            $this->isObjectNew(true);
        }
        Mage::dispatchEvent('model_save_before', array('object'=>$this));
        Mage::log('entity to use: '.$this->_eventPrefix.'_save_before');
        Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
        return $this;
    }

adin_marketplace is my module.
adin_marketplace/observer class is the class file to use (app/code/local/Adin/Marketplace/Model/Observer.php )
adinMarketplaceTarifSaveBefore is the method inside my class.

Now, create your class and your method : app/code/local/Geophyle/Marketplace/Model/Observer.php

class Adin_Marketplace_Model_Observer {
public function adinMarketplaceTarifSaveBefore($observer)
{
    $event = $observer->getEvent();
    $tarif = $event->getTarif();
    //do some stuff...
}

And that’s all.
You change an .xml file, so don’t forget to clear your cache.

Magento app/code/core/Mage/Eav/Model/Entity/Abstract.php on line 1180 error

You try to save a product and you get this error : Invalid argument supplied for foreach().
This was unexpected and you don’t understand why.

The problem is, that you are not allowed to save products from the frontend.
The origData property is not filled

public function setOrigData($key=null, $data=null)
{
    if (Mage::app()->getStore()->isAdmin()) {
        return parent::setOrigData($key, $data);
    }

    return $this;
}

So when you try to save the product, this error is raised.

What to do when you encounter this error ?

A quick fix is to add this before saving, on your controller for example.

Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);

A proper solution is to extend Mage_Catalog_Model_Product and replace setOrigData method

public function setOrigData($key = null, $data = null)
{
    if (is_null($key)) {
        $this->_origData = $this->_data;
    } else {
        $this->_origData[$key] = $data;
    }
    return $this;
}

Magento get countries dropdown list

Create a dropdown list with magento’s countries is easy :

<select name="billing_country" id="billing_country">
<?php
 $_countries = Mage::getResourceModel('directory/country_collection')->loadData()->toOptionArray(false);
 foreach($_countries as $_country)
 {
       echo '<option ';
       if($this->getCustomer()->getBillingCountry() == $_country['value']){ echo 'selected '; }
       echo 'value="'.$_country['value'].'">'.$_country['label'].'</option>';
 }
?>
</select>

Magento create dropdown from product attribute option

Here a simple but complete example to create a dropdown list for a product attribute

//retrieve informations
$attribute_model        = Mage::getModel('eav/entity_attribute');
$attribute_code         = $attribute_model->getIdByCode('catalog_product', 'audience');
$attribute              = $attribute_model->load($attribute_code);
$attribute_options_model= Mage::getModel('eav/entity_attribute_source_table') ;
$attribute_table        = $attribute_options_model->setAttribute($attribute);
$options                = $attribute_options_model->getAllOptions(false);

//display them
echo '<select id="audience" class="select" name="audience">';
foreach($options as $option)
{
    echo '<option value="'.$option['value'].'">'.$helper->__($option['label']).'</option>';
}
echo '</select>';

Magento load template from controller

To load html content of a template inside a controller, for example for ajax response, you can do :

$block = $this->getLayout()->createBlock('adin_admin/adminhtml_scheduletcrender')->setTemplate('admin/scheduletcrender.phtml');
$block->assign(array('ateliers' => $ateliers));
$html = $block->toHtml();
$response = array('html' => $html);
     
//send response as json
$this->getResponse()->setHeader('Content-type', 'application/json');
$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));

PHP define constants with arrays

You need to define a constant containing an array but the code below doesn’t work ?

define('FILTER_ECOLE', array('price, categorie, niveau'));

You get an error :

Notice: Use of undefined constant FILTER_ECOLE - assumed 'FILTER_ECOLE'  in /home/www/adin/app/code/local/Adin/Catalog/Block/Nav/Catalog/Layer/View/Sidebar.php on line 50

To define an use a constant containing an array you can simply serialize it :

#define
define('FILTER_ECOLE', serialize(array('price, categorie, niveau')));

#use
$filter = unserialize(FILTER_ECOLE);

Magento – update attribute parameters programmatically

This is how you can update attribute parameters, the one saved in catalog_eav_attribute table

In this exemple we will enable the “used_in_product_listing” params of the “code_catalogue” attribute

$attributeId = Mage::getResourceModel('eav/entity_attribute')->getIdByCode('catalog_product','code_catalogue');
if ($attributeId) {
    $attribute = Mage::getModel('catalog/resource_eav_attribute')->load($attributeId);
    $attribute->setUsedInProductListing(1)->save();
}

And that’s all.

Magento – How to get current router module controller action ?

You can easily know “where you are” using these fonctions :

in template / block files

$this->getRequest()->getRouteName();
$this->getRequest()->getModuleName();
$this->getRequest()->getControllerName();
$this->getRequest()->getActionName();

in class files

Mage::app()->getRequest()->getRouteName();
Mage::app()->getRequest()->getModuleName();
Mage::app()->getRequest()->getControllerName();
Mage::app()->getRequest()->getActionName();

Magento SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ‘000000254’ for key ‘UNQ_SALES_FLAT_ORDER_INCREMENT_ID’

In some cases, customers encounter this error on your website :

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '000000254' for key 'UNQ_SALES_FLAT_ORDER_INCREMENT_ID'

You don’t understand and drive you crazy ?
Don’t worry, I will help you.

First, this error probably happend when customer quit violenty the payment page, without canceling, so your order is not “finished” and your quote is not “released” (is_active = 1 in db). Customer come back, recover his quote and try to proceed it again.
The reserved_increment_id is not updated and when magento try to create the order, mySQL give him the previous error.

How a so obvious bug like this can exist ? and why does it crash only on my website, it seems to work fine on other website ?
Let me guest, your client (clients always have weird needs …) ask you to start order number at 0 instead of 100000000, or ask you to put a letter into it.

So, why does this reserved_increment_id is not updated ?
Let check on the function which update the reserved_increment_id field :

// /app/code/core/Mage/Sales/Model/Resource/Quote.php
    /**
     * Check is order increment id use in sales/order table
     *
     * @param int $orderIncrementId
     * @return boolean
     */
    public function isOrderIncrementIdUsed($orderIncrementId)
    {
        $adapter   = $this->_getReadAdapter();
        $bind      = array(':increment_id' => (int)$orderIncrementId);
        $select    = $adapter->select();
        $select->from($this->getTable('sales/order'), 'entity_id')
            ->where('increment_id = :increment_id');
        $entity_id = $adapter->fetchOne($select, $bind);
        if ($entity_id > 0) {
            return true;
        }

        return false;
    }

You see it, the little (int) in this line $bind = array(‘:increment_id’ => (int)$orderIncrementId); ?
This transform your increment_id in your request from “000000254” to “254”, mySQL doesn’t find an order with the increment_id “254” and your quote is not updated.

To fix it, override this function :
In your Sales/etc/config.xml file, add

<global>
  <model>
    <sales_resource>
                <rewrite>
                    <quote>Adin_Sales_Model_Resource_Sales_Quote</quote>
                </rewrite>
            </sales_resource>
   </model>
</global>

And create the new class file /app/code/local/Adin/Sales/Model/Resource/Sales/Quote.php

class Adin_Sales_Model_Resource_Sales_Quote extends Mage_Sales_Model_Resource_Quote {

    /**
     * Check is order increment id use in sales/order table
     *
     * @param int $orderIncrementId
     * @return boolean
     */
    public function isOrderIncrementIdUsed($orderIncrementId)
    {
        $adapter   = $this->_getReadAdapter();
        $bind      = array(':increment_id' => $orderIncrementId);
        $select    = $adapter->select();
        $select->from($this->getTable('sales/order'), 'entity_id')
            ->where('increment_id = :increment_id');
        $entity_id = $adapter->fetchOne($select, $bind);
        if ($entity_id > 0) {
            return true;
        }

        return false;
    }

}

That should do the tricks.