排序算法:Magento结帐总计错误排序导致错误的运费税计算

时间:2012-02-08 13:30:57

标签: php sorting magento graph-theory

在Magento中,有一个功能,您可以通过指定总计应运行的总数之前和之后来定义总计算的顺序。

我添加了一个自定义总计,如果我将以下行添加到config.xml,则排序错误。错误表示:tax_shipping 之前 <{strong> shipping。 这会导致运费的税费增加两倍。

但这违反了条件

tax_shipping
after: shipping

我的猜测:整套规则必定存在一些矛盾。但我怎样才能找到它?

这是我添加的唯一规则。如果没有此规则,tax_shipping将在shipping之后排序。

<shippingprotectiontax>
    <class>n98_shippingprotection/quote_address_total_shippingprotectionTax</class>
    <after>subtotal,discount,shipping,tax</after>
    <before>grand_total</before>
</shippingprotectiontax>

下面我粘贴Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()中usort调用返回的已排序数组 对于那些没有安装Magento的人,代码如下:

/**
 * uasort callback function
 *
 * @param   array $a
 * @param   array $b
 * @return  int
 */
protected function _compareTotals($a, $b)
{
    $aCode = $a['_code'];
    $bCode = $b['_code'];
    if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {
        $res = -1;
    } elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {
        $res = 1;
    } else {
        $res = 0;
    }
    return $res;
}

protected function _getSortedCollectorCodes()
{

    ...

    uasort($configArray, array($this, '_compareTotals'));
    Mage::log('Sorted:');

    // this produces the output below
    $loginfo = "";
    foreach($configArray as $code=>$data) {
        $loginfo .= "$code\n";
        $loginfo .= "after: ".implode(',',$data['after'])."\n";
        $loginfo .= "before: ".implode(',',$data['before'])."\n";
        $loginfo .= "\n";
    }
    Mage::log($loginfo);

    ...

日志输出:

nominal
after: 
before: subtotal,grand_total

subtotal
after: nominal
before: grand_total,shipping,freeshipping,tax_subtotal,discount,tax,weee,giftwrapping,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax

freeshipping
after: subtotal,nominal
before: tax_subtotal,shipping,grand_total,tax,discount

tax_shipping
after: shipping,subtotal,freeshipping,tax_subtotal,nominal
before: tax,discount,grand_total,grand_total

giftwrapping
after: subtotal,nominal
before: 

tax_subtotal
after: freeshipping,subtotal,subtotal,nominal
before: tax,discount,shipping,grand_total,weee,customerbalance,giftcardaccount,reward

weee
after: subtotal,tax_subtotal,nominal,freeshipping,subtotal,subtotal,nominal
before: tax,discount,grand_total,grand_total,tax

shipping
after: subtotal,freeshipping,tax_subtotal,nominal
before: grand_total,discount,tax_shipping,tax,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax

discount
after: subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee
before: grand_total,tax,customerbalance,giftcardaccount,reward,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax

cashondelivery
after: subtotal,discount,shipping,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal
before: tax,grand_total,grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,customerbalance,giftcardaccount,reward

shippingprotection
after: subtotal,discount,shipping,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal
before: tax,grand_total,grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,customerbalance,giftcardaccount,reward

tax
after: subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery,shippingprotection
before: grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,shippingprotectiontax

shippingprotectiontax
after: subtotal,discount,shipping,tax,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery,shippingprotection
before: grand_total,customerbalance,giftcardaccount,reward

cashondelivery_tax
after: subtotal,discount,shipping,tax,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery
before: grand_total,customerbalance,giftcardaccount,reward

tax_giftwrapping
after: tax,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee
before: grand_total,customerbalance,giftcardaccount

grand_total
after: subtotal,nominal,shipping,freeshipping,tax_subtotal,discount,tax,tax_giftwrapping,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
before: customerbalance,giftcardaccount,reward

reward
after: wee,discount,tax,tax_subtotal,grand_total,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,freeshipping,subtotal,subtotal,nominal,subtotal,nominal,shipping,freeshipping,tax_subtotal,discount,tax,tax_giftwrapping
before: giftcardaccount,customerbalance,customerbalance

giftcardaccount
after: wee,discount,tax,tax_subtotal,grand_total,reward,subtotal,shipping,nominal,freeshipping,tax_shipping,weee
before: customerbalance

customerbalance
after: wee,discount,tax,tax_subtotal,grand_total,reward,giftcardaccount,subtotal,shipping,nominal,freeshipping,tax_shipping,weee
before: 

编辑:

在Vinai回答后,我添加了更多调试代码

$fp = fopen('/tmp/dotfile','w');
fwrite($fp,"digraph TotalOrder\n");
fwrite($fp,"{\n");
foreach($configArray as $code=>$data) {
    $_code = $data['_code'];
    foreach($data['before'] as $beforeCode) {
        fwrite($fp,"$beforeCode -> $_code;\n");
    }
    foreach($data['after'] as $afterCode) {
        fwrite($fp,"$_code -> $afterCode;\n");
    }
}
fwrite($fp,"}\n");
fclose($fp);

用graphviz:dot -Tpng dotfile > viz.png对其进行可视化。这是第一次尝试的结果。在排序后调用。

Visualization

EDIT2:

我觉得这很没用。

所以我在合并after / before条目之前对数组进行了可视化。 (在$configArray = $this->_modelsConfig;之后)

这是没有我的shippingprotectiontax条目:

enter image description here

这是我的shippingprotectiontax条目:

enter image description here

我没有看到任何明显的矛盾。

EDIT3:

在uasort之前配置数组:

array (
  'nominal' => 
  array (
    'class' => 'sales/quote_address_total_nominal',
    'before' => 
    array (
      0 => 'subtotal',
      1 => 'grand_total',
    ),
    'renderer' => 'checkout/total_nominal',
    'after' => 
    array (
    ),
    '_code' => 'nominal',
  ),
  'subtotal' => 
  array (
    'class' => 'sales/quote_address_total_subtotal',
    'after' => 
    array (
      0 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'shipping',
      2 => 'freeshipping',
      3 => 'tax_subtotal',
      4 => 'discount',
      5 => 'tax',
      6 => 'weee',
      7 => 'giftwrapping',
      8 => 'cashondelivery',
      9 => 'cashondelivery_tax',
      10 => 'shippingprotection',
      11 => 'shippingprotectiontax',
    ),
    'renderer' => 'tax/checkout_subtotal',
    'admin_renderer' => 'adminhtml/sales_order_create_totals_subtotal',
    '_code' => 'subtotal',
  ),
  'shipping' => 
  array (
    'class' => 'sales/quote_address_total_shipping',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'freeshipping',
      2 => 'tax_subtotal',
      3 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'discount',
      2 => 'tax_shipping',
      3 => 'tax',
      4 => 'cashondelivery',
      5 => 'cashondelivery_tax',
      6 => 'shippingprotection',
      7 => 'shippingprotectiontax',
    ),
    'renderer' => 'tax/checkout_shipping',
    'admin_renderer' => 'adminhtml/sales_order_create_totals_shipping',
    '_code' => 'shipping',
  ),
  'grand_total' => 
  array (
    'class' => 'sales/quote_address_total_grand',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'nominal',
      2 => 'shipping',
      3 => 'freeshipping',
      4 => 'tax_subtotal',
      5 => 'discount',
      6 => 'tax',
      7 => 'tax_giftwrapping',
      8 => 'cashondelivery',
      9 => 'cashondelivery_tax',
      10 => 'shippingprotection',
      11 => 'shippingprotectiontax',
    ),
    'renderer' => 'tax/checkout_grandtotal',
    'admin_renderer' => 'adminhtml/sales_order_create_totals_grandtotal',
    'before' => 
    array (
      0 => 'customerbalance',
      1 => 'giftcardaccount',
      2 => 'reward',
    ),
    '_code' => 'grand_total',
  ),
  'freeshipping' => 
  array (
    'class' => 'salesrule/quote_freeshipping',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'tax_subtotal',
      1 => 'shipping',
      2 => 'grand_total',
      3 => 'tax',
      4 => 'discount',
    ),
    '_code' => 'freeshipping',
  ),
  'discount' => 
  array (
    'class' => 'salesrule/quote_discount',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'shipping',
      2 => 'nominal',
      3 => 'freeshipping',
      4 => 'tax_subtotal',
      5 => 'tax_shipping',
      6 => 'weee',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'tax',
      2 => 'customerbalance',
      3 => 'giftcardaccount',
      4 => 'reward',
      5 => 'cashondelivery',
      6 => 'cashondelivery_tax',
      7 => 'shippingprotection',
      8 => 'shippingprotectiontax',
    ),
    'renderer' => 'tax/checkout_discount',
    'admin_renderer' => 'adminhtml/sales_order_create_totals_discount',
    '_code' => 'discount',
  ),
  'tax_subtotal' => 
  array (
    'class' => 'tax/sales_total_quote_subtotal',
    'after' => 
    array (
      0 => 'freeshipping',
      1 => 'subtotal',
      2 => 'subtotal',
      3 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'tax',
      1 => 'discount',
      2 => 'shipping',
      3 => 'grand_total',
      4 => 'weee',
      5 => 'customerbalance',
      6 => 'giftcardaccount',
      7 => 'reward',
    ),
    '_code' => 'tax_subtotal',
  ),
  'tax_shipping' => 
  array (
    'class' => 'tax/sales_total_quote_shipping',
    'after' => 
    array (
      0 => 'shipping',
      1 => 'subtotal',
      2 => 'freeshipping',
      3 => 'tax_subtotal',
      4 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'tax',
      1 => 'discount',
      2 => 'grand_total',
      3 => 'grand_total',
    ),
    '_code' => 'tax_shipping',
  ),
  'tax' => 
  array (
    'class' => 'tax/sales_total_quote_tax',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'shipping',
      2 => 'discount',
      3 => 'tax_subtotal',
      4 => 'freeshipping',
      5 => 'tax_shipping',
      6 => 'nominal',
      7 => 'weee',
      8 => 'cashondelivery',
      9 => 'shippingprotection',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'customerbalance',
      2 => 'giftcardaccount',
      3 => 'tax_giftwrapping',
      4 => 'reward',
      5 => 'cashondelivery_tax',
      6 => 'shippingprotectiontax',
    ),
    'renderer' => 'tax/checkout_tax',
    'admin_renderer' => 'adminhtml/sales_order_create_totals_tax',
    '_code' => 'tax',
  ),
  'weee' => 
  array (
    'class' => 'weee/total_quote_weee',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'tax_subtotal',
      2 => 'nominal',
      3 => 'freeshipping',
      4 => 'subtotal',
      5 => 'subtotal',
      6 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'tax',
      1 => 'discount',
      2 => 'grand_total',
      3 => 'grand_total',
      4 => 'tax',
    ),
    '_code' => 'weee',
  ),
  'customerbalance' => 
  array (
    'class' => 'enterprise_customerbalance/total_quote_customerbalance',
    'after' => 
    array (
      0 => 'wee',
      1 => 'discount',
      2 => 'tax',
      3 => 'tax_subtotal',
      4 => 'grand_total',
      5 => 'reward',
      6 => 'giftcardaccount',
      7 => 'subtotal',
      8 => 'shipping',
      9 => 'nominal',
      10 => 'freeshipping',
      11 => 'tax_shipping',
      12 => 'weee',
    ),
    'renderer' => 'enterprise_customerbalance/checkout_total',
    'before' => 
    array (
    ),
    '_code' => 'customerbalance',
  ),
  'giftcardaccount' => 
  array (
    'class' => 'enterprise_giftcardaccount/total_quote_giftcardaccount',
    'after' => 
    array (
      0 => 'wee',
      1 => 'discount',
      2 => 'tax',
      3 => 'tax_subtotal',
      4 => 'grand_total',
      5 => 'reward',
      6 => 'subtotal',
      7 => 'shipping',
      8 => 'nominal',
      9 => 'freeshipping',
      11 => 'tax_shipping',
      12 => 'weee',
    ),
    'before' => 
    array (
      0 => 'customerbalance',
    ),
    'renderer' => 'enterprise_giftcardaccount/checkout_cart_total',
    '_code' => 'giftcardaccount',
  ),
  'giftwrapping' => 
  array (
    'class' => 'enterprise_giftwrapping/total_quote_giftwrapping',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'nominal',
    ),
    'renderer' => 'enterprise_giftwrapping/checkout_totals',
    'before' => 
    array (
    ),
    '_code' => 'giftwrapping',
  ),
  'tax_giftwrapping' => 
  array (
    'class' => 'enterprise_giftwrapping/total_quote_tax_giftwrapping',
    'after' => 
    array (
      0 => 'tax',
      1 => 'subtotal',
      2 => 'shipping',
      3 => 'discount',
      4 => 'tax_subtotal',
      5 => 'freeshipping',
      6 => 'tax_shipping',
      7 => 'nominal',
      8 => 'weee',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'customerbalance',
      2 => 'giftcardaccount',
    ),
    '_code' => 'tax_giftwrapping',
  ),
  'reward' => 
  array (
    'class' => 'enterprise_reward/total_quote_reward',
    'after' => 
    array (
      0 => 'wee',
      1 => 'discount',
      2 => 'tax',
      3 => 'tax_subtotal',
      4 => 'grand_total',
      5 => 'subtotal',
      6 => 'shipping',
      7 => 'nominal',
      8 => 'freeshipping',
      9 => 'tax_subtotal',
      10 => 'tax_shipping',
      11 => 'weee',
      12 => 'subtotal',
      13 => 'shipping',
      14 => 'discount',
      15 => 'tax_subtotal',
      16 => 'freeshipping',
      17 => 'tax_shipping',
      18 => 'nominal',
      19 => 'weee',
      20 => 'freeshipping',
      21 => 'subtotal',
      22 => 'subtotal',
      23 => 'nominal',
      24 => 'subtotal',
      25 => 'nominal',
      26 => 'shipping',
      27 => 'freeshipping',
      28 => 'tax_subtotal',
      29 => 'discount',
      30 => 'tax',
      31 => 'tax_giftwrapping',
    ),
    'before' => 
    array (
      0 => 'giftcardaccount',
      1 => 'customerbalance',
      2 => 'customerbalance',
    ),
    'renderer' => 'enterprise_reward/checkout_total',
    '_code' => 'reward',
  ),
  'cashondelivery' => 
  array (
    'class' => 'cashondelivery/quote_total',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'discount',
      2 => 'shipping',
      3 => 'nominal',
      4 => 'subtotal',
      5 => 'shipping',
      6 => 'nominal',
      7 => 'freeshipping',
      8 => 'tax_subtotal',
      9 => 'tax_shipping',
      10 => 'weee',
      11 => 'subtotal',
      12 => 'freeshipping',
      13 => 'tax_subtotal',
      14 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'tax',
      1 => 'grand_total',
      2 => 'grand_total',
      3 => 'customerbalance',
      4 => 'giftcardaccount',
      5 => 'tax_giftwrapping',
      6 => 'reward',
      7 => 'customerbalance',
      8 => 'giftcardaccount',
      9 => 'reward',
    ),
    'renderer' => 'cashondelivery/checkout_cod',
    'admin_renderer' => 'cashondelivery/adminhtml_sales_order_create_totals_cod',
    '_code' => 'cashondelivery',
  ),
  'cashondelivery_tax' => 
  array (
    'class' => 'cashondelivery/quote_taxTotal',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'discount',
      2 => 'shipping',
      3 => 'tax',
      4 => 'nominal',
      5 => 'subtotal',
      6 => 'shipping',
      7 => 'nominal',
      8 => 'freeshipping',
      9 => 'tax_subtotal',
      10 => 'tax_shipping',
      11 => 'weee',
      12 => 'subtotal',
      13 => 'freeshipping',
      14 => 'tax_subtotal',
      15 => 'nominal',
      16 => 'subtotal',
      17 => 'shipping',
      18 => 'discount',
      19 => 'tax_subtotal',
      20 => 'freeshipping',
      21 => 'tax_shipping',
      22 => 'nominal',
      23 => 'weee',
      24 => 'cashondelivery',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'customerbalance',
      2 => 'giftcardaccount',
      3 => 'reward',
    ),
    '_code' => 'cashondelivery_tax',
  ),
  'shippingprotection' => 
  array (
    'class' => 'n98_shippingprotection/quote_address_total_shippingprotection',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'discount',
      2 => 'shipping',
      3 => 'nominal',
      4 => 'subtotal',
      5 => 'shipping',
      6 => 'nominal',
      7 => 'freeshipping',
      8 => 'tax_subtotal',
      9 => 'tax_shipping',
      10 => 'weee',
      11 => 'subtotal',
      12 => 'freeshipping',
      13 => 'tax_subtotal',
      14 => 'nominal',
    ),
    'before' => 
    array (
      0 => 'tax',
      1 => 'grand_total',
      2 => 'grand_total',
      3 => 'customerbalance',
      4 => 'giftcardaccount',
      5 => 'tax_giftwrapping',
      6 => 'reward',
      7 => 'cashondelivery_tax',
      8 => 'customerbalance',
      9 => 'giftcardaccount',
      10 => 'reward',
    ),
    '_code' => 'shippingprotection',
  ),
  'shippingprotectiontax' => 
  array (
    'class' => 'n98_shippingprotection/quote_address_total_shippingprotectionTax',
    'after' => 
    array (
      0 => 'subtotal',
      1 => 'discount',
      2 => 'shipping',
      3 => 'tax',
      4 => 'nominal',
      5 => 'subtotal',
      6 => 'shipping',
      7 => 'nominal',
      8 => 'freeshipping',
      9 => 'tax_subtotal',
      10 => 'tax_shipping',
      11 => 'weee',
      12 => 'subtotal',
      13 => 'freeshipping',
      14 => 'tax_subtotal',
      15 => 'nominal',
      16 => 'subtotal',
      17 => 'shipping',
      18 => 'discount',
      19 => 'tax_subtotal',
      20 => 'freeshipping',
      21 => 'tax_shipping',
      22 => 'nominal',
      23 => 'weee',
      24 => 'cashondelivery',
      25 => 'shippingprotection',
    ),
    'before' => 
    array (
      0 => 'grand_total',
      1 => 'customerbalance',
      2 => 'giftcardaccount',
      3 => 'reward',
    ),
    '_code' => 'shippingprotectiontax',
  ),
)

