Array_map для коллекции с интерфейсами массива?
У меня есть класс под названием Collection
, который хранит объекты того же типа.
Collection
реализует интерфейсы массива: Iterator
, ArrayAccess
, SeekableIterator
и Countable
.
Я хотел бы передать объект Collection
в качестве аргумента массива функции array_map
. Но это не с ошибкой
Предупреждение PHP: array_map(): Аргумент # 2 должен быть массивом
Могу ли я достичь этого, реализуя другие/более интерфейсы, чтобы объекты Collection
рассматривались как массивы?
Ответы
Ответ 1
Функция array_map()
не поддерживает Traversable
в качестве аргумента массива, поэтому вам нужно будет выполнить шаг преобразования:
array_map($fn, iterator_to_array($myCollection));
Помимо повторения итерации по коллекции дважды, он также дает массив, который впоследствии не будет использоваться.
Другой способ - написать собственную функцию карты:
function map(callable $fn)
{
$result = array();
foreach ($this as $item) {
$result[] = $fn($item);
}
return $result;
}
Обновление
Судя по вашему делу, кажется, что вас даже не интересует результат операции с картой; поэтому имеет смысл использовать iterator_apply()
.
iterator_apply($myCollection, function($obj) {
$obj->method1();
$obj->method2();
return true;
});
Ответ 2
array_map
хочет, как следует из названия, массивов. В конце концов, он не называется iterator_map
.;)
Помимо iterator_to_array()
, который создает потенциально большой временный массив, нет никакого трюка для создания итеративных объектов с array_map
.
Функциональная библиотека PHP имеет реализацию map
, которая работает в любой итеративной коллекции.
Ответ 3
Я придумал следующее решение:
//lets say you have this iterator
$iterator = new ArrayIterator(array(1, 2, 3));
//and want to append the callback output to the following variable
$out = [];
//use iterator to apply the callback to every element of the iterator
iterator_apply(
$iterator,
function($iterator, &$out) {
$current = $iterator->current();
$out[] = $current*2;
return true;
},
array($iterator, &$out) //arguments for the callback
);
print_r($out);
Таким образом, вы можете сгенерировать массив без итерации дважды, как вам хотелось бы с помощью подхода, например:
$iterator = new ArrayIterator(array(1,2,3));
$array = iterator_to_array($iterator); //first iteration
$output = array_map(function() {}, $array); //second iteration
Удачи!
Ответ 4
Если вы не заинтересованы в создании нового массива, который является функцией, сопоставленной с исходным массивом, вы можете просто использовать цикл foreach (потому что вы реализуете Iterator
).
foreach($item in $myCollection) {
$item->method1();
$item->method2();
}
если вы действительно хотите использовать карту, тогда я думаю, вам придется реализовать свои собственные. Я бы предложил сделать его методом в коллекции, например:
$mutatedCollection = $myCollection->map(function($item) {
/* do some stuff to $item */
return $item;
});
Я бы спросил, действительно ли вы хотите использовать map
, или просто вы имеете в виду foreach
Ответ 5
Я просто наткнулся на этот вопрос, и мне удалось преобразовать коллекцию в массив, чтобы она заработала:
array_map($cb, (array) $collection);
Отказ от ответственности Для первоначального вопроса это не может быть подходящим вариантом, но я нашел вопрос, пытаясь решить проблему, которую я решил с помощью этого решения. Я рекомендовал бы использовать пользовательскую карту итератора, где это возможно/жизнеспособно.
Другой вариант - сделать что-то вроде этого:
foreach($collection as &$item) {
$item = $cb($item);
}
который будет мутировать основную коллекцию.
РЕДАКТИРОВАТЬ:
Было отмечено, что приведение к массиву может иметь нежелательные побочные эффекты. Было бы лучше добавить метод в вашу коллекцию, чтобы он возвращал массив из итератора, и перебрать его, либо добавить метод map
который принимает обратный вызов и запускает цикл на базовом итераторе.