Debugging tips & tricks with Magento Commerce

If you are new to Magento then the vast amount of code to digest can be quite overwhelming. cold_sweat However, here are some tips I’ve learned about over the past on some means to tear down Magento and figure out what’s making it work.

Zend_Debug::dump

Use Zend_Debug::dump($foo); instead of using var_dump($foo); or print_r($foo); it is essentially the same, just a wrapper with pre HTML tag for formatting and escaping of special characters.

idea More details about arrow Zend Frameworks dump method.

However there will be times that simply dumping objects to the screen can be too much and cause browser hangups or even crashes. The best practice I’ve learned is to always use the getData() method that Magento has built-in to the Varien Object, this way your not getting redundant amounts of data dumped to the screen but only the bits you really care about.


Varien Object getData, debug

Magento also has a built-in debug() method in the Varien Object as well that you can use to display data in a string representation of itself.

exclamation Keep in mind debug does NOT always get attached to every object.

idea More details about the arrow Varien Object debug method, arrow Varien Object getData method


Log files

What if your having difficulty displaying things to screen or don’t want any users to see debug output. With this in mind you can also use a built in logging function, similar to arrow Zend_Log and is essentially a wrapper as well. You can find it defined in app/Mage.php.

    /**
     * log facility (??)
     *
     * @param string $message
     * @param integer $level
     * @param string $file
     * @param bool $forceLog
     */
    public static function log($message, $level = null, $file = '', $forceLog = false)
...

And here is an example:

Mage::Log($foo);

Which will log the output the contents of $foo to /var/log/system.log by default.

You can also specify your own log file with an extra argument, your custom log file will appear in /var/log/mylogfile.log

Mage::log($foo, null, 'mylogfile.log');

You can also use combinations of functions/methods to output the contents of an array for instance:

Mage::log(var_dump($fooArray), null, 'mylogfile.log');

exclamation Logging MUST be enabled within the admin: Configuration -> Developer -> Log Settings -> Enabled = Yes


XML Configuration

Most of the time, I have have issues with my XML configurations. Since Magento is very configuration based driven, one improper case or underscore can render things useless. Magento doesn’t validate the XML or throw any errors when such are encountered but rather ignored. The best means I’ve found to figure out whats going on is to display the entire XML built from all XML configurations files with.

header("Content-Type: text/xml");
die(Mage::app()->getConfig()->getNode()->asXML());

xDebug

xDebug is probably one of the more well known and most used debugging tools available. If your IDE does support it, I would highly suggest taking the time to get your environments setup so that you can connect and use it. I’m not going to cover the general use and configuration of it, however Classy Llama has a nifty post that helps keep Magento from letting xDebug take over error handling.

arrow Classy Llama’s Enable xDebugs Error Handler

exclamation It requires modification to the Core files and cannot be extended since the class is final. Make note of your change when doing this, or merely use it on a per need basis and removing it after your done with it. You can also setup your version control to ignore any changes with it.


Built-in PHP functions

If you’re using a bare bones editor without any type of auto complete looking up available class methods can be a pain digging through Magento’s numerous files and folders. To get all available methods from any class you can use var_export, get_class_methods and get_class in combination.

print "<pre>"; var_export(get_class_methods(get_class($this)));

idea More details on: var_export(), get_class(), get_class_methods()

You can also use it in combination with Magento’s getData() to display data with key values intact.

print "<pre>"; var_export(array_keys($this->getData()));

Developer Mode

One last tip I’ve been doing lately is modifying index.php and adding ini_set('display_errors', 1); to the condition checking for the developer mode flag: MAGE_IS_DEVELOPER_MODE. By default the display_errors ini_set is commented out. Here is what my change looks like:

if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
  Mage::setIsDeveloperMode(true);
  ini_set('display_errors', 1);
}

Then use .htaccess SetEnv to enable and disable developer mode per environment:

SetEnv MAGE_IS_DEVELOPER_MODE "true"

If you have any tips or if I missed something please feel free to comment and I’ll add it to the article.

.net C# API to Magento via XML-RPC

Since I’m starting to get back to writing .Net C# again, I decided to dig around for anything Magento related and found a nice XML-RPC based library, that will allow you to utilize Magento’s API. Samples are provided and seem to be pretty straight forward, however they do note a lot of the returns even though stated as an integer or such, but tend to most always return a string. Which leads me to believe its something with the XML-RPC in between the two, and PHP’s love to turn most any type into a string. Here is one of the examples to get your gears turning:

