Archive for the ‘Magento Development’ Category

Enabling Xdebug’s Exception Handler in Magento

Saturday, May 8th, 2010

Magento’s default error and exception handlers provide more information than the default php handlers, as they print out a basic backtrace (example) 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 default exception 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 (see this blog post for an example of how to automatically enable developer mode on development or staging sites). 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!

Posted in Magento Development | 2 Comments »

Track Inventory for Configurable Products

Saturday, May 8th, 2010

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:

You can download this module here. All you need to do to install the module is to copy the app folder into your Magento install folder. Note:

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.

Posted in Magento Development | 4 Comments »

Displaying Magento Tiered Pricing on the Category List View

Wednesday, May 5th, 2010

In this article, I’ll be outlining how to display Magento’s tiered pricing on the category list view.

Displaying full tiered pricing information on the category list view is not enabled in the default Magento themes. Rather, each product that has tiered pricing available simply adds a line underneath the default price information that indicates the lowest tier price available. For example: “As low as: $1.00″. This is nice, but doesn’t give us any information about what the different pricing levels are unless we click through to the actual product page.

Adding full tiered pricing information to this category list view is pretty simple. First, we’ll need to load full tiered pricing data on the product object, because the default category collection doesn’t include this data on the products. To do this, add the following lines immediately inside of the foreach() loop in your catalog/product/list.phtml template (lines 44 and 86 in the base/default/template/catalog/category/product/list.phtml as of version 1.4.0.1):

<?php
    $attribute = $_product->getResource()->getAttribute('tier_price');
    if ($attribute) {
        $attribute->getBackend()->afterLoad($_product);
    }
?>

This checks each product for a tier_price attribute, and if it exists, if the runs the _afterLoad() method from the tier_price attribute’s backend model to go through the tier price data, parse it out, and add it to the product model.

Once the data has been loaded, actually displaying the tiered prices is fairly easy. Locate the call to getPriceHtml() inside of the foreach loop in the list.phtml template (lines 55 and 96 in the base/default list.phtml). First, change the second parameter of the getPriceHtml() method from 'true' to 'false', to get rid of the “As low as…” text, and then immediately after the line containing getPriceHtml() add the following code:

<?php if(trim($this->getTierPriceHtml($_product))): ?>
    <?php echo $this->getTierPriceHtml($_product) ?>
<?php endif; ?>

This will check to see if tiered pricing data is available, and if it is, it will display it for each product on the category list. And that’s it! Check out an upcoming blog post for information on how to modify the tiered pricing display to show tiered pricing in a table format instead of Magento’s default format.

Cheers!

Posted in Magento Development | 5 Comments »

Disabling Javascript Merging in Magento

Saturday, March 27th, 2010

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 frontend. This makes debugging difficult for a few of reasons:

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”.

DisableJsMerging

I hope this helps in your Magento Javascript debugging!

Posted in Magento Development | No Comments »

Viewing Error Backtraces for Ambiguous Error Messages

Friday, March 19th, 2010

There are many times when working with the Magento frontend that I get very ambiguous error message. Here is a screenshot of the type of error I’m talking about:
Error Message

I usually just search the entire codebase for the error message code, locate the place where the exception is getting caught, and then temporarily modify the code to Mage::log() the following data: $e->getMessage(), $e->getTraceAsString(), etc…

I just came across an exception handler in the Mage_Checkout_MultishippingController class that passed the generic exception message as well as the Exception class itself to Mage::getSingleton(’checkout/session’)->addException(). Here’s the code (from line 219 of the containing file):

        catch (Exception $e) {
            Mage::getSingleton('checkout/session')->addException(
                $e,
                Mage::helper('checkout')->__('Data saving problem')
            );
            $this->_redirect('*/*/addresses');
        }

I dug into the Mage::getSingleton(’checkout/session’)->addException() class and realized that it logged the exception code to the var/logs/exception.log file (it only logs the exception if logging is enabled in System > Preferences > Developer). This means that instead of locating and hacking up core files to see exceptions that are thrown, in a lot of cases, you can just monitor the contents of the exception.log file.

If you’re developing on a Mac machine, you can monitor the contents of the exception.log file using the Console app. If you’re on a Linux box, you can use “tail -f var/log/exception.log” to monitor the contents of this file.

Hopefully this helps you in your debugging efforts!

Posted in Magento Development | 5 Comments »

Editing Magento’s Footer Links

Tuesday, March 2nd, 2010