更新: Magento Bug Ticket:https://jira.magento.com/browse/MCACE-129

6 个答案:

答案 0 :(得分:20)

感谢你坚持@Alex,这是一个更好的答案,更好的解释:)我的第一个答案是错误的。

PHP为所有数组排序函数实现quicksort(参考zend_qsort.c) 如果数组中的两个记录相同,则它们的位置将被交换。

问题是 giftwrap 总记录,根据_compareTotals(),该记录大于小计名义但< strong>等于所有其他总数

根据$confArray输入数组的原始顺序以及pivot元素的位置,将 giftwrap 与例如交换 giftwrap 是合法的。 折扣,因为两者相同,即使折扣大于运费

这可能会从排序算法的角度来解决问题:

  • 运费&lt; tax_shipping
  • giftwrapping == shipping
  • giftwrapping == tax_shipping

有几种可能的解决方案,即使最初的问题是选择快速排序来构建directed acyclic dependency graph

  • 一个(坏的,短期的)解决方案是在 giftwrapping 总计中添加更多依赖项,即使其他总计可能仍然存在更多问题,而这些问题到目前为止还没有表现出来。
  • 真正的解决方案是为问题实施topological sorting算法。

有趣的是,没有很多PHP软件包可供使用。有一个孤立的PEAR包Structures_Graph。使用它可能是快速解决方案,但这意味着将$confArray转换为Structures_Graph结构(因此可能 快速)。

