Recently, I posted about avoiding common coding problems. These practices can help to reduce debugging overall time. Even after common coding issues have been reduced, you are often still left with a need to debug more complex problems. These are the bugs that you either didn’t write, which are sometimes buried in the core of the framework or application on which you are working (in our case, Magento), or that stem from incorrect or incomplete logic. These types of problems can often require a variety of techniques to debug, especially when the bug is in the code you neither wrote nor have seen.
Example
The Problem
A client was having a problem where no product on a grouped product page was able to be added to the cart. All qty text boxes were hidden, and it was ultimately represented the same way an out of stock grouped product was represented.
Debugging Methodology
- Start Simple
- Check for common mistakes
- Check the quickest things first
- Isolate the specifics of the problem
- Isolate the source of the problem
- Determine the factors that could possibly impact the source of the problem
- Create logs to determine which factor is the culprit
- Fix the bug whether code, user, or data related
Solution
1. Capture the Current State
The first step before doing any debugging is to create a copy of the database. Sometimes, especially with complex eCommerce sites with multiple integrations, a problem can be difficult to isolate. If I accidentally “fix” the problem while debugging it, my efforts at fixing the problem long term can be delayed until the problem occurs again. It is also good practice to debug apart from production if the problem can be reproduced in a development environment.
2 & 3. Check for Quick Fixes & Common Mistakes
Once I had that backup, I start by checking things in the Magento admin like stock, qty, product status, product websites, product visibility, etc. of the parent grouped product and its child simple products. In this case, I determined that according to the admin, all of the products had satisfactory attribute values. There were at least a few products that were enabled, in the appropriate website, in stock, with a qty greater than the out of stock qty level, and attached correctly to the grouped product. Next, I went to the code that determines whether a product is eligible to be purchased. This code is somewhat spread out, but I determined with a fair degree of certainty that there should have been products eligible to purchase.
4. Isolate the Specifics
Because I had determined that this problem was most likely not caused by a simple mistake and I had checked the quickest things, I moved on to isolate the specifics of the problem. Since the quantity text boxes were hidden, I knew that whatever logic was causing the products to not be able to be added to the cart was associated with the grouped product page. I opened up lib/Zend/Db/Adapter/Abstract.php, the file that processes most of the actual SQL calls that Magento uses and their bindings before the query is sent to the DB, and added some log statements. I logged every query that was made along with its associated bindings. I then created a log before the products on the grouped product page were loaded and a log after they were loaded. By doing this, I was able to determine which queries were most likely responsible for determining whether the products should be eligible for addition to the cart.
A new tool from Zend, called Z-Ray, would have made this query logging process simpler since it automatically displays all queries when enabled. I highly recommend using this tool for debugging your applications since it makes your debugging information available to you without having to add any logging or enable profiling.
5. Isolate the Source
Next, I then looked for a query that looked like it was dealing with stock. With this query in hand, I went to the database and ran the query to see its results. There were no valid results from this query. I now had the specifics of the problem (no valid stock items being returned from the DB), so I started removing filters from the query until it would return rows. By using this method, I was able to determine that the problem was related to the stock status that was set on the grouped product itself. I had isolated the source of the problem.
6. Determine Factors Affecting the Source
I next investigated where in the code this stock status was set and what conditions were used to determine whether the stock status should be in stock or out of stock. I determined that the stock status was supposed to be “in stock” based on the conditions in the code where stock status was updated. According to the code, the conditions should have produced an “in stock” status, but the grouped product was nevertheless “out of stock.” This explained why I had not found anything wrong with the attributes of grouped and associated simple products when I had done my initial debugging in my “check the quickest things” first step. Since I knew that in this case, stock, which is stored on the simple product, was being updated dynamically through the Magento API, I looked at the logic that determined stock status from this stock information. Sure enough, there was a separate set of logic for generating stock status compared to if the stock item were updated through the product admin section. Since the stock status code was distinct for updating stock via the API and I was not able to reproduce the problem by saving the same stock levels on the product in the admin, I determined that I needed to either create sandbox API calls or log the live integration. Saving in the product actually was fixing the problem, which gave me clear evidence that I needed to find out what the API was doing differently.
7. Create Logs to Identify the Culprit
Because the budget was more of a constraint than the timeline, I opted to log data on the live integration and wait for the next time this was broken. I fixed the data temporarily by saving a few grouped products and set up logging statements that would allow me to see the API parameters, the stock items, the stock status, and the sql queries for each API call. By doing this, I was able to determine the exact API call/parameters used to allow me to create a non-API based sandbox call and step through the stock status update process in a debugger since I had determined the factors that impacted the source of the problem and logged their values. Since I was trying to identify why a grouped product’s stock status was false, I identified the array that was supposed to contain a list of the child simple products stock statuses keyed by their ids. The array that was returned for the child products statuses was an array with only a single key/value pair where the key was a 1 and the value was a seemingly random number.
By looking at the code that was generating the child product stock statuses, I was able to identify the problem. The query that was getting statuses from the database was switching the order of the keys and the values. The stock status, which was a 1, was being represented as the key and the key was being represented as the value. Since the stock status for each child product was 1, the resulting array was a key of 1 and a value of the final child product id. The children products’ stock statuses were then being used to update the parent stock status and since there was no valid child product stock status marked as in stock, the parent product was being marked as out of stock.
If you would like to view the relevant methods where I found this problem, you can look at Mage_CatalogInventory_Model_Resource_Stock_Status::saveProductStatus
and Mage_Catalog_Model_Resource_Product_Status::getProductStatus
in the else
section under the $select
and $rows
variable assignment. I was working with EE 1.12.0.2 but the error likely exists in or around CE 1.7.0.2 as well.
8. Resolve the Issue
I was able to fix the problem with a simple rewrite of the Mage_Catalog_Model_Resource_Product_Status class by changing the order of the columns in the select in the getProductStatus() method.
Ultimately, by following the steps listed in the debugging methodology section above, I was able to identify the problem relatively quickly. The order of the steps I followed is important because at any point I may have identified the problem and not had to continue debugging the problem. I highly recommend using the above methodology or similar to arrive at a solution most quickly and ultimately save time, money, and frustration.