Archive for the ‘Development’ Category
Inspecting the contents of Magento models using debug() vs var_dump()
Saturday, July 31st, 2010
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 it’s 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.
Here are three screenshots exemplifying the difference between the different methods (note: the beautifully formatted var_dump output is a feature of the xdebug php extension):
Method 1:
Method 2:
Method 3:
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.
Posted in Magento Development | No Comments »
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 | 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:
- 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 | 4 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! --> <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 | 20 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 | 32 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 | 87 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:
- 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 | No Comments »