维基百科很好地解释了这个问题,因此滚动自己的解决方案可能是一个有趣的挑战。 German Wikipedia topological sorting页面将问题分解为逻辑步骤,并且在PERL中也有一个很好的示例算法。

答案 1 :(得分:17)

最后,这是我对这个问题的补丁。

它实现了Vinai建议的拓扑排序。

  1. app/code/core/Mage/Sales/Model/Config/Ordered.php复制到app/code/local/Mage/Sales/Model/Config/Ordered.php
  2. 将修补程序的内容保存到文件total-sorting.patch并调用patch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php
  3. 如果要升级,请务必重新执行这些步骤。

    该补丁经过测试可与Magento 1.7.0.2一起使用

    --- app/code/core/Mage/Sales/Model/Config/Ordered.php   2012-08-14 14:19:50.306504947 +0200
    +++ app/code/local/Mage/Sales/Model/Config/Ordered.php  2012-08-15 10:00:47.027003404 +0200
    @@ -121,6 +121,78 @@
             return $totalConfig;
         }
    
    +// [PATCHED CODE BEGIN]
    +
    +    /**
    +     * Topological sort
    +     *
    +     * Copyright: http://www.calcatraz.com/blog/php-topological-sort-function-384
    +     * And fix see comment on http://stackoverflow.com/questions/11953021/topological-sorting-in-php
    +     *
    +     * @param $nodeids Node Ids
    +     * @param $edges Array of Edges. Each edge is specified as an array with two elements: The source and destination node of the edge
    +     * @return array|null
    +     */
    +    function topological_sort($nodeids, $edges) {
    +        $L = $S = $nodes = array();
    +        foreach($nodeids as $id) {
    +            $nodes[$id] = array('in'=>array(), 'out'=>array());
    +            foreach($edges as $e) {
    +                if ($id==$e[0]) { $nodes[$id]['out'][]=$e[1]; }
    +                if ($id==$e[1]) { $nodes[$id]['in'][]=$e[0]; }
    +            }
    +        }
    +        foreach ($nodes as $id=>$n) { if (empty($n['in'])) $S[]=$id; }
    +        while ($id = array_shift($S)) {
    +            if (!in_array($id, $L)) {
    +                $L[] = $id;
    +                foreach($nodes[$id]['out'] as $m) {
    +                    $nodes[$m]['in'] = array_diff($nodes[$m]['in'], array($id));
    +                    if (empty($nodes[$m]['in'])) { $S[] = $m; }
    +                }
    +                $nodes[$id]['out'] = array();
    +            }
    +        }
    +        foreach($nodes as $n) {
    +            if (!empty($n['in']) or !empty($n['out'])) {
    +                return null; // not sortable as graph is cyclic
    +            }
    +        }
    +        return $L;
    +    }
    +
    +    /**
    +     * Sort config array
    +     *
    +     * public to be easily accessable by test
    +     *
    +     * @param $configArray
    +     * @return array
    +     */
    +    public function _topSortConfigArray($configArray)
    +    {
    +        $nodes = array_keys($configArray);
    +        $edges = array();
    +
    +        foreach ($configArray as $code => $data) {
    +            $_code = $data['_code'];
    +            if (!isset($configArray[$_code])) continue;
    +            foreach ($data['before'] as $beforeCode) {
    +                if (!isset($configArray[$beforeCode])) continue;
    +                $edges[] = array($_code, $beforeCode);
    +            }
    +
    +            foreach ($data['after'] as $afterCode) {
    +                if (!isset($configArray[$afterCode])) continue;
    +                $edges[] = array($afterCode, $_code);
    +            }
    +        }
    +        return $this->topological_sort($nodes, $edges);
    +    }
    +
    +// [PATCHED CODE END]
    +
    +
         /**
          * Aggregate before/after information from all items and sort totals based on this data
          *
    @@ -138,38 +210,16 @@
             // invoke simple sorting if the first element contains the "sort_order" key
             reset($configArray);
             $element = current($configArray);
    +        // [PATCHED CODE BEGIN]
             if (isset($element['sort_order'])) {
                 uasort($configArray, array($this, '_compareSortOrder'));
    +            $sortedCollectors = array_keys($configArray);
    +
             } else {
    -            foreach ($configArray as $code => $data) {
    -                foreach ($data['before'] as $beforeCode) {
    -                    if (!isset($configArray[$beforeCode])) {
    -                        continue;
    -                    }
    -                    $configArray[$code]['before'] = array_unique(array_merge(
    -                        $configArray[$code]['before'], $configArray[$beforeCode]['before']
    -                    ));
    -                    $configArray[$beforeCode]['after'] = array_merge(
    -                        $configArray[$beforeCode]['after'], array($code), $data['after']
    -                    );
    -                    $configArray[$beforeCode]['after'] = array_unique($configArray[$beforeCode]['after']);
    -                }
    -                foreach ($data['after'] as $afterCode) {
    -                    if (!isset($configArray[$afterCode])) {
    -                        continue;
    -                    }
    -                    $configArray[$code]['after'] = array_unique(array_merge(
    -                        $configArray[$code]['after'], $configArray[$afterCode]['after']
    -                    ));
    -                    $configArray[$afterCode]['before'] = array_merge(
    -                        $configArray[$afterCode]['before'], array($code), $data['before']
    -                    );
    -                    $configArray[$afterCode]['before'] = array_unique($configArray[$afterCode]['before']);
    -                }
    -            }
    -            uasort($configArray, array($this, '_compareTotals'));
    +            $sortedCollectors = $this->_topSortConfigArray($configArray);
             }
    -        $sortedCollectors = array_keys($configArray);
    +        // [PATCHED CODE END]
    +
             if (Mage::app()->useCache('config')) {
                 Mage::app()->saveCache(serialize($sortedCollectors), $this->_collectorsCacheKey, array(
                         Mage_Core_Model_Config::CACHE_TAG
    @@ -196,27 +246,6 @@
         }
    
         /**
    -     * Callback that uses after/before for comparison
    -     *
    -     * @param   array $a
    -     * @param   array $b
    -     * @return  int
    -     */
    -    protected function _compareTotals($a, $b)
    -    {
    -        $aCode = $a['_code'];
    -        $bCode = $b['_code'];
    -        if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {
    -            $res = -1;
    -        } elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {
    -            $res = 1;
    -        } else {
    -            $res = 0;
    -        }
    -        return $res;
    -    }
    -
    -    /**
          * Callback that uses sort_order for comparison
          *
          * @param array $a
    

    编辑:还有另一个建议的更改(针对Magento 2):https://github.com/magento/magento2/pull/49

答案 2 :(得分:3)

编辑:这个答案错误。请参阅评论中的讨论。


正如Vinai所说,问题是即使参数是,order函数也会返回0 不平等。我修改了函数以回退键的字符串顺序,如下所示:

protected function _compareTotals($a, $b)
{
    $aCode = $a['_code'];
    $bCode = $b['_code'];
    if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {
        $res = -1;
    } elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {
        $res = 1;
    } else {
        $res = strcmp($aCode, $bCode); // was $res = 0 before
    }
    return $res;
}

答案 3 :(得分:1)

好多年来一直坚持这个!!! +

现在我知道为什么过去的一些项目很难调整关于wee和税收组合的噩梦我可以说,从来没有理解为什么,昨天我发现为什么,后来我发现这篇文章,真是一种耻辱..但大多数时候我需要知道能够搜索问题的反应..

至少对于没有恐惧的linux头的obvius解决方案,是下面的代码,基本上我利用古老的linux命令tsort,特别是在我们需要的方式做一个topolocical命令..

对于我们中间的昆虫学和考古学家来说,一些指针http://www.gnu.org/software/coreutils/manual/html_node/tsort-invocation.html,I正在使用正宗的80年代技术... yummmm

    /**
 * Aggregate before/after information from all items and sort totals based on this data
 *
 * @return array
 */
