Llama Loose In LA: Speaks At Magento Imagine Conference

Listen up Magento fans! (Yes, Mr. or Mrs. Potential-client-considering-Classy-Llama’s-proficiency, that means you, too.)

If you thought Classy Llama Studios was just “in it for the short term” or simply “out to make a buck” then think again! We are, in fact, all about impacting the globe and everyone in it. To that end, we use Magento as our e-commerce platform of choice. But! We don’t stop with just using the platform: we find ways to make it, and its use, even better.

That’s why we’re sending Erik Hansen, our Director of Technology (and one of our top llamas), over to bright and sunny California to speak at the incredible Magento Imagine eCommerce conference!

Erik’s slice of the seminar, called “Best Practices for Magento Debugging”, will provide a whirlwind tour covering the ideal Magento environment configuration, debug steps for common development problems, and tips on debugging with an Eclipse-based editor. (For those of you readers not interested in the guts of the workshop or the innards of Magento, let’s just say that we really know our stuff.)

For you Llama-like techies who may be attending this near-earth-shattering event, upon completion of Erik’s workshop, you’ll be able to:

  • Customize your Magento installation to provide you with rich debug data for errors and exceptions
  • Uncover the cause of mysterious issues plaguing a Magento site
  • Debug more effectively using an Eclipse-based editor

We hope you’ll go…and if you’re able to make it to the conference, please stop by and say “Hi!” to Erik for all of us lonely llamas not in California with you.

Saving a Product Attribute without Saving the Entire Product

Today I was faced with an interesting task. I needed to update an attribute on as many as several thousand products at once within a reasonable amount of time. My first thought was to load a collection of products and then save that collection. Under the hood, saving a collection simply loops through all models in that collection and calls the save method on the model.

I started in this direction when I tested my code, Magento threw the following error:

exception ‘Zend_Db_Statement_Exception’ with message ‘SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘value’ cannot be null’

After consulting with one of the other developers here, I realized that because a collection does not load entire products but only a subset of the products’ attributes. I tried loading every single product and saving it but this turned out to be painfully slow. It would have taken hours to save just a few hundred products. Clearly, this was unacceptable.

I started brainstorming and I realized that I could use the same methods that are used by the mass product attributes update page uses. After looking into this method, I realized that I could only send one value per attribute update. To get around this limitation, I set up an array keyed using the new attribute value with a value of an array of all products that had that specific value. This array was fed through a loop and the update attributes method was called for every value. This turned out to be very fast and it simplified the process enormously.

The following code is what I ended up using to update the attributes.

// $products is a collection of products that need to be updated
// $productArray contains an array with the following format:
// array('productId' => 'value to add')
foreach ($products as $product) {
    $newPopularity = $product->getPopularity() + $productArray[$product->getId()];
    $productData[$newPopularity][] = $product->getId();
}
foreach ($productData as $popularityValue => $pidArray) {
    Mage::getSingleton('catalog/product_action')
        ->updateAttributes($pidArray, array('popularity' => $popularityValue), 0);
}

There is a previous blog post about saving product attributes here. I ran a comparison between the two methods for an 1162 product collection. Using this method it took 6.233485 sec vs 8.680476 sec using the individual save method. Although this method is quicker, the disparity between the two methods is proportional to the number of values that are the same. So a collection with 2000 ranging over 5 values will be helped a lot more than a collection of 2000 with 1000 values.

  • This module is built on Magento 1.4.1.1. It should work on most versions of Magento.

Get Configurable Super Attributes

I wanted to do a check on a configurable product to see if it had a specific attribute. But not just any attribute, a global attribute used to create associated products within the configurable product – a “Super Product Attribute”. So here ya go:

    $_product = $this->getProduct();
    $_attributes = $_product->getTypeInstance(true)->getConfigurableAttributes($_product);
    foreach($_attributes as $_attribute){
        var_dump($_attribute->debug());
    }

A little bonus:

The “->debug()” method returns the set of values you can use the get magic method on. For example:

array
  'product_super_attribute_id' => string '263' (length=3)
  'product_id' => string '27' (length=2)
  'attribute_id' => string '80' (length=2)
  'position' => string '0' (length=1)
  'product_attribute (Mage_Catalog_Model_Resource_Eav_Attribute)' => 
    array
      'attribute_id' => string '80' (length=2)
      'entity_type_id' => string '4' (length=1)
      'attribute_code' => string 'color' (length=5)
...

So var_dump($_attribute->getAttributeId()); will return string ’80’ (length=2). Just use “get” with the Capitalized Label, getProductAttribute(), getProductId(), getPosition(), etc. Enjoy!

Magento Developer’s Paradise Summary

I just arrived back in the US after a long weekend at the Magento Developer’s Paradise. We (David Alger and myself) had a great time at the event (see photos of the event below). There were a number of different types of attendees: Magento Professional & Enterprise development companies, extension development companies (like Webshopapps.com and Sweet Tooth), freelance developers, and Magento team members. We had the opportunity to meet with a number of the European partners (Netresearch,Icommerce,Inchoo, et al) and other developers to share ideas on how to successfully build Magento projects.

Yoav Kutner kicked off the conference with a keynote Sunday morning. Here are some bullet points from his speech:

  • Magento is the fastest growing e-commerce platform in the world.
    • “Magento” is searched for more often than “Ecommerce” on Google
    • Has been downloaded over 2.5 million times
    • Over 65K merchants are using Magento
      • This stat has been on the Magento website for a while, but I always wondered how this number was derived: I asked Yoav if Magento pinged any server to notify that it was installed at a certain address and he said that it did not, which is fantastic from a privacy perspective. The only way that Magento Inc. knew the urls of the 65K+ sites is that the admin panel in Magento is setup to check with the Magento servers to see if there are any updates/notifications that should be displayed to the admin user. The list of all urls that have requested updates from the updates RSS feed were culled down to a list of publicly accessible domains and then were tested to ensure that they were an actual Magento site.
    • It’s been estimated that over $25 billion has been transacted through the Magento platform.
    • 2.4 million extension downloads
    • 350+ active developers for Magento Connect
    • 180+ solution partners – 55% of these are in Europe
  • Magento is being used on continually larger projects – some Magento sites are nearing the $1B/yr revenue mark.
  • Magento Community 1.4.2.0 is planned for release in early November. This will be a maintenance release that will resolve 450 issues.
  • Magento Community 1.5 Roadmap – CE 1.5 is currently slated to have the following features (subject to change):
    • Order composite products in admin
    • Edit order without creating a new one
    • Custom order statuses (this is currently possible by adding an xml node in a config.xml file of a custom module, but 1.5 will allow you to manage order statuses via the admin)
    • Multistep checkout with no Javascript
    • Improved import functionality (note: the uRapidflow extension currently enables these features, plus more)
      • Product types supported:
        • Simple
        • Virtual
        • Grouped
        • Configurable
        • Gift Card
      • All types of attributes will be supported
      • Support for different values per scope
      • Support for custom options
      • Support for tiered pricing
  • Magento Enterprise 1.9.2 is planned for release in mid October (this will also just be a maintenance release) and 1.10 is planned for release at the end of the year. Magento 1.10 planned roadmap:
    • Gift wrapping
    • CDN/DB as alternative image storage
    • Support for MSSQL and Oracle databases
  • There are over 700 Magento Enterprise users

Some notes from the other talks at the conference:

  • Magento Connect – Jonathan Beri talked about upcoming features in Magento Connect 2.0. Some of the things that are planned for 2.0:
    • The option for Magento developers to sell modules directly within Magento Connect
    • The ability to sell a module as a subscription, instead of a one-time purchase
    • More advanced sorting/filtering options
  • Magento Extensions for the Cloud/Saas – Jonathan Beri also talked a little bit about the OpenSocial extension infrastructure they are planning for the Saas solution they are building. Jonathan previously worked for Myspace where they use OpenSocial for their extension infrastructure.
  • Performance – Dima Soroka gave a great presentation on Magento performance optimization. You can review the slides from his presentation here: http://j.mp/98mlR8
  • Debugging with PDT+xdebug – I gave a quick presentation on the importance of using a robust IDE (PDT, Zend Studio, Netbeans) with debugging capabilities. You can read a blog post I wrote a while back on how to enable rich error and exception backtraces here: http://bit.ly/co1qc4. Yoav mentioned that some members of the Magento development team have recently started using PhpStorm (http://bit.ly/dbxhkq).

