20

我正在使用将 SQL 结果作为可迭代对象返回的 PHP 框架。问题是我有一个返回一行的 SQL 查询,我不想创建一个foreach-loop 来获取第一个 - 也是唯一一个 - 元素。

那么我该怎么做呢?

这些不起作用:

$obj->item(0)->propName;
$obj->next()->propName;
$obj[0]->propName;

有任何想法吗?

4

4 回答 4

24

假设“可迭代”,您的意思是对象实现了Iterator接口,您可以使用它$obj->current()来检索当前元素,所以$obj->current()->propName可能就是您想要的。

如果迭代器指针已被移动(例如,如果它在 a 中使用foreach,它不会重置指针),那么您可以在调用$obj->rewind()之前将指针设置回第一个元素$obj->current()

于 2013-01-22T07:01:18.167 回答
15

只有两个类接口可以遍历:IteratorIteratorAggregate任何其他接口都必须实现其中之一)。


迭代器

Iterator 的第一个元素可以通过以下方式获得:

$iterator->rewind();
if (!$iterator->valid()) {
    throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();

如果您确定:

  • the$iterator从未被遍历过foreach,或者如果它被遍历过,但循环从未被breakor停止过return因为 PHP 7 这一点是无关紧要的,因为 foreach 不影响指针
  • 从来没有叫过$iterator->next();

您可以省略$iterator->rewind();前面示例中的 。

如果你确定元素的$iterator数量为零,你甚至可以省略条件块测试$iterator->valid()

因此,如果保留这些先前的条件,那么您需要的只是:

$firstElement = $iterator->current();

迭代器聚合

IteratorAggergate 实际上只是一个 Iterator 或另一个 IteratorAggregate 的信封。从逻辑上讲,这意味着最后有一个迭代器。

如果您知道迭代器的深度是多少,只需抓住它并像第一个示例一样使用:

$iterator = $iteratorAggregate->getIterator();

但是,如果您现在不了解深度,则可以使用也适用于迭代器的解决方案:

$array = iterator_to_array($iteratorAggregate);
// $array is an ordinary array now
if (count($array) === 0) {
    throw new Exception('There is no any element!');
}
$firstElement = reset($array);

不幸的是,在 biig 数组的情况下,这有点矫枉过正,因为尽管我们只需要一个,但必须创建所有元素的副本。此外,如果迭代器是无限生成器,您将耗尽内存。

有一种可行的解决方案:

while ($iterator instanceof \IteratorAggregate) {
    $iterator = $iterator->getIterator();
}
// $iterator now contains instance of Iterator

我为一组 10000 个成员做了一个简单的基准测试,这个解决方案的速度几乎快了 6 倍。

因此,适用于所有情况的通用解决方案是:

while ($iterator instanceof \IteratorAggregate) {
    $iterator = $iterator->getIterator();
}
$iterator->rewind();
if (!$iterator->valid()) {
    throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();

LLAP

于 2016-07-23T01:07:17.353 回答
2

对于任何类型的 iterables( array, Iterator, IteratorAggregate) 你都可以使用这个函数:

/* public static */ function first(iterable $iterable, $default = null) {
    foreach ($iterable as $item){
        return $item;
    }
    return $default;
}

如果您希望在 iterable 为空时抛出异常,则可以使用该版本:

/* public static */ function firstOrThrow(iterable $iterable, \Throwable $e) {
    foreach ($iterable as $item){
        return $item;
    }
    throw $e;
}

这既适用于数组和迭代器对象,也只计算迭代器中的第一项,这在某些情况下可以提高性能。

于 2020-10-04T14:41:23.293 回答
0

另一种我个人认为不太冗长的替代方法是先将迭代器转换为数组,然后检查数组是否为空。

$result = iterator_to_array($iterator, true);
if(!empty($result)){
   $user = end($result)->id;
}

当您知道您的迭代器将只返回一个“迭代”时非常有用。但是,如果您将拥有数千个,请注意您将首先使用所有数据填充数组,这可能会增加您的内存使用量,直到数组再次为空。

于 2019-12-24T08:41:15.217 回答