To follow up on our post about editing Magento’s top links, I thought I might as well write an article about editing the footer links while I’m at it.  I’d recommend reading our post about using a local.xml file before getting started on this post.  This post might not make much sense until you read it. [NOTE: This specific example assumes you are using the blank theme. Layout handles may differ from theme to theme.]

In local.xml:

<?xml version="1.0"?>
<layout version="0.1.0">
    <default>
        <reference name="footer_links">
            <!-- Add custom links. Pretty self-explanatory.
            Dig into app/code/core/Mage/Page/Block/Template/Links.php for more info -->
            <action method="addLink" translate="label title">
                <label>About Us</label>
                <url>about</url>  <!-- can use a full url if not using urlParams below -->
                <title>About Us</title>
                <prepare>true</prepare> <!-- set true if adding base url param -->
                <urlParams helper="core/url/getHomeUrl"/> <!-- base url - thanks @Russ! -->
                <position>1</position>
                <liParams/>
                <aParams>'class="top-link-about-us"'</aParams>
                <beforeText></beforeText>
                <afterText></afterText>
            </action>
 
            <!-- Remove 'Site Map' Link - Default Position: 10
            Original link adding in catalog.xml -->
            <action method="removeLinkByUrl"><url helper="catalog/map/getCategoryUrl" /></action>
 
            <!-- Remove 'Search Terms' Link - Default Position: 20
            Original link adding in catalogsearch.xml-->
            <action method="removeLinkByUrl"><url helper="catalogsearch/getSearchTermUrl" /></action>
 
            <!-- Remove 'Advanced Search' - Default Position: 30
            Original link adding in catalogsearch.xml-->
            <action method="removeLinkByUrl"><url helper="catalogsearch/getAdvancedSearchUrl" /></action>
 
            <!-- Remove 'Contact Us' link - Original link in contacts.xml
            <!-- Best bet to go to Magento's Admin > System > Configuration > (left sidebar) Contacts
            > Contact Us Enabled = NO -->
            <!-- You can pass the full url, which is a hassle if you have dev and stage sites -->
            <action method="removeLinkByUrl"><url>http://YOUR_SITE.com/contacts/</url></action>
            <!-- see comments below for making a custom helper to remove contacts link
            no matter what your base url is  -->
	</reference>
 
        <!-- By default, Magento sets a static footer block. Find it in the admin under
        CMS > Static Blocks.
	<reference name="footer">
            <!-- Remove Magento's default static block and use the 'addLink' method above
            to add your custom and inline links.  I use the 'unsetChild' method as often as
            possible as opposed using the more final <remove name="cms_footer_links"/>
            just in case I want to add the block somewhere else -->
            <action method="unsetChild"><name>cms_footer_links</name></action>
 
            <!-- Remove all the other Magento links - "Site Map, Search Terms, Advanced Search, and
            Contact Us" -->
            <action method="unsetChild"><name>footer_links</name></action> <!-- Magento 1.4.x -->
	</reference>
</default>
</layout>

If all else fails, you can edit template/page/template/links.phtml. However, if you can make all your changes in local.xml, your life will be so much easier when it comes to upgrading Magento and making future edits.

Posted in Development, Magento Development | 17 Comments »

Editing Magento’s Top Links (The Better Way)

Wednesday, February 24th, 2010

You might need to read the post about using a local.xml file before this post makes much sense.

We offer all our clients a completely customized design from scratch, which means changing anything – including those annoying defaulted top links.  This post will show you how to edit your top links (without editing core layout files which may change when you update Magento) by utilizing a local.xml file. You can even add your own custom links without touching any template files! [NOTE: This specific example assumes you are using the blank theme. Layout handles may differ from theme to theme.]

ADDITION: Check out the blog post about editing Magento’s footer links

In local.xml:

<?xml version="1.0"?>
<layout version="0.1.0">
    <default>
        <reference name="root">
            <reference name="top.links">
                <!-- Add custom links. Pretty self-explanatory.
                Dig into app/code/core/Mage/Page/Block/Template/Links.php for more info -->
                <action method="addLink" translate="label title">
                    <label>About Us</label>
                    <url>about</url>  <!-- can use full url also -->
                    <title>About Us</title>
                    <prepare>true</prepare> <!-- set true if adding base url param -->
                    <urlParams helper="core/url/getHomeUrl"/> <!-- base url - thanks @Russ! -->
                    <position>1</position>
                    <liParams/>
                    <aParams>'class="top-link-about-us"'</aParams>
                    <beforeText></beforeText>
                    <afterText></afterText>
                </action>
 
                <!-- Removes 'My Account' link - Default position: 10 -->
                <action method="removeLinkByUrl"><url helper="customer/getAccountUrl"/></action>
 
                <!-- Removes 'Wishlist' link - Default position: 20 -->
                <!-- for Magento 1.3.x -->
                <action method="removeLinkByUrl"><url helper="wishlist/"/></action>
 
                <!-- for Magento 1.4.x -->
                <remove name="wishlist_link"/>
 
                <!-- Removes 'My Cart' AND 'Checkout' links
                Default position: 40 and 50 respectively -->
                <remove name="checkout_cart_link"/>
 
                <!-- To re-add 'My Cart' or 'Checkout' after removing both -->
                <block type="checkout/links" name="checkout_cart_link_custom">
                    <action method="addCartLink"></action>
                    <action method="addCheckoutLink"></action>
                </block>
            </reference>
        </reference>
    </default>
 
    <customer_logged_out>
        <!-- Removes 'Log In' link - Default position: 60 -->
        <reference name="top.links">
            <action method="removeLinkByUrl"><url helper="customer/getLoginUrl"/></action>
        </reference>
    </customer_logged_out>
 
    <customer_logged_in>
        <!-- Removes 'Log Out' link - Default position: 60 -->
        <reference name="top.links">
            <action method="removeLinkByUrl"><url helper="customer/getLogoutUrl"/></action>
        </reference>
    </customer_logged_in>
 
</layout>

If you absolutely cannot find a way to customize your top links using these methods, you can edit the /template/page/template/links.phtml

Posted in Development, Magento Development | 30 Comments »

The Better Way to Modify Magento Layouts

Tuesday, February 23rd, 2010

In this article, I’m going to be covering what I believe to be a very effective way of modifying the layout of any Magento theme.

For several of the first Magento themes I built, I copied the layout files from the default or blank theme into the custom theme layout folder. I would then modify the layout files directly, editing or commenting out content in files like: catalog.xml, page.xml, checkout.xml, etc… I never liked editing these files directly, as I knew that when it came time to upgrade to a newer version of Magento that had upgraded the layout files, I’d have to merge the changes into the new layout files.

One day, I was digging through the Magento code relating to layout files and discovered a bit of code that made me realize that it was possible to just place a local.xml file in my custom theme’s layout folder and have it loaded automatically by Magento. (this code is on line 283 in /app/code/core/Mage/Core/Model/Layout/Update.php in the fetchFileLayoutUpdates() method).

Due to Magento’s brilliant tags, it’s possible to do just about anything you want without having to edit any of the default layout files.

Before delving into the code, let’s look at the advantages/disadvantages of this method:

Advantages

Disadvantages

Here is the slimmed down, commented local.xml from one of our recent projects:

<?xml version="1.0"?>
<layout version="0.1.0">
 
<default>
 
	<reference name="head">
		<!-- Magento looks in /skin/frontend/<INTERFACE>/<THEME>/js/buyprinting.js
		for this file -->
		<action method="addItem"><type>skin_js</type><name>js/buyprinting.js</name></action>
 
		<!-- This removes the item that was set in the page.xml file -->
		<action method="removeItem"><type>skin_js</type><name>js/iehover-fix.js</name></action>
 
		<!-- Magento looks in /js/prototype/element.storage.js for this file -->
		<action method="addJs"><name>prototype/element.storage.js</name></action>
 
		<action method="addCss">
<stylesheet>css/buyprinting.css</stylesheet></action>
	</reference>
 
	<reference name="header">
        <!-- This adds a CMS block that can be called from the template file
        associated with the header block. -->
		<block type="cms/block" name="cms_quick_help">
			<action method="setBlockId"><block_id>quick_help</block_id></action>
		</block>
 
        <!-- The remove tag removes the blocks with the specified name from the layout -->
		<remove name="top.menu"/>
		<remove name="store_language"/>
		<remove name="breadcrumbs"/>
	</reference>
 
	<reference name="top.nav">
		<remove name="catalog.topnav"/>
	</reference>
 
	<reference name="left">
		<remove name="left.newsletter"/>
		<remove name="left.permanent.callout"/>
		<remove name="catalogsearch.leftnav"/>
 
        <!-- When you use the remove tag, it removes any blocks with the specified name from
            the entire layout, regardless of the context. So, if I remove right.newsletter in
            the <default> context and that name is used in say the <catalog_product_view> context,
            then both blocks will be removed.  Because remove operates on the global context,
            you can only remove an element once.  Since <remove name="right.newsletter" /> is
            being called in catalogsearch.xml, we have to unset it, or else we'll get an error.
 
            The line below only unsets the block from the parent's context, not the global
            layout context -->
		<action method="unsetChild"><name>right.newsletter</name></action>
	</reference>
 
	<reference name="right">
        <!-- Some blocks have to be removed using remove, others via unsetChild.
            I've not spent the time digging into the code to figure out why -->
		<remove name="right.permanent.callout"/>
		<remove name="catalog.compare.sidebar"/>
		<remove name="left.reports.product.viewed"/>
		<action method="unsetChild"><name>sale.reorder.sidebar</name></action>
		<action method="unsetChild"><name>wishlist_sidebar</name></action>
		<action method="unsetChild"><name>right.reports.product.viewed</name></action>
		<remove name="cart_sidebar"/>
	</reference>
 