#region Product Image Examples
//// gets the product image current store view (doc says int returned, but is string)
string myProductImageCurrentStore = ProductImage.CurrentStore(apiUrl, sessionId, new object[] { });
Console.WriteLine(myProductImageCurrentStore);

You can find the full API here, along with some notes:
arrow .NET C# Object Library for Magento

arrow .net C# API to Magento via XML-RPC

Direct download link to the API:
arrow Ez.Newsletter.MagentoApi_v1.1.zip

Magento SEO

For those that are subscribed to the Magento RSS news feed, this may not be news to you, but for those that aren’t here is a good listing of SEO best practices for Magento. My only suggestion to Yoast was to add in Google Site map generation to their list which was added, along with a bit more about how to setup the Google site map’s thats built-in to Magento automagically generate daily or such with a cron job.

Here is the full list and I’d highly recommend alot of the tips and tricks mentioned.

arrow Magento SEO – Yoast – Tweaking Websites.

A couple other sites to perhaps check out:
arrow http://www.seothegame.com/
arrow http://www.johnon.com/

Exporting users/customers from X-Cart to Magento

title-images-import2

Following my previous posting of importing basic product data into Magento from X-Cart I stumbled across this code on the Magento Community forums with a nifty PHP script to prepare a CSV file of users to be imported into Magento.

:!: UPDATE: Finally got around to trying to utilize this script, and realized there are in fact some issues with using it. Some of the main items I noticed is:

  • It is not 3.5.x X-Cart compatible, and possibly lower versions.
  • As well as it was not very optimized for a very large number of customers, causing memory limit errors while attempting to export.

I have updated the code to handle the two items mentioned above and have successfully imported almost 6k customers from a 3.5.x version of X-Cart. Here is the updated script, the same instructions apply, copy the file into your admin directory of X-Cart and then proceed to login to the admin of X-Cart, after successfully logging in, simply change index.php in your url to migrate.php and wait for the script to prompt for you to download a .csv file of the users. Be patient especially if you have a large number of customers as the script can take awhile to generate this data without any prompts that it is working.

idea When using this code be sure and note the following comments as, depending on your X-Cart version. Anonymous customers are ignored.

Create the file $xcart_dir/admin/migrate.php and copy the following code into it:

<?php
/*
Migrate X-Cart customers to Magento
Original by Spydor at: http://www.magentocommerce.com/boards/viewthread/30894/
Modified by B00MER at: http://www.molotovbliss.com 3/15/2009 3:49:23 AM
Modifications include:
X-Cart 3.5 compatibility, possibly lower versions as well.
Optimization for large number of records to avoid memory limit errors
*/
@set_time_limit(2700);
# Include core functions
if (!require("../admin/auth.php"))
	require("./auth.php");
