merging of associative arrays broken

stella - October 21, 2009 - 19:10
Project:Ubercart
Version:6.x-2.0-rc7
Component:Code
Category:bug report
Priority:normal
Assigned:Unassigned
Status:needs review
Description

I'm taking _uc_price_get_handlers() as an example, but this may occur elsewhere too.

Using hook_uc_price_handler I should be able to override the default 'options' array to be used in uc_price(). The _uc_price_get_handlers() function sets up a default $handlers['options'] before invoking each of the hook_uc_price_handler implementations. When a hook is found which provides some options overrides, the following happens:

      if (isset($hook['options']) && is_array($hook['options'])) {
        $handlers['options'] += $hook['options'];
      }

However, this doesn't work in the way I think it was intended. It's my understanding that the options returned by the hooks ($hook['options']) should overwrite the default options configured in $handlers['options']. This code doesn't do that however - it will only merge in new options and won't overwrite the defaults. This is because when merging associative arrays using += and when there is the same key in both arrays, the value of the key on the left will be retained, for example,

$a = array('a' => 123, 'b' => 456);
$b = array('a' => 999, 'c' => 888);
$a += $b;

The new value for $a will be:
$a = array('a' => 123, 'b' => 456, 'c' => 888);

Is this really the intended behaviour? I hope not.

#1

fenstrat - October 22, 2009 - 01:37

Good point. And no I'd also hope that is not the intended behaviour - as shown by:

<?php
 
// Merge any incoming options, giving them precedence.
 
$options += $handlers['options'];
?>

Rather I'd say it's just a miss on the fact that += keeps the left most keys. Whereas the key's at left need to be overwritten.

array_merge overwrites existing string keys.

array_merge: "If the input arrays have the same string keys, then the later value for that key will overwrite the previous one."

So it should be:

<?php
 
// Merge any incoming options, giving them precedence.
 
$options = array_merge($options, $handlers['options']);
?>

And the same for (most other?) += occurrences in uc_price().

array_merge handles numeric keys differently, but this shouldn't be an as these arrays always have string based keys.

Would be good for chaos (who was largely responsible for uc_price) to chime in here and make sure this is correct.

#2

Island Usurper - October 28, 2009 - 15:42

I think when the hook options are added to the handler options is the only time it's wrong. Every other time it's adding the default values to the user-supplied options.

I've kept the + operator just in case something passes in numeric keys. They shouldn't, but this should mean that nothing unexpected happens to them.

AttachmentSize
610916_price_hook_options.patch 464 bytes

#3

Island Usurper - October 28, 2009 - 15:42
Status:active» needs review

#4

stella - October 29, 2009 - 11:10
Status:needs review» needs work

I think the same thing needs to be done to line 114 of uc_store/includes/uc_price.inc too, doesn't it?

  // Merge any incoming options, giving them precedence.
  $options += $handlers['options'];

#5

Island Usurper - November 2, 2009 - 22:19
Status:needs work» needs review

I don't believe so. Whoever calls uc_price() can pass in $options themselves, and they may want to do things differently from the handlers. Hard-coded values are trumped by alter handlers, but they are trumped by user-given data.

However, that means the 'cache' key is set in the wrong place. Handlers can't change that setting because it is added to the $options before their settings. I suppose it really belongs with the other default values for $options.

AttachmentSize
610916_price_hook_options.patch 987 bytes
 
 

Drupal is a registered trademark of Dries Buytaert.