给usort回调函数传递额外的参数

50
我有以下函数,这些函数是 WordPress 函数,但这实际上是一个 PHP 问题。它们根据每个对象元数据中的 artist_lastname 属性来排序我的 $term 对象。
我想在第一个函数中将字符串传递给 $meta。这将使我可以重用此代码,并且可以将其应用于各种元数据属性。
但我不知道如何将其他参数传递给 usort 回调函数。我尝试创建类似于 JS 的匿名函数,但是服务器上的 PHP 版本太旧(v.5.2.17),导致语法错误。
非常感谢任何帮助或指向正确手册的推动!谢谢!
function sort_by_term_meta($terms, $meta) 
{
  usort($terms,"term_meta_cmp");
}

function term_meta_cmp( $a, $b ) 
{
    $name_a = get_term_meta($a->term_id, 'artist_lastname', true);
    $name_b = get_term_meta($b->term_id, 'artist_lastname', true);
    return strcmp($name_a, $name_b); 
}

PHP 版本:5.2.17

6个回答

111

我认为这个问题值得更新。我知道原来的问题是针对PHP 5.2版本的,但我在这里寻找解决方案,并找到了适用于更新版本的PHP的解决方案,想着这可能对其他人也有用。

对于PHP 5.3及以上版本,您可以使用“use”关键字将局部变量引入匿名函数的本地作用域中。所以以下代码应该有效:

function sort_by_term_meta(&$terms, $meta) {
    usort($terms, function($a, $b) use ($meta) {
        $name_a = get_term_meta($a->term_id, 'artist_lastname', true);
        $name_b = get_term_meta($b->term_id, 'artist_lastname', true);
        return strcmp($name_a, $name_b);  
    });
}

更多通用代码

如果您想对数组进行一次排序并需要额外的参数,您可以使用类似于以下的匿名函数:

usort($arrayToSort, function($a, $b) use ($myExtraArgument) {
    //$myExtraArgument is available in this scope
    //perform sorting, return -1, 0, 1
    return strcmp($a, $b);
});

如果您需要一个可重复使用的函数来对需要额外参数的数组进行排序,您可以始终像原问题那样包装匿名函数:

function mySortFunction(&$arrayToSort, $myExtraArgument1, $myExtraArgument2) {
    usort($arrayToSort, function($a, $b) use ($myExtraArgument1, $myExtraArgument2) {
        //$myExtraArgument1 and 2 are available in this scope
        //perform sorting, return -1, 0, 1
        return strcmp($a, $b);
    });
}

