在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
对其进行可视化。这是第一次尝试的结果。在排序后调用。
EDIT2:
我觉得这很没用。
所以我在合并after / before条目之前对数组进行了可视化。 (在$configArray = $this->_modelsConfig;
之后)
这是没有我的shippingprotectiontax
条目:
这是我的shippingprotectiontax
条目:
我没有看到任何明显的矛盾。
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
答案 0 :(得分:20)
感谢你坚持@Alex,这是一个更好的答案,更好的解释:)我的第一个答案是错误的。
PHP为所有数组排序函数实现quicksort(参考zend_qsort.c) 如果数组中的两个记录相同,则它们的位置将被交换。
问题是 giftwrap 总记录,根据_compareTotals()
,该记录大于小计和名义但< strong>等于所有其他总数。
根据$confArray
输入数组的原始顺序以及pivot元素的位置,将 giftwrap 与例如交换 giftwrap 是合法的。 折扣,因为两者相同,即使折扣大于运费。
这可能会从排序算法的角度来解决问题:
有几种可能的解决方案,即使最初的问题是选择快速排序来构建directed acyclic dependency graph
有趣的是,没有很多PHP软件包可供使用。有一个孤立的PEAR包Structures_Graph。使用它可能是快速解决方案,但这意味着将$confArray
转换为Structures_Graph
结构(因此可能 快速)。
维基百科很好地解释了这个问题,因此滚动自己的解决方案可能是一个有趣的挑战。 German Wikipedia topological sorting页面将问题分解为逻辑步骤,并且在PERL中也有一个很好的示例算法。
答案 1 :(得分:17)
最后,这是我对这个问题的补丁。
它实现了Vinai建议的拓扑排序。
app/code/core/Mage/Sales/Model/Config/Ordered.php
复制到app/code/local/Mage/Sales/Model/Config/Ordered.php
total-sorting.patch
并调用patch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php
如果要升级,请务必重新执行这些步骤。
该补丁经过测试可与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发布/热修复有多难?