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. 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:
Hopefully this little trick allows you to code Magento more effectively!
Posted in Magento Development | 4 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:
- 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)
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:
- 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.
Posted in Magento Development | 6 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:
- 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!
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:
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! --> <!-- there are a few param you can send to do different things in <urlParams> dig into app/code/core/Mage/Core/Model/Url.php, around line 803 --> <!-- below adds #add-fragment to the end of your url --> <!-- <urlParams><_fragment>add-fragment</_fragment></urlParams> --> <!-- below adds ?add-query to the end of your url --> <!-- <urlParams><_query>add-fragment</_query></urlParams> --> <!-- below gives you a new session id (i think...)--> <!-- <urlParams><_nosid>true</_nosid></urlParams> --> <!-- below replaces double quotes, single quotes, greater than, and less than signs to their respective url escaped replacements (%22, %27, %3E, %3C) --> <!-- <urlParams><_escape>i'm-a-blog-url</_escape></urlParams> --> <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 | 30 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! --> <!-- there are a few param you can send to do different things in <urlParams> dig into app/code/core/Mage/Core/Model/Url.php, around line 803 --> <!-- below adds #add-fragment to the end of your url --> <!-- <urlParams><_fragment>add-fragment</_fragment></urlParams> --> <!-- below adds ?add-query to the end of your url --> <!-- <urlParams><_query>add-fragment</_query></urlParams> --> <!-- below gives you a new session id (i think...)--> <!-- <urlParams><_nosid>true</_nosid></urlParams> --> <!-- below replaces double quotes, single quotes, greater than, and less than signs to their respective url escaped replacements (%22, %27, %3E, %3C) --> <!-- <urlParams><_escape>i'm-a-blog-url</_escape></urlParams> --> <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 | 60 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
Before delving into the code, let’s look at the advantages/disadvantages of this method:
Advantages
- Allows you to upgrade themes without having to merge in changes
- All custom layout changes are centralized, allowing developers to more easily make changes to custom theme elements
Disadvantages
- At first, it’s slower to use this method than hacking up the standard layout files directly
- You will have one more place to look where the site might be pulling code (template phtmls, standard layout files, admin layout updates, AND local.xml)
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 | 158 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 | 2 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:
- You’ve been passed a model from an event, and you aren’t sure if the data in that model can be safely saved.
- You are saving many models and want to make your save operations as efficient as possible
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 | 1 Comment »
Add Magento Admin Account Using MySQL Script
Thursday, January 21st, 2010
Note: this script works on Magento CE 1.3.2.4 and 1.4.0.* but does not work in the Magento Enterprise Edition
As a Magento development company, we have a number of designers, developers, and project managers that need admin access to the sites that we build. As opposed to using one global admin account for our entire team, we believe it to be a much better practice to assign individual admin accounts to each team member who is going to be working on a project.
One of our developers, Matt Johnson, has developed a script to allow us to easily add an admin user to a Magento installation. Each member on our team that needs Magento admin access has a sql file that adds their admin account to Magento. When we start a new project, we run the sql script for the members of our team that are going to need access to the server. I hope you’ll find this snippet useful.
/* This is an example SQL script. You should replace all the UPPERCASED variables. The <USERNAME> variable can only be alphanumeric characters, and probably underscores (haven't tested) You can either generate a password hash using the PHP code below, or you can grab a password hash from the admin_user table from an existing Magento install for which you know the admin password. */ /* Use the following PHP script to generate a salted password hash. You can also use a straight md5 hash, but it's much more easily brute-forced <?php $password = 'PASSWORD'; $salt = 'GF'; echo $hash = md5($salt.$password).':'.$salt; ?> */ insert into admin_user select (select max(user_id) + 1 from admin_user) user_id, 'FIRST NAME' first_name, 'LAST NAME' last_name, 'EMAIL' email, 'USERNAME' username, 'HASH EXAMPLE: 178f29c8e4c6c801db90cd171e3b2b53:in' password, /* You can replace this value with an md5 hash */ now() created, NULL modified, NULL logdate, 0 lognum, 0 reload_acl_flag, 1 is_active, (select max(extra) from admin_user where extra is not null) extra; insert into admin_role select (select max(role_id) + 1 from admin_role) role_id, (select role_id from admin_role where role_name = 'Administrators') parent_id, 2 tree_level, 0 sort_order, 'U' role_type, (select user_id from admin_user where username = 'USERNAME') user_id, 'USERNAME' role_name |
Posted in Magento Development | 16 Comments »