太好了!我可以建议修改您的第一个示例,实际上使用$meta参数,这样会更清晰。 - matteo
1
非常有用,谢谢;你也可以这样传递多个参数 ($extraArgument1, $extraArgument2) - Jeff Solomon
你如何定义比较函数而不是作为闭包单独使用(以便您可以重复使用它)? - morksinaanab
usort($terms, function($a, $b) use ($meta) { - 当然,谢谢! - WEBjuju

25
在PHP中,一个回调函数的选项是传递一个包含对象句柄和要在对象上调用的方法名的两个元素数组。例如,如果$obj是类MyCallable的实例,并且你想在$obj上调用MyCallablemethod1方法,那么你可以将array($obj, "method1")作为回调函数传递。
使用这种支持的回调类型的一个解决方案是定义一个类,它基本上像一个闭包类型一样只能使用一次:
function sort_by_term_meta( $terms, $meta ) 
{
    usort($terms, array(new TermMetaCmpClosure($meta), "call"));
}

function term_meta_cmp( $a, $b, $meta )
{
    $name_a = get_term_meta($a->term_id, $meta, true);
    $name_b = get_term_meta($b->term_id, $meta, true);
    return strcmp($name_a, $name_b); 
} 

class TermMetaCmpClosure
{
    private $meta;

    function __construct( $meta ) {
        $this->meta = $meta;
    }

    function call( $a, $b ) {
        return term_meta_cmp($a, $b, $this->meta);
    }
}

我喜欢这个,但我认为我更喜欢@Kato的,因为它被分成了一个小机器 - 每次实例化一个新对象有什么优势吗? - djb
@djb:Kato的解决方案基本上引入了一个全局变量,其中包含$meta字符串。PHP是单线程的,因此这不是一个致命问题。但是,我认为将$meta字符串封装起来更加清晰,这样代码在执行排序时就不会意外更改静态变量的内容。 - Daniel Trebbien
啊,现在我明白了为什么。我能看到它的概念清晰度...已接受。谢谢。 - djb
@Daniel - 我同意封装性,并且没有考虑将实际对象作为第一个参数传递而不是类名;太好了!尽管如此,我认为静态方法的方法稍微更容易理解和跟踪思路,就像djb最初发表的评论一样;权衡之间的权衡;) - Kato

8

假设您可以访问对象和静态变量(PHP 5或更高版本),您可以创建一个对象并直接传递参数,如下所示:

<?php
class SortWithMeta {
    private static $meta;

    static function sort(&$terms, $meta) {
       self::$meta = $meta;
       usort($terms, array("SortWithMeta", "cmp_method"));
    }

    static function cmp_method($a, $b) {
       $meta = self::$meta; //access meta data
       // do comparison here
    }

}

// then call it
SortWithMeta::sort($terms, array('hello'));

假设您无法访问对象/静态内容,则可以使用全局变量:
$meta = array('hello'); //define meta in global

function term_meta_cmp($a, $b) {
   global $meta; //access meta data
   // do comparison here
}

usort($terms, 'term_meta_cmp');

谢谢,这很有趣。我认为我更喜欢这种方法而不是每次创建一个新对象?或者@Daniel的方法更干净? - djb
在静态方法 SortWithMeta::sort 中,我认为需要通过引用传递参数 $terms - Daniel Trebbien
@Daniel - 是的,我认为它需要通过引用传递;现在更新。 - Kato
@djb - 从技术上讲,Daniel's 使用的资源会更少,因为 $meta 可以进行垃圾回收(您也可以在 cmp_method() 中添加 self::$meta = null; 使它们等效!),但除非 $meta 包含大量数据,否则这不会很重要;选择对您来说最容易理解和操作的方式;我都喜欢 ;) - Kato

3

警告: 此函数已从PHP 7.2.0开始被弃用,不建议使用此函数。

文档显示 create_function() 应该在PHP >= 4.0.1上工作。这个是真的吗?

function term_meta_cmp( $a, $b, $meta )  {
    echo "$a, $b, $meta<hr>"; // Debugging output
}
$terms = array("d","c","b","a");
usort($terms, create_function('$a, $b', 'return term_meta_cmp($a, $b, "some-meta");'));

这确实可以工作,但并没有真正解决问题。你仍然将元数据硬编码到比较函数中传递,需要一个变量来解决这个问题。 - Bas
1
太好了!这是我找到的唯一一种在不弄乱索引参数的情况下调用递归闭包的方法!不幸的是,这个函数在PHP 7.2中已经被弃用,应该使用本地匿名函数(闭包)来替换它,因为它使用了eval(),而eval是邪恶的 - AymDev

1

这并不能帮助你使用usort(),但仍然可能会有所帮助。你可以使用其他排序函数之一array_multisort()来对数组进行排序。

思路是构建一个值数组(从get_term_meta()返回的值)并将其与主要的$terms数组进行多重排序。

function sort_by_term_meta(&$terms, $meta) 
{
    $sort_on = array();
    foreach ($terms as $term) {
        $sort_on[] = get_term_meta($term->term_id, $meta, true);
    }
    array_multisort($sort_on, SORT_ASC, SORT_STRING, $terms);
}

0

如何最简单地将参数传递给usort()函数?

我喜欢这里的许多答案,但我想要一个尽可能简单的解决方案,同时也可以演示! 在调用usort时,像这样提供额外的参数...

usort($sortable, [$arg1, $arg2, ... $argn, compareFunction]);

但是一定要先定义这些参数,这样你最终会得到类似于...

$arg1 = 'something';
$arg2 = 'something else';
$argn = 'yet another thing';

usort($sortable, [$arg1, $arg2, ... $argn, compareFunction]);

接下来,$arg1$arg2$argn 将会在 compareFunction() 中可用。

演示一下!

为了演示,这里有一个usort()函数仅考虑要比较的元素的前三个字母...

function cmp ($a, $b) {
     return strcmp(substr($a, 0, $num), substr($a, 0, $num));
}

$terms = ['123a', '123z', '123b',];
$num = 3;
$thing = 4;
usort($terms, [$num, $thing, cmp]);

print_r($terms);

完整在线演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接