Thanks to Thomas, Annemarie, and others at Netresearch.de for the great job you did at hosting the event.

At a loss for words?

Wikipedia explains Lorem Ipsum as “placeholder text (filler text) commonly used to demonstrate the graphic elements of a document or visual presentation, such as font, typography, and layout.” Basically, this gives designers one less thing to worry about, so as not to get in the way of the creative process.

If you don’t like doing a search online for “Lorem Ipsum”, finding the right amount of text needed, and then copy/paste into your design every time you need to use Lorem Ipsum, then you should download Loremify. This widget allows for quick and easy grabs of the perfect amount of text for your needs.

Tobias Bjerrome Ahlin is the interface designer who developed this awesome tool, and he describes it as follows:

“Loremify is a free Mac app to quickly copy Lorem Ipsum to your clipboard. It lets you wrap the dummy text in HTML or markdown, specify the amount of text, and copy it to your clipboard—all in one click.”

Here is an example from Tobias Ahlin’s blog of Loremify in action.

Loremify example as gif

The interface is clean and simple.

Loremify example as gif 2

If you like helpful tips like this, follow us on Twitter to stay up to date on our most recent blog posts as well as retweets from other digital commerce industry leaders.

Ameds Featured on Official Magento Blog

I won’t lie.  We like being noticed.  That’s why we’re named Classy Llama.

But we like it even BETTER when our clients are noticed.  Recently, Ameds.com, one of our Enterprise clients, was featured on the Magento blog.

Magento said the following about our work on Ameds:

“The migration of 40,000+ SKU storefront from a proprietary code base to Magento Commerce Edition 1.8 was achieved successfully by minimizing risk of adverse effects on pre-existing marketing campaigns, traffic and customer base… [and] migration and integration of all customer data (including passwords and order history), as well as retention of thousands of old URLs via systematic URL rewriting patterns allowing for preserved natural search engine rankings, in addition to continued operation of PPC & CSE campaigns with virtually no changes.”

Inspecting the contents of Magento models using debug() vs var_dump()

When you are developing for Magento, it is often helpful to see the contents of a Model. A model is a pretty complex object, so when you want to inspect the contents of the model, you generally don’t want to see all of its properties – all you’re really wanting to see is the contents of the _data property, which is the array that contains all of the attributes/fields that have been retrieved from the database.

Typically, when inspecting a model, I’ve used one of the following methods:

$model = Mage::getModel('catalog/product')->load(1);
 
# Method #1: This dumps all of the model's property's, including the _data property.
# This results in a lot of superfluous code that I don't need to see.
var_dump($model);
 
# Method #2: This only logs the data in the _data property.  The problem is, if the _data property contains
# any other models, all of the properties of those models will be dumped
var_dump($model->getData());

I recently came across the ->dump() method that is implemented in the Varien_Object class. This method recurses into the _data property of a model and returns an array of the values in the _data property, including the _data properties of the child classes. Here’s the example code:

# Method #3
var_dump($model->debug());

Note: The Varien_Object class is a class from which MANY classes in Magento extend. Most notably, all blocks and all data models extend Varien_Object. If you’ve never looked at the Varien_Object class, I’d recommend scanning/reading through the methods in that class. If you’re too lazy to read through the methods, but want to see what the debug() method does, I’ve included it at the bottom of this post.

In case you’re curious to see how the debug() method works, here’s the code from the Varien_Object class:

    /**
     * Present object data as string in debug mode
     *
     * @param mixed $data
     * @param array $objects
     * @return string
     */
    public function debug($data=null, &$objects=array())
    {
        if (is_null($data)) {
            $hash = spl_object_hash($this);
            if (!empty($objects[$hash])) {
                return '*** RECURSION ***';
            }
            $objects[$hash] = true;
            $data = $this->getData();
        }
        $debug = array();
        foreach ($data as $key=>$value) {
            if (is_scalar($value)) {
                $debug[$key] = $value;
            } elseif (is_array($value)) {
                $debug[$key] = $this->debug($value, $objects);
            } elseif ($value instanceof Varien_Object) {
                $debug[$key.' ('.get_class($value).')'] = $value->debug(null, $objects);
            }
        }
        return $debug;
    }