function resolveState($b_state,$b_country,$s_state,$s_country){
	global $states;
	$result = array();
	foreach($states as $key=>$value){
		// Billing
		if(($value['state_code']==$b_state) && ($value['country_code']==$b_country))
			 $result['billing'] = $value['state'];
		// Shipping
		if(($value['state_code']==$s_state) && ($value['country_code']==$s_country))
			 $result['shipping'] = $value['state'];
	}
	if(empty($result['billing']))
		$result['billing'] = $b_state;
	if(empty($result['shipping']))
		$result['shipping'] = $s_state;
	return $result;
}
function func_export_csv($data,$title) {
	$output = $data;
	$size_in_bytes = strlen($output);
	   header("Content-type: application/vnd.ms-excel");
	   header("Content-disposition: csv; filename=".$title . '_' . date("Y-m-d") . ".csv; size=$size_in_bytes");
	   return $output;
}
# Define new line
$newline = "\n";
# Define CSV fields
$output =  '"website","email","group_id","prefix","firstname","middlename","lastname","suffix","password_hash","taxvat","billing_prefix","billing_firstname","billing_middlename","billing_lastname","billing_suffix","billing_street_full","billing_city","billing_region","billing_country","billing_postcode","billing_telephone","billing_company","billing_fax","shipping_prefix","shipping_firstname","shipping_middlename","shipping_lastname","shipping_suffix","shipping_street_full","shipping_city","shipping_region","shipping_country","shipping_postcode","shipping_telephone","shipping_company","shipping_fax","created_in","is_subscribed"';
$output .= "$newline";
$states = func_query("SELECT $sql_tbl[states] .state, $sql_tbl[states] .code AS state_code, $sql_tbl[states] .country_code FROM $sql_tbl[states], $sql_tbl[countries] WHERE $sql_tbl[states] .country_code=$sql_tbl[countries] .code AND $sql_tbl[countries] .active='Y'");
# total customers
$sql = "SELECT COUNT(*) AS total_results FROM $sql_tbl[customers] where login NOT LIKE 'anonymous%'";
$results = func_query($sql);
$total_results=$results[0]["total_results"];
# number of records to return each loop
$num_of_records=150;
# start for loop to feed out data
for($i=0; $i<=$total_results; $i+=$num_of_records) {
  #$q = "SELECT * FROM xcart_customers where login NOT LIKE 'anonymous%' "; // Memory Hog with SELECT *
  $q = "SELECT email,title,firstname,lastname,password,title,b_address,b_city,b_country,b_zipcode,phone,company,fax,s_address,s_city,s_country,s_zipcode FROM xcart_customers where login NOT LIKE 'anonymous%' LIMIT $i,$num_of_records ";
  $result = func_query($q);
  foreach($result as $key=>$value){
       # Uncomment for version 4.0+ of X-Cart
  	   $result[$key]['password_hash'] = md5(text_decrypt($value['password']));
       # for version 3.5 and below of X-Cart
  	   #$result[$key]['password_hash'] = text_decrypt($value['password']);
  	   $realstates = resolveState($value['b_state'], $value['b_country'], $value['s_state'], $value['s_country']);
  	   $result[$key]['b_real_state'] = $realstates['billing'];
  	   $result[$key]['s_real_state'] = $realstates['shipping'];
  }
  foreach($result as $key => $value) {
    #func_print_r($result);exit;
  	$output .= '"base",';
  	$output .= '"' . $value['email'] . '",';
  	$output .= '"General",';
  	$output .= '"' . $value['title'] . '",';
  	$output .= '"' . $value['firstname'] . '",';
  	$output .= '"",';
  	$output .= '"' . $value['lastname'] . '",';
  	$output .= '"",';
  	$output .= '"' . $value['password_hash'] . '",';
  	$output .= '"",';
  	# Billing info
  	$output .= '"' . $value['title'] . '",';
  	$output .= '"' . $value['firstname'] . '",';
  	$output .= '"",';
  	$output .= '"' . $value['lastname'] . '",';
  	$output .= '"",';
  	$output .= '"' . $value['b_address'] . '",';
  	$output .= '"' . $value['b_city'] . '",';
  	$output .= '"' . $value['b_real_state'] . '",';
  	$output .= '"' . $value['b_country'] . '",';
  	$output .= '"' . $value['b_zipcode'] . '",';
  	$output .= '"' . $value['phone'] . '",';
  	$output .= '"' . $value['company'] . '",';
  	$output .= '"' . $value['fax'] . '",';
  	# Shipping Info
  	$output .= '"' . $value['title'] . '",';
  	$output .= '"' . $value['firstname'] . '",';
  	$output .= '"",';
  	$output .= '"' . $value['lastname'] . '",';
  	$output .= '"",';
  	$output .= '"' . $value['s_address'] . '",';
  	$output .= '"' . $value['s_city'] . '",';
  	$output .= '"' . $value['s_real_state'] . '",';
  	$output .= '"' . $value['s_country'] . '",';
  	$output .= '"' . $value['s_zipcode'] . '",';
  	$output .= '"' . $value['phone'] . '",';
  	$output .= '"' . $value['company'] . '",';
  	$output .= '"' . $value['fax'] . '",';
  	$output .= '"default",';
  	$output .= '"0"';
  	$output .= "$newline";
  }
}
#func_print_r($output); // uncomment to see output
print func_export_csv($output,"xcart_customers");
?>

Here is the original code by Spydor:

