我正在使用将 SQL 结果作为可迭代对象返回的 PHP 框架。问题是我有一个返回一行的 SQL 查询,我不想创建一个foreach
-loop 来获取第一个 - 也是唯一一个 - 元素。
那么我该怎么做呢?
这些不起作用:
$obj->item(0)->propName;
$obj->next()->propName;
$obj[0]->propName;
有任何想法吗?
假设“可迭代”,您的意思是对象实现了Iterator
接口,您可以使用它$obj->current()
来检索当前元素,所以$obj->current()->propName
可能就是您想要的。
如果迭代器指针已被移动(例如,如果它在 a 中使用foreach
,它不会重置指针),那么您可以在调用$obj->rewind()
之前将指针设置回第一个元素$obj->current()
。
只有两个类接口可以遍历:Iterator和 IteratorAggregate(任何其他接口都必须实现其中之一)。
Iterator 的第一个元素可以通过以下方式获得:
$iterator->rewind();
if (!$iterator->valid()) {
throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();
如果您确定:
$iterator
从未被遍历过foreach
,或者如果它被遍历过,但循环从未被break
or停止过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
对于任何类型的 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;
}
这既适用于数组和迭代器对象,也只计算迭代器中的第一项,这在某些情况下可以提高性能。
另一种我个人认为不太冗长的替代方法是先将迭代器转换为数组,然后检查数组是否为空。
$result = iterator_to_array($iterator, true);
if(!empty($result)){
$user = end($result)->id;
}
当您知道您的迭代器将只返回一个“迭代”时非常有用。但是,如果您将拥有数千个,请注意您将首先使用所有数据填充数组,这可能会增加您的内存使用量,直到数组再次为空。