I hope this quick tip helps you program with models more effectively! If you have any questions, feel free to post comments below.

Enabling Xdebug’s Exception Handler in Magento

Magento’s default error and exception handlers provide more information than the default php handlers, as they print out a basic backtrace as opposed to just printing out the file and line number of the error but there are many times when Magento’s handlers just don’t cut it; times when you need to see the fully expanded variables that get passed to the functions/methods in the backtrace.

In the Initial Magento Setup Development Tips blog post, I wrote about how Xdebug overrides the default php error handler with a detailed, customizable error backtrace. I explained how to modify Magento’s core code so that php errors get handled by Xdebug, not Magento’s default error handler.

In this blog post, I’m going to explain how to modify Magento so that exceptions get handled by Xdebug, not Magento’s defaultexception handler.

The Mage::run() method is the method that gets the entire Magento request cycle started, so it makes sense that this is where the exceptions get handled:

    /**
     * Front end main entry point
     *
     * @param string $code
     * @param string $type
     * @param string|array $options
     */
    public static function run($code = '', $type = 'store', $options=array())
    {
        try {
            Varien_Profiler::start('mage');
            self::setRoot();
            self::$_app = new Mage_Core_Model_App();
            self::$_events = new Varien_Event_Collection();
            self::$_config = new Mage_Core_Model_Config();
            self::$_app->run(array(
                'scope_code' => $code,
                'scope_type' => $type,
                'options'    => $options,
            ));
            Varien_Profiler::stop('mage');
        } catch (Mage_Core_Model_Session_Exception $e) {
            header('Location: ' . self::getBaseUrl());
            die();
        } catch (Mage_Core_Model_Store_Exception $e) {
            require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
            die();
        } catch (Exception $e) {
            if (self::isInstalled() || self::$_isDownloader) {
                self::printException($e);
                exit();
            }
            try {
                self::dispatchEvent('mage_run_exception', array('exception' => $e));
                if (!headers_sent()) {
                    header('Location:' . self::getUrl('install'));
                } else {
                    self::printException($e);
                }
            } catch (Exception $ne) {
                self::printException($ne, $e->getMessage());
            }
        }
    }

The problem with the above code is that any exceptions that get thrown by any Magento code get handled by the Mage::printException() method. What we need to do is run the essential code without wrapping it in any try/catch blocks if the developer mode is enabled from the index.php. This will result in all exceptions being handled by the Xdebug exception handler. Here is the resulting code:

    /**
     * Front end main entry point
     *
     * @param string $code
     * @param string $type
     * @param string|array $options
     */
    public static function run($code = '', $type = 'store', $options=array())
    {
        if(!self::getIsDeveloperMode()){
            try {
                Varien_Profiler::start('mage');
                self::setRoot();
                self::$_app = new Mage_Core_Model_App();
                self::$_events = new Varien_Event_Collection();
                self::$_config = new Mage_Core_Model_Config();
                self::$_app->run(array(
                    'scope_code' => $code,
                    'scope_type' => $type,
                    'options'    => $options,
                ));
                Varien_Profiler::stop('mage');
            } catch (Mage_Core_Model_Session_Exception $e) {
                header('Location: ' . self::getBaseUrl());
                die();
            } catch (Mage_Core_Model_Store_Exception $e) {
                require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
                die();
            } catch (Exception $e) {
                if (self::isInstalled() || self::$_isDownloader) {
                    self::printException($e);
                    exit();
                }
                try {
                    self::dispatchEvent('mage_run_exception', array('exception' => $e));
                    if (!headers_sent()) {
                        header('Location:' . self::getUrl('install'));
                    } else {
                        self::printException($e);
                    }
                } catch (Exception $ne) {
                    self::printException($ne, $e->getMessage());
                }
            }
        } else { // If we're running in developer mode, we want all exceptions to be handled by the php|xdebug error handler
            Varien_Profiler::start('mage');
            self::setRoot();
            self::$_app = new Mage_Core_Model_App();
            self::$_events = new Varien_Event_Collection();
            self::$_config = new Mage_Core_Model_Config();
            self::$_app->run(array(
                'scope_code' => $code,
                'scope_type' => $type,
                'options'    => $options,
            ));
            Varien_Profiler::stop('mage');
        }
    }