</default>
 
<!-- CATALOG PAGES -->
	<catalog_product_view><!-- 2columns-right -->
		<reference name="root">
			<action method="setTemplate"><template>page/2columns-left.phtml</template></action>
		</reference>
		<reference name="content">
			<reference name="product.info">
				<block type="cms/block" name="cms_product_info_tabs">
					<action method="setBlockId"><block_id>product_info_tabs</block_id></action>
				</block>
				<block type="catalog/product_view" name="product.clone_prices" as="prices" template="catalog/product/view/price_clone.phtml"/>
				<action method="unsetChild"><name>tierprices</name></action>
				<action method="unsetChild"><name>addto</name></action>
				<remove name="addto"/>
				<reference name="product.info.options.wrapper.bottom">
					<action method="unsetChild"><name>product.tierprices</name></action>
				</reference>
			</reference>
		</reference>
	</catalog_product_view>
 
</layout>

I hope with article has given you some direction as to how you can improve you Magento theming skills. If you have any additional tips/comments on coding layouts, please suggest them in the comments section.

Posted in Magento Development | 55 Comments »

The Smart Method of Loading Collections

Wednesday, February 10th, 2010

One fairly unknown feature of Magento collections is that you actually don’t have to call ->load() on a collection before being able to access the items in the collection. So you can do this:

$orders = Mage::getResourceModel('sales/order_collection')->addAttributeToSelect('*');
# The load method is not necessary, as iterating through a collection automatically loads it, if it hasn't already been loaded.
# $orders->load();
foreach($orders as $order){
    echo 'Order Id: ' . $order->getId() . "\n";
}

The base Varien_Data_Collection class implements the IteratorAggregate (http://php.net/manual/en/class.iteratoraggregate.php) interface which extends the Traversable (http://www.php.net/manual/en/class.traversable.php) interface. When a class extends the Traversable interface, it guarantees that that class can be iterated through using foreach(). When foreach is called on a collection, it calls the getIterator() method in the Varien_Data_Collection class and uses the value returned from that method as the value that the foreach iterates through. This is the getIterator() method:

    public function getIterator()
    {
        $this->load();
        return new ArrayIterator($this->_items);
    }

This auto-loading functionality works for both EAV and flat-table collections.

As you can see, a collection ensures that it is loaded before running through a foreach loop. Note: collections can only be loaded once per instantiation. If you want to reload a collection, you have to call the clear() method, and then reset the select and filters before calling the load() method again.

Knowing that collections function in this way allows you to write code without explicit calls to load(). This ultimately should result in more flexible code. You can have a method in a block that loads a collection. That collection can then either be called by a template file and iterated through, or you can have another method that loads the collection from the first method, and then adds additional selects/filters to it. This practice of not explicitly calling the load() results in more flexible and reusable code.

Posted in Magento Development | No Comments »

Saving the Value of a Specific Attribute from a Model

Wednesday, February 10th, 2010

In Magento, it’s very easy to save all the data in a model by running $model->save();. (Note: In this blog post, model refers to an EAV model, not a flat resource model) This saves all the attributes for the model to their respective attribute tables. There are times when saving the value of just one of the model attributes is desirable. A couple cases where you’d want to do this:

Here is how you’d save just one attribute of a product model:

$product = Mage::getModel('catalog/product')->load(1);
$product->setName('Some Random Name');
$product->getResource()->saveAttribute($product, 'name');

Posted in Magento Development | No Comments »

Call us at 417-597-3397, email us at sales@classyllama.com, or use this form to contact us:

  1. (required)
  2. (required)
 

cforms contact form by delicious:days

Full builds (design and development) start at $70k