Передайте дополнительные параметры для обратного вызова usort

У меня есть следующие функции. Функции WordPress, но на самом деле это вопрос PHP. Они сортируют мои объекты $term в соответствии со свойством artist_lastname в метаданных каждого объекта.

Я хочу передать строку в $meta в первой функции. Это позволило бы мне повторно использовать этот код, поскольку я мог бы применить его к различным свойствам метаданных.

Но я не понимаю, как передать дополнительные параметры обратному вызову usort. Я попытался создать анонимную функцию в стиле JS, но версия PHP на сервере слишком старая и вызвала синтаксическую ошибку.

Любая помощь - или толчок в правый угол руководства - с благодарностью. Спасибо!

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); 
} 

person djb    schedule 22.11.2011    source источник
comment
вы упомянули, что это более старая версия PHP, но не какая версия :( Кроме того, вы не указываете, для чего вы собираетесь использовать $meta (я не вижу его нигде внутри term_meta_cmp)   -  person Kato    schedule 22.11.2011
comment
Привет. Это 5.2.17. Извиняюсь за неясность, я хотел заменить 'artist_lastname'.   -  person djb    schedule 22.11.2011


Ответы (5)


В PHP один вариант для обратного вызова заключается в передаче двухэлементного массива, содержащего дескриптор объекта и имя метода для вызова объекта. Например, если $obj был экземпляром класса MyCallable, и вы хотите вызвать метод method1 класса MyCallable для $obj, то вы можете передать 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);
    }
}
person Daniel Trebbien    schedule 22.11.2011
comment
Мне это нравится, но я думаю, что мне больше нравится @Kato, поскольку он как бы «разбит» на небольшую машину - есть ли преимущества в создании экземпляра нового объекта каждый раз? - person djb; 22.11.2011
comment
@djb: решение Като по существу вводит глобальную переменную, в которой хранится строка $meta. PHP однопоточный, так что это не мешает. Однако я думаю, что лучше инкапсулировать строку $meta, чтобы код не мог случайно изменить содержимое статической переменной во время выполнения сортировки. - person Daniel Trebbien; 22.11.2011
comment
а, теперь я понимаю, почему. Я вижу его концептуальную чистоту... принятую. Благодарю. - person djb; 22.11.2011
comment
@Daniel - я согласен с инкапсуляцией и не рассматривал возможность передачи фактического объекта в качестве первого аргумента вместо имени класса; отличное решение! Тем не менее, я думаю, что статический подход немного легче понять и мысленно следовать, как отмечено в первоначальных комментариях djb; компромиссы компромиссы ;) - person Kato; 22.11.2011

Я думаю, что этот вопрос заслуживает обновления. Я знаю, что первоначальный вопрос был для 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);
    });
}
person Bas    schedule 24.03.2014
comment
Большой! Могу ли я предложить изменить ваш первый пример, чтобы он фактически использовал параметр $meta, чтобы он был более ясным. - person matteo; 05.02.2017
comment
Супер полезно, спасибо; понял, что вы также можете передавать несколько аргументов таким образом ($extraArgument1, $extraArgument2) - person Jeff Solomon; 30.10.2018
comment
Как бы вы определили функцию сравнения не как замыкание, а отдельно (чтобы вы могли использовать ее повторно). - person morksinaanab; 05.03.2019
comment
usort($terms, function($a, $b) use ($meta) { - конечно, спасибо! - person WEBjuju; 26.02.2020

Предполагая, что у вас есть доступ к объектам и статическим данным (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');
person Kato    schedule 22.11.2011
comment
Спасибо, это интересно. Я думаю, что предпочитаю это каждый раз создавать новый объект? Или метод @Daniel чище? - person djb; 22.11.2011
comment
Я считаю, что в статическом методе SortWithMeta::sort параметр $terms нужно передавать по ссылке. - person Daniel Trebbien; 22.11.2011
comment
@Daniel - да, я думаю, это нужно передать по ссылке; обновление сейчас - person Kato; 22.11.2011
comment
@djb - Дэниел технически будет использовать меньше ресурсов, потому что $meta может собирать мусор (вы всегда можете добавить self::$meta = null; внутри cmp_method(), чтобы сделать их эквивалентными!), но это не будет иметь значения, если $meta не содержит массивные данные; используйте то, что легче всего понять и манипулировать для вас; оба нравятся ;) - person Kato; 22.11.2011

Внимание Эта функция устарела, начиная с 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");'));
person John Watson    schedule 22.11.2011
comment
Ну, это работает, но не решает проблему. Вы по-прежнему передаете жестко закодированную мета в функцию сравнения, переменная - это то, что нужно. - person Bas; 24.03.2014
comment
Большой ! это единственный способ вызвать рекурсивное замыкание, не запутавшись с параметром индекса! К сожалению, эта функция устарела в PHP 7.2 и должна быть заменена нативной анонимной функцией (замыканием), поскольку она использует eval(), что зло - person AymDev; 14.08.2018

Это никак не поможет вам с 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);
}
person salathe    schedule 22.11.2011