Не думаю, что нам нужно добавлять что-то новое в язык. В PHP 7.1 мы получили новый тип iterable
. Используя подсказку типа, вы можете заставить вашу функцию / метод принимать только аргументы, которые могут быть повторены и семантически должны быть повторены (в отличие от объектов, которые не реализуют Traversable
).
// Function that iterates on $iterable
function iterateOverIt(iterable $iterable) {
echo '<ul>'.PHP_EOL;
foreach ($iterable as $item) {
echo "<li>", $item instanceof DateTime ? $item->format("c") : (
isset($item["col"]) ? $item["col"] : $item
), "</li>\n";
}
echo '</ul>'.PHP_EOL;
}
Конечно, это не гарантирует, что l oop будет повторяться хотя бы один раз. В вашем тестовом примере вы можете просто использовать буферизацию вывода, но это не будет применяться к ситуациям, когда вам нужно выполнить действие, только если l oop будет повторяться хотя бы один раз. Это невозможно. Было бы технически невозможно реализовать такую согласованность. Позвольте мне привести простой пример:
class SideEffects implements Iterator {
private $position = 0;
private $IHoldValues = [1, 1, 2, 3, 5, 8];
public function __construct() {
$this->position = 0;
}
public function doMeBefore() {
$this->IHoldValues = [];
echo "HAHA!".PHP_EOL;
}
public function rewind() {
$this->position = 0;
}
public function current() {
return $this->IHoldValues[$this->position];
}
public function key() {
return $this->position;
}
public function next() {
++$this->position;
}
public function valid() {
return isset($this->IHoldValues[$this->position]);
}
}
$it = new SideEffects();
if (1/* Some logic to check if the iterator is valid */) {
$it->doMeBefore(); // causes side effect, which invalidates previous statement
foreach ($it as $row) {
echo $row.PHP_EOL;
}
}
Как вы можете видеть в этом неясном примере, такая совершенная согласованность в вашем коде невозможна.
Причина, по которой у нас есть разные способы итерации в PHP, заключается в том, что не существует решения "один размер подходит всем". То, что вы пытаетесь сделать, - это совсем наоборот. Вы пытаетесь создать решение, которое работало бы для всего, что может быть повторено, даже если оно, вероятно, не должно повторяться. Вместо этого вы должны написать код, который может обрабатывать все подходы соответствующим образом.
function iterateOverIt(iterable $iterable) {
if (is_array($iterable)) {
echo 'The parameter is an array'.PHP_EOL;
if ($iterable) {
// foreach
}
} elseif ($iterable instanceof Iterator) {
echo 'The parameter is a traversable object'.PHP_EOL;
/**
* @var \Iterator
*/
$iterable = $iterable;
if ($iterable->valid()) {
// foreach
}
} elseif ($iterable instanceof Traversable) {
echo 'The parameter is a traversable object'.PHP_EOL;
// I donno, some hack?
foreach ($iterable as $i) {
// do PRE
foreach ($iterable as $el) {
// foreach calls reset first on traversable objects. Each loop should start anew
}
// do POST
break;
}
} else {
// throw new Exception?
}
}
Если вы действительно хотите, вы можете даже включить в него обычные объекты, используя is_object()
, но, как я уже сказал, если он не реализует Traversable
, не выполняйте итерации по нему.