protected function _getSortedCollectorCodes() {
    if (Mage::app()->useCache('config')) {
        $cachedData = Mage::app()->loadCache($this->_collectorsCacheKey);
        if ($cachedData) {
            return unserialize($cachedData);
        }
    }
    $configArray = $this->_modelsConfig;
    // invoke simple sorting if the first element contains the "sort_order" key
    reset($configArray);
    $element = current($configArray);
    if (isset($element['sort_order'])) {
        uasort($configArray, array($this, '_compareSortOrder'));
        $sortedCollectors = array_keys($configArray);
    } else {
        foreach ($configArray as $code => $data) {
            foreach ($data['before'] as $beforeCode) {
                if (!isset($configArray[$beforeCode])) {
                    continue;
                }
                $configArray[$code]['before'] = array_merge(
                        $configArray[$code]['before'],
                        $configArray[$beforeCode]['before']);
                $configArray[$code]['before'] = array_unique(
                        $configArray[$code]['before']);
                $configArray[$beforeCode]['after'] = array_merge(
                        $configArray[$beforeCode]['after'], array($code),
                        $data['after']);
                $configArray[$beforeCode]['after'] = array_unique(
                        $configArray[$beforeCode]['after']);
            }
            foreach ($data['after'] as $afterCode) {
                if (!isset($configArray[$afterCode])) {
                    continue;
                }
                $configArray[$code]['after'] = array_merge(
                        $configArray[$code]['after'],
                        $configArray[$afterCode]['after']);
                $configArray[$code]['after'] = array_unique(
                        $configArray[$code]['after']);
                $configArray[$afterCode]['before'] = array_merge(
                        $configArray[$afterCode]['before'], array($code),
                        $data['before']);
                $configArray[$afterCode]['before'] = array_unique(
                        $configArray[$afterCode]['before']);
            }
        }
        //uasort($configArray, array($this, '_compareTotals'));
        $res = "";
        foreach ($configArray as $code => $data) {
            foreach ($data['before'] as $beforeCode) {
                if (!isset($configArray[$beforeCode])) {
                    continue;
                }
                $res = $res . "$code $beforeCode\n";
            }
            foreach ($data['after'] as $afterCode) {
                if (!isset($configArray[$afterCode])) {
                    continue;
                }
                $res = $res . "$afterCode $code\n";
            }
        }
        file_put_contents(Mage::getBaseDir('tmp')."/graph.txt", $res);
        $sortedCollectors=explode("\n",shell_exec('tsort '.Mage::getBaseDir('tmp')."/graph.txt"),-1);           
    }
    if (Mage::app()->useCache('config')) {
        Mage::app()
                ->saveCache(serialize($sortedCollectors),
                        $this->_collectorsCacheKey,
                        array(Mage_Core_Model_Config::CACHE_TAG));
    }
    return $sortedCollectors;
}

