Sunday, September 2, 2012

Fix for Magento’s Shipping API’s “sales_order_shipment.list” call


When searching for shipments, the most likely parameter we’d want to search by is “order_increment_id” i.e. what shipments are attached to a given order.  However, this is not possible with the way the API is written.  If we attempt to do a search of the form:

$params = array(array('order_increment_id' => $value));
$shipments = $this->call('sales_order_shipment.list', $params);


we get back this error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'order_increment_id' in 'where clause'

The reason for this lies in:
/core/Mage/Sales/Model/Order/Shipment/Api.php

This call does not work correctly:
$collection = Mage::getResourceModel('sales/order_shipment_collection')
   ->addAttributeToSelect('increment_id')
   ->addAttributeToSelect('created_at')
   ->addAttributeToSelect('total_qty')
   ->joinAttribute('shipping_firstname', 'order_address/firstname', 'shipping_address_id', null, 'left')
    ->joinAttribute('shipping_lastname', 'order_address/lastname', 'shipping_address_id', null, 'left')
    ->joinAttribute('order_increment_id', 'order/increment_id', 'order_id', null, 'left')
    ->joinAttribute('order_created_at', 'order/created_at', 'order_id', null, 'left');

If you go search for this on the net, you will find in various places where it says that “joinAttribute” will join the “order” table using the “using the “order_id” field as the join ON column and map “increment_id” to “order_increment_id”.   But after much effort trying to figure out what parameters you can pass to this call to access said “order_increment_id” field, you will eventually have to give up in frustration.  The reason for this lies in the “joinAttribute” function.

Here it is in all its glory
From:  app/code/core/Mage/Sales/Model/Mysql4/Collection/Abstract.php

/**
 * Backward compatibility with EAV collection
 * @todo implement join functionality if necessary
 */
 public function joinAttribute($alias, $attribute, $bind, $filter=null,
                                $joinType='inner', $storeId=null)
 {
    return $this;
 }

Note the “todo”.   This function currently does absolutely nothing but return “$this” so as to not break the chaining.  So if we want to be able to search for shipments by “order_increment_id”, we are going to have to modify either “joinAttribute” or the API. 

I opted to go the route of modifying the API.  By adding one line, we can join in the order shipment grid table ‘sales_flat_order_shipment_grid’ and pick up the field we are after.  But of course, we don’t want to modify the core file itself, so we move it to:
app/code/local/Mage/Sales/Model/Mysql4/Collection/Abstract.php first.

$collection = Mage::getResourceModel('sales/order_shipment_collection')
   ->addAttributeToSelect('increment_id')
   ->addAttributeToSelect('created_at')
   ->addAttributeToSelect('total_qty')
   ->join('sales/shipment_grid',
          '`main_table`.`increment_id`=`sales/shipment_grid`.`increment_id`',
          'order_increment_id')
   ->joinAttribute('shipping_firstname', 'order_address/firstname',
                   'shipping_address_id', null, 'left')
   ->joinAttribute('shipping_lastname', 'order_address/lastname',
                   'shipping_address_id', null, 'left')
   ->joinAttribute('order_increment_id', 'order/increment_id',
                   'order_id', null, 'left')
   ->joinAttribute('order_created_at', 'order/created_at', 'order_id', null, 'left');  


Note that I opted not to remove the useless “joinAttribute” method calls.  It will be easier to figure out what to do with this if and when Magento finally fixes this if we leave them in so it’s more obvious what we’ve done.

Saturday, August 4, 2012

Loading Configuration Files and Accessing Arrays Using Object Operator (->)


Sometimes is nice to be able to access a multi-dimensional or even a single-dimensional array with the object operator rather than clunky array brackets. 

$config->logs->event->filename;

vs

$config['logs']['event']['filename'];

This is easily done using the following class:

<?php

/**
 * array_to_object.php
 *
 * Class for converting arrays to objects
 *
 * @author Paul Snell
 *
 */
class ArrayToObject {

   /**
    * @var array
    */
   private $_arrayData = array();
  
   /*
    * ctor
    * @param array $arrayData
    */
   public function __construct($arrayData) {
      $this->_arrayData = $arrayData;
      foreach ($arrayData as $key => $data) {
         if (is_array($data)) {
            $this->_arrayData[$key] = new ArrayToObject($data);
         }
      }
   }
  
   /**
    * Magic __get() method that returns corresponding value
    * for a given array key
    *
    * @param string $name
    *
    * @return var
    */
   public function __get($name) {
      if (array_key_exists($name, $this->_arrayData)) {
         return $this->_arrayData[$name];
      } else {
        // throw new Exception ("No entry found for array key: " . $name);
        return null;
      }
   }
}
?>

If you are working in an environment without in-built support for configuration files (no framework), then you can use the previous class in conjunction with the following one to very elegantly manage configuration data:

<?php

require_once('array_to_object.php');

/**
 *
 * @author Paul Snell
 */
class LoadIni {
   /**
    * @var string
    */
   private static $_iniData = array();
  
   /**
    * Class constructor
    *
    */
   private function __construct() {
    
   }

   /**
    * Load up a given config file
    *
    * @param  string $iniFilename
    * @return array $config
    */
   public static function load($iniFilename, $section=null) {
      if (!array_key_exists($iniFilename, self::$_iniData)) {
         $base = $_SERVER['DOCUMENT_ROOT'] . '/../config/';
         self::$_iniData[$iniFilename] = parse_ini_file($base . $iniFilename, true);
      }
      if ($section && array_key_exists($section, self::$_iniData[$iniFilename])) {
         $newObj = new ArrayToObject(self::$_iniData[$iniFilename][$section]);
      } else {
         $newObj = new ArrayToObject(self::$_iniData[$iniFilename]);
      }
      return $newObj;
   }
}

Note that this class is a Singleton.  It is only instantiated once.  Any subsequent calls for the same configuration file will just return the existing configuration data. 

However, it will create a fresh copy of the ArrayToObject() class every time it is called.  I did that because of the option of passing in a “section” identifier.  With a little effort it could probably still be modified not to rebuild the ArrayToObject() object even then.  But in the performance has never been a significant issue, I never bothered to do it.

Using the above two classes, if you are have a configuration file that contains something like this,

; config.ini
[amazon]
queueName = event_transaction_queue
verifyHash = 1
minRetryTime = 60

[logs]
PrimaryLogger = /tmp/plog.log
PrimaryLoggerLevel = 5

then if we can find the minRetryTime quite easily via the following function calls:

$config = LoadIni::load('config.ini');
$amazonRetry = $config->amazon->minRetryTime;

 We can also do the following:

$config = LoadIni::load('config.ini');
$amazon $config->amazon;
$retryTime = $amazon->minRetryTime;
$queueName $amazon->queueName;

For a big project sans framework, this produces a more elegant and manageable solution than trying to use defines in a .php configuration file.