Clearing Magento Model instance

As we all know, it’s always better to use collections in Magento than to instantiate model inside a loop. Database hit is one of the most expensive operations you can do. You should always try to make one query in order to get the collection and then loop through it’s elements instead of loading and possibly saving your data inside a loop.

While possible most of the time, there are cases when you just need to create model instance inside a loop. Sometimes you need to choose between loading a model in a loop and making a very complicated query. Even Magento core team does instantiate model where not appropriate sometimes:

app/code/core/Mage/Adminhtml/controllers/Sales/OrderController.php

    /**
     * Cancel selected orders
     */
    public function massCancelAction()
    {
        $orderIds = $this->getRequest()->getPost('order_ids', array());
        $countCancelOrder = 0;
        $countNonCancelOrder = 0;
        foreach ($orderIds as $orderId) {
            $order = Mage::getModel('sales/order')->load($orderId);
            if ($order->canCancel()) {
                $order->cancel()
                    ->save();
                $countCancelOrder++;
            } else {
                $countNonCancelOrder++;
            }
        }
        if ($countNonCancelOrder) {
            if ($countCancelOrder) {
                $this->_getSession()->addError($this->__('%s order(s) cannot be canceled', $countNonCancelOrder));
            } else {
                $this->_getSession()->addError($this->__('The order(s) cannot be canceled'));
            }
        }
        if ($countCancelOrder) {
            $this->_getSession()->addSuccess($this->__('%s order(s) have been canceled.', $countCancelOrder));
        }
        $this->_redirect('*/*/');
    }

Thing to consider

If you really need to do this, here is one thing to have in mind. At least I did not think it’s so obvious. Consider this code:

$modelInstance = Mage::getModel('catalog/product');

$product = $modelInstance->load(1);
$product->setAbc('aaa');
	
$secondproduct = $modelInstance->load(2);
$secondproduct->setDef('bbb');
var_dump($secondproduct->getAbc());
var_dump($secondproduct->getDef());

Seems that loading does not actually clear the instance so the result would be:

string 'aaa'
string 'bbb'

First product’s property remains in the second product.

To be more precise, loading does not actually clear the instance when you are working with models that have resource class that inherits from Mage_Eav_Model_Entity_Abstract. It’s because this class has load method that works with addData method:

Mage_Eav_Model_Entity_Abstract

/**
     * Load entity's attributes into the object
     *
     * @param  Mage_Core_Model_Abstract       $object
     * @param  integer                        $entityId
     * @param  array|null                     $attributes
     * @return Mage_Eav_Model_Entity_Abstract
     */
    public function load($object, $entityId, $attributes = array())
    {
        Varien_Profiler::start('__EAV_LOAD_MODEL__');
        /**
         * Load object base row data
         */
        $select  = $this->_getLoadRowSelect($object, $entityId);
        $row     = $this->_getReadAdapter()->fetchRow($select);

        if (is_array($row)) {
            $object->addData($row);
        } else {
            $object->isObjectNew(true);
        }

        if (empty($attributes)) {
            $this->loadAllAttributes($object);
        } else {
            foreach ($attributes as $attrCode) {
                $this->getAttribute($attrCode);
            }
        }

        $this->_loadModelAttributes($object);

        $object->setOrigData();
        Varien_Profiler::start('__EAV_LOAD_MODEL_AFTER_LOAD__');

        $this->_afterLoad($object);
        Varien_Profiler::stop('__EAV_LOAD_MODEL_AFTER_LOAD__');

        Varien_Profiler::stop('__EAV_LOAD_MODEL__');

        return $this;
    }

While Mage_Core_Model_Resource_Db_Abstract works with setData.

/**
     * Load an object
     *
     * @param  Mage_Core_Model_Abstract             $object
     * @param  mixed                                $value
     * @param  string                               $field  field to load by (defaults to model id)
     * @return Mage_Core_Model_Resource_Db_Abstract
     */
    public function load(Mage_Core_Model_Abstract $object, $value, $field = null)
    {
        if (is_null($field)) {
            $field = $this->getIdFieldName();
        }

        $read = $this->_getReadAdapter();
        if ($read && !is_null($value)) {
            $select = $this->_getLoadSelect($field, $value, $object);
            $data = $read->fetchRow($select);

            if ($data) {
                $object->setData($data);
            }
        }

        $this->unserializeFields($object);
        $this->_afterLoad($object);

        return $this;
    }

Difference is that addData adds elements to object’s _data property and overwrites only if key exists, while setData overwrites entire _data property when passed an array as first argument.

The clearInstance method

So in this case, to get the correct result you would first need to clear the instance:

$modelInstance = Mage::getModel('catalog/product');

$product = $modelInstance->load(1);
$product->setAbc('aaa');
$modelInstance->clearInstance();
	
	
$secondproduct = $modelInstance->load(2);
$secondproduct->setDef('bbb');
var_dump($secondproduct->getAbc());
var_dump($secondproduct->getDef());

This would be the result:

null
string 'bbb'

Clearing instance for products is basically setting object’s data property to an empty array

We can see that in Mage_Core_Model_Abstract there is a final method clearInstance, and _clearData is obviously meant to be implemented in child classes

/**
     * Clearing object for correct deleting by garbage collector
     *
     * @return Mage_Core_Model_Abstract
     */
    final public function clearInstance()
    {
        $this->_clearReferences();
        Mage::dispatchEvent($this->_eventPrefix.'_clear', $this->_getEventData());
        $this->_clearData();

        return $this;
    }
/**
     * Clearing object's data
     *
     * @return Mage_Core_Model_Abstract
     */
    protected function _clearData()
    {
        return $this;
    }

In Mage_Catalog_Model_Product for example we have an implementation of _clearData.

protected function _clearData()
    {
        foreach ($this->_data as $data) {
            if (is_object($data) && method_exists($data, 'reset')) {
                $data->reset();
            }
        }

        $this->setData(array());
        $this->setOrigData();
        $this->_customOptions       = array();
        $this->_optionInstance      = null;
        $this->_options             = array();
        $this->_canAffectOptions    = false;
        $this->_errors              = array();

        return $this;
    }

So if after all you need to load EAV based model inside a loop, remember to clear instance:

$modelInstance = Mage::getModel('catalog/product');
foreach($ids as $id) {
    $modelinstance->load($id);
    // do some stuff
    $modelInstance->clearInstance();
}

For this you will have to create your own _clearData method inside your EAV model, which could look like this:

protected function _clearData()
{
    $this->_data = array();
    return $this;
}

Hope this helped, let me know your experience.

Would you like to recommend this article to your friends or colleagues?
Feel free to share.

facebooktwittergoogle_plusredditlinkedin
This article was posted in category Magento, PHP.

Article "Clearing Magento Model instance" has 3 responses

Leave a Reply to Juan Carlos Cancel reply

Your email address will not be published. Required fields are marked *

*

*

*