为了完整起见,我发布了完整的功能,当然至少对我来说是一种魅力......

答案 4 :(得分:1)

我决定选择B计划,重载getSortedCollectors ......直接向我提供绝对控制,如果我要介绍新模块,我必须检查是否需要在这里添加

<?php
class YourModule_Sales_Model_Total_Quote_Collector extends Mage_Sales_Model_Quote_Address_Total_Collector {

    protected function _getSortedCollectorCodes() {
        return array(
            'nominal',
            'subtotal',
            'msrp',
            'freeshipping',
            'tax_subtotal',
            'weee',
            'shipping',
            'tax_shipping',
            'floorfee',
            'bottlediscount',
            'discount',
            'tax',
            'grand_total',
        );
    }

}

答案 5 :(得分:0)

上面的讨论清楚地表明了问题。对于没有在集合的任何两个元素之间定义的顺序函数的数据集,通常的排序不起作用。如果只将某些关系定义为“部分依赖”,则必须使用拓扑排序来遵守声明的“之前”和“之后”语句。在我的测试中,这些声明在结果集中被打破了,具体取决于那里,我添加了额外的模块。恐慌部分,它不仅影响附加模块,而且可能以不可预测的方式改变整个排序结果。    所以,我实现了标准的拓扑排序,它解决了这个问题:

/**
 * The source data of the nodes and their dependencies, they are not required to be symmetrical node cold list other
 * node in 'after' but not present in its 'before' list:
 * @param $configArray
 *  $configArray = [
 *    <nodeCode>     => ["_code"=> <nodeCode>, "after"=> [<dependsOnNodeCode>, ...], "before"=> [<dependedByCode>, ...] ],
 *     ...
 * ]
 * The procedure updates adjacency list , to have every edge to be listed in the both nodes (in one 'after' and other 'before')
 */
function normalizeDependencies(&$configArray) {
    //For each node in the source data
    foreach ($configArray as $code => $data) {
            //Look up all nodes listed 'before' and update their 'after' for consistency
        foreach ($data['before'] as $beforeCode) {
            if (!isset($configArray[$beforeCode])) {
                continue;
            }
            $configArray[$beforeCode]['after'] = array_unique(array_merge(
                $configArray[$beforeCode]['after'], array($code)
            ));
        }
            //Look up all nodes listed 'after' and update their 'before' for consistency
        foreach ($data['after'] as $afterCode) {
            if (!isset($configArray[$afterCode])) {
                continue;
            }
            $configArray[$afterCode]['before'] = array_unique(array_merge(
                $configArray[$afterCode]['before'], array($code)
            ));
        }
    }
}

