Magento Varien Object

Within Magento you’ll have probably come across methods where you can retrieve data from certain entities. For example, to load a simple product you would write the following code below.

$product = Mage::getModel('catalog/product')->load(1); // Where 1 is the product ID

And to get data from certain attributes, you might use the example shown below.

$product->getId();
$product->getName();
$product->getShortDescription();

You can also set data on certain attributes.

$product->setName('Some Different Product Name');
$product->setDescription('Some alternate long-winded description...');

Furthermore, if you were to add a custom product attribute, for example using the attribute code is_seasonal that could represent if a product is available at certain times of the year. Getting and setting this data you would use the getIsSeasonal() and setIsSeasonal() methods.

$product->getIsSeasonal();
$product->setIsSeasonal(1);

Magento is highly unlikely to have getIsSeasonal() and setIsSeasonal() methods for our custom attribute by default. So how does Magento get and set this information?

All Magento models extend the Mage_Core_Model_Abstract class which in turn extend from the Varien_Object class.

Varien_Object contains a __call() method. If you’re not familiar with this method in PHP, it is a PHP ‘magic method’ that is called when a method that doesn’t exist in the class is called.

<?php
class Varien_Object implements ArrayAccess
{
    ....
    public function __call($method, $args)
    {
        switch (substr($method, 0, 3)) {
            case 'get' :
                //Varien_Profiler::start('GETTER: '.get_class($this).'::'.$method);
                $key = $this->_underscore(substr($method,3));
                $data = $this->getData($key, isset($args[0]) ? $args[0] : null);
                //Varien_Profiler::stop('GETTER: '.get_class($this).'::'.$method);
                return $data;

            case 'set' :
                //Varien_Profiler::start('SETTER: '.get_class($this).'::'.$method);
                $key = $this->_underscore(substr($method,3));
                $result = $this->setData($key, isset($args[0]) ? $args[0] : null);
                //Varien_Profiler::stop('SETTER: '.get_class($this).'::'.$method);
                return $result;

            case 'uns' :
                //Varien_Profiler::start('UNS: '.get_class($this).'::'.$method);
                $key = $this->_underscore(substr($method,3));
                $result = $this->unsetData($key);
                //Varien_Profiler::stop('UNS: '.get_class($this).'::'.$method);
                return $result;

            case 'has' :
                //Varien_Profiler::start('HAS: '.get_class($this).'::'.$method);
                $key = $this->_underscore(substr($method,3));
                //Varien_Profiler::stop('HAS: '.get_class($this).'::'.$method);
                return isset($this->_data[$key]);
        }
        throw new Varien_Exception("Invalid method ".get_class($this)."::".$method."(".print_r($args,1).")");
    }
    ....
}

As you can see, the switch statement is used to find out if we’re getting (get), setting (set), unsetting (uns) or checking if the data exists (has). This is determined by taking the first 3 characters of the non-existent method e.g. getIsSeasonal

The _underscore() method is called in each case, passing in the second part of the method i.e. the characters after the ‘get’, ‘set’ etc. For example, getIsSeasonal.

<?php
class Varien_Object implements ArrayAccess
{
    ....
    protected function _underscore($name)
    {
        if (isset(self::$_underscoreCache[$name])) {
            return self::$_underscoreCache[$name];
        }
        #Varien_Profiler::start('underscore');
        $result = strtolower(preg_replace('/(.)([A-Z])/', "$1_$2", $name));
        #Varien_Profiler::stop('underscore');
        self::$_underscoreCache[$name] = $result;
        return $result;
    }
    ....
}

This method contains a preg_replace() function that effectively looks for uppercase letters within the string after lowercase letters and adds an underscore between them. The string as a whole is then made into lowercase via the strtolower() function. So IsSeasonal becomes is_seasonal.

The relevant methods in the switch statement are called. So for the ‘get’ case, the getData() method is run passing in the $key parameter.

<?php
class Varien_Object implements ArrayAccess
{
    ....
    public function getData($key='', $index=null)
    {
        if (''===$key) {
            return $this->_data;
        }

        $default = null;

        // accept a/b/c as ['a']['b']['c']
        if (strpos($key,'/')) {
            $keyArr = explode('/', $key);
            $data = $this->_data;
            foreach ($keyArr as $i=>$k) {
                if ($k==='') {
                    return $default;
                }
                if (is_array($data)) {
                    if (!isset($data[$k])) {
                        return $default;
                    }
                    $data = $data[$k];
                } elseif ($data instanceof Varien_Object) {
                    $data = $data->getData($k);
                } else {
                    return $default;
                }
            }
            return $data;
        }

        // legacy functionality for $index
        if (isset($this->_data[$key])) {
            if (is_null($index)) {
                return $this->_data[$key];
            }

            $value = $this->_data[$key];
            if (is_array($value)) {
                //if (isset($value[$index]) && (!empty($value[$index]) || strlen($value[$index]) > 0)) {
                /**
                 * If we have any data, even if it empty - we should use it, anyway
                 */
                if (isset($value[$index])) {
                    return $value[$index];
                }
                return null;
            } elseif (is_string($value)) {
                $arr = explode("\n", $value);
                return (isset($arr[$index]) && (!empty($arr[$index]) || strlen($arr[$index]) > 0))
                    ? $arr[$index] : null;
            } elseif ($value instanceof Varien_Object) {
                return $value->getData($index);
            }
            return $default;
        }
        return $default;
    }
    ....
}

Notice the first conditional at the top of the method, if you pass in an empty string as the $key argument the entity data is returned using $this->_data.

Note: This article is based on Magento Community/Open Source version 1.9.