This code allows Xdebug’s exception handler to do its magic, resulting in fully detailed exception backtraces like this:

XdebugException

Hopefully, this little trick allows you to code Magento more effectively!

Track Inventory for Configurable Products

We recently had a client who needed to be able to manage inventory for configurable products in Magento. Since configurable products are intended to merely group simple products together, the ability to track inventory for configurable products is not something that is possible in a vanilla Magento install.

I dug into the Magento codebase to better understand how inventory is managed for the different product types in Magento. As you could probably guess, the CatalogInventory module handles all of Magento’s inventory management. In side of the /app/code/core/Mage/CatalogInventory/etc/config.xml file, you’ll find the following section of xml contained in the global tag:

<catalog>
<product>
        <type>
            <simple>
                <is_qty>1</is_qty>
            </simple>
            <virtual>
                <is_qty>1</is_qty>
            </virtual>
            <configurable>
                <stock_indexer>cataloginventory/indexer_stock_configurable</stock_indexer>
            </configurable>
            <grouped>
                <stock_indexer>cataloginventory/indexer_stock_grouped</stock_indexer>
            </grouped>
        </type>
    </product>
</catalog>

This section of xml is used by Mage_CatalogInventory_Helper_Data::getIsQtyTypeIds method to determine if a certain product type “qualifies” for inventory tracking. It is also used to indicate if there is a custom resource model that needs to be used to calculate the stock availability for a certain product type.

The beautiful thing about the way Magento handles the configuration xml is that we can create xml nodes that mirror the structure of the above xml in a config.xml file in a custom module – this allows us to change pretty much any configuration value without having to touch any of the config.xml files of core Magento modules. Magento will then merge the xml nodes from both config.xml files. Since custom modules are loaded after core modules, any nodes that override default Magento nodes will take precedence.

I created a small module that enables inventory tracking for configurable products. The following sections in Magento are overridden by this module:

  • app/code/core/Mage/CatalogInventory/Model/Observer.php (lines 365 – 405)
  • app/code/core/Mage/CatalogInventory/etc/config.xml (line 202)
  • app/design/adminhtml/default/default/template/catalog/product/tab/inventory.phtml (line 55)


Note:

  • There are two different ways of tracking inventory for configurable products. Read the comments in the app/code/local/CLS/ConfigurableInventory/etc/config.xml file for details.
  • This module is built for Magento 1.4.0.1. It’ll likely work on older versions of Magento, but I’ve only tested it on 1.4.0.1

UPDATE (5/12/10): After working with this test module on a dev site, it looks like the $item->getProduct() method call on line 21 in the CLS_ConfigurableInventory_Model_CatalogInventory_Observer class actually isn’t returning a product model for all order items. This is something that I’ll be debugging once we actually implement this test code in a project. I’ll try to post an update here when we do that.

I’d love to hear from any of you that end up implementing this code on your site.

Disabling Javascript Merging in Magento

If you have ever written Javascript for Magento, you have probably been frustrated by the fact that Magento merges all of its JS files before outputting them to the front end. This makes debugging difficult for a few of reasons:

  • It makes it more difficult to see if your JS file is even being loaded
  • When Firebug (or your JS debugger of choice) logs errors to the console, it’s impossible to match up the line numbers Firebug reports with the line numbers in the file
  • If you want to use Firebug’s debugging or profiling tools, you’re greatly impaired by having all the JS in one file.

A few months ago, one of our developers stumbled across the ability to disable the JS merging in Magento: Go to System > Preferences > Developer > Javascript Settings tab > Set “Merge JavaScript Files” to “No”.

I hope this helps in your Magento Javascript debugging!

Contact Us