<?php
require "../auth.php";
$states = func_query("SELECT $sql_tbl[states] .state, $sql_tbl[states] .code AS state_code, $sql_tbl[states] .country_code FROM $sql_tbl[states], $sql_tbl[countries] WHERE $sql_tbl[states] .country_code=$sql_tbl[countries] .code AND $sql_tbl[countries] .active='Y'");
#print_r($states);
$q = "SELECT * FROM xcart_customers where login NOT LIKE 'anonymous%' ";
$result = func_query($q);
foreach($result as $key=>$value){
       $result[$key]['password_hash'] = md5(text_decrypt($value['password']));
       $realstates = resolveState($value['b_state'], $value['b_country'], $value['s_state'], $value['s_country']);
       $result[$key]['b_real_state'] = $realstates['billing'];
       $result[$key]['s_real_state'] = $realstates['shipping'];
}
#print_r($result);
$output =  '"website","email","group_id","prefix","firstname","middlename","lastname","suffix","password_hash","taxvat","billing_prefix","billing_firstname","billing_middlename","billing_lastname","billing_suffix","billing_street_full","billing_city","billing_region","billing_country","billing_postcode","billing_telephone","billing_company","billing_fax","shipping_prefix","shipping_firstname","shipping_middlename","shipping_lastname","shipping_suffix","shipping_street_full","shipping_city","shipping_region","shipping_country","shipping_postcode","shipping_telephone","shipping_company","shipping_fax","created_in","is_subscribed"';
$output .= "1512";
foreach($result as $key => $value){
    $output .= '"base",';
    $output .= '"' . $value['email'] . '",';
    $output .= '"General",';
    $output .= '"' . $value['title'] . '",';
    $output .= '"' . $value['firstname'] . '",';
    $output .= '"",';
    $output .= '"' . $value['lastname'] . '",';
    $output .= '"",';
    $output .= '"' . $value['password_hash'] . '",';
    $output .= '"",';
    # Billing info
    $output .= '"' . $value['title'] . '",';
    $output .= '"' . $value['firstname'] . '",';
    $output .= '"",';
    $output .= '"' . $value['lastname'] . '",';
    $output .= '"",';
    $output .= '"' . $value['b_address'] . '",';
    $output .= '"' . $value['b_city'] . '",';
    $output .= '"' . $value['b_real_state'] . '",';
    $output .= '"' . $value['b_country'] . '",';
    $output .= '"' . $value['b_zipcode'] . '",';
    $output .= '"' . $value['phone'] . '",';
    $output .= '"' . $value['company'] . '",';
    $output .= '"' . $value['fax'] . '",';
    # Shipping Info
    $output .= '"' . $value['title'] . '",';
    $output .= '"' . $value['firstname'] . '",';
    $output .= '"",';
    $output .= '"' . $value['lastname'] . '",';
    $output .= '"",';
    $output .= '"' . $value['s_address'] . '",';
    $output .= '"' . $value['s_city'] . '",';
    $output .= '"' . $value['s_real_state'] . '",';
    $output .= '"' . $value['s_country'] . '",';
    $output .= '"' . $value['s_zipcode'] . '",';
    $output .= '"' . $value['phone'] . '",';
    $output .= '"' . $value['company'] . '",';
    $output .= '"' . $value['fax'] . '",';
    $output .= '"default",';
    $output .= '"0"';
    $output .= "1512";
}
print func_export_csv($output,"xcart_customers") ;
function resolveState($b_state,$b_country,$s_state,$s_country){
    global $states;
    $result = array();
    foreach($states as $key=>$value){
        // Billing
        if(($value['state_code']==$b_state) && ($value['country_code']==$b_country))
             $result['billing'] = $value['state'];
        // Shipping
        if(($value['state_code']==$s_state) && ($value['country_code']==$s_country))
             $result['shipping'] = $value['state'];
    }
    if(empty($result['billing']))
        $result['billing'] = $b_state;
    if(empty($result['shipping']))
        $result['shipping'] = $s_state;
    return $result;
}
function func_export_csv($data,$title) {
    $output = $data;
    $size_in_bytes = strlen($output);
       header("Content-type: application/vnd.ms-excel");
       header("Content-disposition: csv; filename=".$title . '_' . date("Y-m-d") . ".csv; size=$size_in_bytes");
       return $output;
}

Here is the original posting by Spydor:
Magento – Migrating users from Xcart to Magento – General Forum – eCommerce Software for Growth.

How To Setup Multiple Magento Stores

Crucial Web Hosting has a very well put together blog posting detailing how one can utilize Magento’s MultiStore ability and different approachs to take for whatever reason they maybe.  Like subdomains, subfolders or even completely different domains.  They also go into how to setup SSL for each of the stores if you are needing to keep Site A’s identity hidden from Site B’s.  It’s defiantly worth checking out or atleast bookmarking for a later read.

arrow Crucial Web Hosting » Blog » How To Setup Multiple Magento Stores.

arrow A GUIDE TO SETTING UP MULTIPLE STORES IN MAGENTO