/**
 *  http://en.wikipedia.org/wiki/Topological_sorting
 *  Implements Kahn (1962) algorithms
 */
function topoSort(&$array) {
    normalizeDependencies($array);
    $result = array(); // Empty list that will contain the sorted elements
    $front = array(); // Set of all nodeCodes with no incoming edges
    //Push all items with no predecessors in S;
    foreach ($array as $code => $data) {
        if ( empty ($data['after']) ) {
            $front[] = $code;
        }
    }
    // While 'front' is not empty
    while (! empty ($front)) {
        //Deque 'frontier' from 'front'
        $frontierCode = array_shift($front);
        //Push it in 'result'
        $result[$frontierCode]= $array[$frontierCode];
        // Remove all outgoing edges from 'frontier';
        while (! empty ($array[$frontierCode]['before'])) {
            $afterCode = array_shift($array[$frontierCode]['before']);
            // remove corresponding edge e from the graph
            $array[$afterCode]['after'] = array_remove($array[$afterCode]['after'], $frontierCode);
            //* if, no more decencies put node into processing queue:
            // if m has no other incoming edges then
            if ( empty ($array[$afterCode]['after']) ) {
                // insert m into 'front'
                array_push($front, $afterCode);
            }
        }
    }
    if(count($result) != count($array)){
        saveGraph($array, 'mage-dependencies.dot');
        throw new Exception("Acyclic dependencies in data, see graphviz diagram: mage-dependencies.dot for details.");
    }
    return $result;
}
 /**
 * Could not find better way to remove value from array
 *
 * @param $array
 * @param $value
 * @return array
 */
protected function array_remove($array, $value){
    $cp = array();
    foreach($array as $b) {
        if($b != $value){
            $cp[]=$b;
        }
    }
    return $cp;
}

/**
 *  Saves graph in the graphviz format for visualisation:
 *    >dot -Tpng /tmp/dotfile.dot > viz-graph.png
 */
function saveGraph($configArray, $fileName){
    $fp = fopen($fileName,'w');
    fwrite($fp,"digraph TotalOrder\n");
    fwrite($fp,"{\n");
    foreach($configArray as $code=>$data) {
        fwrite($fp,"$code;\n");
        foreach($data['before'] as $beforeCode) {
            fwrite($fp,"$beforeCode -> $code;\n");
        }
        foreach($data['after'] as $afterCode) {
            fwrite($fp,"$code -> $afterCode;\n");
        }
    }
    fwrite($fp,"}\n");
    fclose($fp);
}

问题是,将它(或其他拓扑排序)纳入Magento发布/热修复有多难?