对象迭代

PHP 提供了一种定义对象的方式,使您可以迭代项目列表,例如使用 foreach 语句。默认情况下,所有 可见 属性将用于迭代。

示例 #1 简单对象迭代

<?php
class MyClass
{
public
$var1 = 'value 1';
public
$var2 = 'value 2';
public
$var3 = 'value 3';

protected
$protected = 'protected var';
private
$private = 'private var';

function
iterateVisible() {
echo
"MyClass::iterateVisible:\n";
foreach (
$this as $key => $value) {
print
"$key => $value\n";
}
}
}

$class = new MyClass();

foreach(
$class as $key => $value) {
print
"$key => $value\n";
}
echo
"\n";


$class->iterateVisible();

?>

上面的示例将输出

var1 => value 1
var2 => value 2
var3 => value 3

MyClass::iterateVisible:
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var

如输出所示,foreach 迭代了所有可以访问的 可见 属性。

添加笔记

用户贡献笔记 19 个笔记

21
php dot net dot nsp at cvogt dot org
12 年前
关于对迭代器使用 current() 等等仍然存在一个未解决的错误
https://bugs.php.net/bug.php?id=49369
19
wavetrex A(nospam)T gmail DOT com
16 年前
通过阅读下面的帖子,我想知道是否真的不可能让 ArrayAccess 实现真正表现得像一个真正的数组(通过多级)。

看来并非不可能。虽然不太漂亮,但可用。

<?php

class ArrayAccessImpl implements ArrayAccess {

private
$data = array();

public function
offsetUnset($index) {}

public function
offsetSet($index, $value) {
// echo ("SET: ".$index."<br>");

if(isset($data[$index])) {
unset(
$data[$index]);
}

$u = &$this->data[$index];
if(
is_array($value)) {
$u = new ArrayAccessImpl();
foreach(
$value as $idx=>$e)
$u[$idx]=$e;
} else
$u=$value;
}

public function
offsetGet($index) {
// echo ("GET: ".$index."<br>");

if(!isset($this->data[$index]))
$this->data[$index]=new ArrayAccessImpl();

return
$this->data[$index];
}

public function
offsetExists($index) {
// echo ("EXISTS: ".$index."<br>");

if(isset($this->data[$index])) {
if(
$this->data[$index] instanceof ArrayAccessImpl) {
if(
count($this->data[$index]->data)>0)
return
true;
else
return
false;
} else
return
true;
} else
return
false;
}

}

echo
"ArrayAccess 实现,它像多级数组一样工作<hr />";

$data = new ArrayAccessImpl();

$data['string']="Just a simple string";
$data['number']=33;
$data['array']['another_string']="Alpha";
$data['array']['some_object']=new stdClass();
$data['array']['another_array']['x']['y']="LOL @ Whoever said it can't be done !";
$data['blank_array']=array();

echo
"'array' Isset? "; print_r(isset($data['array'])); echo "<hr />";
echo
"<pre>"; print_r($data['array']['non_existent']); echo "</pre>如果试图读取不存在的偏移量,它将返回一个空白对象!使用 isset() 检查它是否存在!<br>";
echo
"'non_existent' Isset? "; print_r(isset($data['array']['non_existent'])); echo "<br />";
echo
"<pre>"; print_r($data['blank_array']); echo "</pre>不幸的是,一个空白数组返回类似的结果 :(<br />";
echo
"'blank_array' Isset? "; print_r(isset($data['blank_array'])); echo "<hr />";
echo
"<pre>"; print_r($data); echo "</pre> (non_existent 仍然保留在结构中。如果有人能帮忙解决这个问题,我会很感激)<hr />";

echo
"显示一些存在的值: ".$data['array']['another_string'];

?>

(在 Artur 在 Jedlinski 上面提到的两个链接中…他们说你不能使用引用,所以我没有使用它们。)
我的实现使用递归对象。)

如果有人找到了更好的(更干净的)解决方案,请给我发邮件。
谢谢,
Wave。
8
hlegius at gmail dot com
15 年前
使用 key() next() rewind() 的迭代器接口比扩展 ArrayIterator,使用 ArrayIterator::next(),ArrayIterator::rewind() 等更慢。
7
strrev('ed.relpmeur@ekneos');
19 年前
使用 SPL ArrayAccess 接口将对象作为数组调用

https://php.net/~helly/php/ext/spl/interfaceArrayAccess.html
6
elias at need dot spam
19 年前
上面的 MyIterator::valid() 方法很糟糕,因为它
在值为 0 或空字符串的条目上中断,请改用 key()

<?php
public function valid()
{
return !
is_null(key($this->var));
}
?>

阅读有关 current() 缺点的信息
https://php.net/current
7
rune at zedeler dot dk
17 年前
来自 knj at aider dot dk 的迭代器模板不会产生正确的结果。
如果你执行
<?
reset($a);
next($a);
echo current($a);
?>
其中 $a 是根据建议的模板定义的,那么将输出第一个元素,而不是预期的第二个元素。
1
mike at eastghost dot com
1 年前
随心所欲的类型转换的日子即将结束。找到这个恶魔般的错误花费了我们太多时间。

PHP-8.2.1 抛出了似乎未捕获的错误(最终出现在 /var/log/apache/DOMAIN-ssl-err.log 中),这是因为我们“implements \Iterator”类中必需的接口方法的返回值类型不匹配(多年来一直运行良好,直到我们升级到 8.2.1),而 PHP 要求的接口方法。

特别是

next()
=====
我们的
public function next() {...}

PHP-8.2.1 的
public function next() : void {...}

valid()
======
我们的
public function valid() {...}

PHP-8.2.1 的
public function valid() : bool {...}

key()
====
我们的
public function key() {...}

PHP-8.2.1 的
public function key() : mixed {...}

rewind()
========
我们的
public function rewind() {...}

PHP-8.2.1 的
public function rewind() : void {...}

current()
=======
我们的
public function current() {...}

PHP-8.2.1 的
public function current() : mixed {...}

我们在函数/方法声明中添加了缺少的/现在至关重要的返回值类型,一切立即恢复正常。

在我看来,Iterator 手册页面没有充分说明这种极端严格性。
5
chad 0x40 herballure 0x2e com
18 年前
如果数组包含 FALSE 值,则 valid() 给出的示例代码将中断。当代码遇到 FALSE 时,它只打印出单个“bool(true)”并退出循环。

<?php
$A
= array(TRUE, FALSE, TRUE, TRUE);
while(
current($A) !== FALSE) {
var_dump(current($A));
next($A);
}
?>

相反,应该使用 key() 函数,因为它只在数组末尾返回 NULL。此代码显示所有四个元素,然后退出

<?php
$A
= array(TRUE, FALSE, TRUE, TRUE);
while(!
is_null(key($A))) {
var_dump(current($A));
next($A);
}
?>
3
markushe at web dot de
19 年前
我注意到了一件事
似乎,当你实现 Iterator 接口时,yout 方法 key() 必须返回字符串或整数。

我尝试返回一个对象,并得到了这个错误
MyClass::key() 返回的类型非法
3
artur at jedlinski dot pl
17 年前
应该注意,下面 “just_somedood at yahoo dot com” 描述的 ArrayAccess 功能目前已损坏,因此它几乎无法使用。

阅读以下链接以了解更多信息
http://bugs.php.net/bug.php?id=34783
http://bugs.php.net/bug.php?id=32983
3
just_somedood at yahoo dot com
19 年前
为了澄清 php at moechofe 的帖子,你可以使用 SPL 来覆盖类的数组运算符。这与对象的最新功能和自动加载(以及其他许多功能)一起,让我完全被 PHP5 迷住了。你也可以在手册的 SPL 部分找到此信息,但我也会在这里发布它,以免被忽略。下面的 Collection 类将允许你将类用作数组,同时还使用 foreach 迭代器

<?php

class Collection implements ArrayAccess,IteratorAggregate
{
public
$objectArray = Array();
//**这些是必需的迭代器函数
function offsetExists($offset)
{
if(isset(
$this->objectArray[$offset])) return TRUE;
else return
FALSE;
}

function &
offsetGet($offset)
{
if (
$this->offsetExists($offset)) return $this->objectArray[$offset];
else return (
false);
}

function
offsetSet($offset, $value)
{
if (
$offset) $this->objectArray[$offset] = $value;
else
$this->objectArray[] = $value;
}

function
offsetUnset($offset)
{
unset (
$this->objectArray[$offset]);
}

function &
getIterator()
{
return new
ArrayIterator($this->objectArray);
}
//**结束必需的迭代器函数

public function doSomething()
{
echo
"I'm doing something";
}
}

?>

我喜欢 PHP 中的最新 SPL 功能!下面是一个使用示例

<?php
class Contact
{
protected
$name = NULL;

public function
set_name($name)
{
$this->name = $name;
}

public function
get_name()
{
return (
$this->name);
}
}

$bob = new Collection();
$bob->doSomething();
$bob[] = new Contact();
$bob[5] = new Contact();
$bob[0]->set_name("Superman");
$bob[5]->set_name("a name of a guy");

foreach (
$bob as $aContact)
{
echo
$aContact->get_name() . "\r\n";
}
?>

将完美运行。这使代码变得非常简单易懂,很棒。这正是我希望 PHP5 走的方向!
3
knj at aider dot dk
19 年前
如果你在字符串中定义了实现 IteratorAggregate 的类。
你不能使用默认值;
<?
...
public function getIterator() {
return new MyIterator(\\$this-><What ever>);
}
..
?>
至少在你想使用 eval(<The string>) 的情况下是这样。
你必须使用
<?
...
public function getIterator() {
\\$arrayObj=new ArrayObject(\\$this-><What ever>);
return \\$arrayObj->getIterator();
}
...
?>
4
baldurien at bbnwn dot eu
18 年前
如果你来自 Java,请注意 PHP 中的迭代器是如何工作的!

在 Java 中,迭代器的工作方式如下
<?php
interface Iterator<O> {
boolean hasNext();
O next();
void remove();
}
?>
但在 php 中,接口是这样的(我保留了泛型和类型,因为这样更容易理解)

<?php
interface Iterator<O> {
boolean valid();
mixed key();
O current();
void next();
void previous();
void rewind();
}
?>

1. valid() 基本上等同于 hasNext()
2. next() 与 java 中的 next() 不等效。它不返回任何值,而 Java 的 next() 方法返回下一个对象,并在集合中移动到下一个对象。PHP 的 next() 方法只会向前移动。

以下是一个使用数组的示例,首先是 Java 版本,然后是 PHP 版本

<?php
class ArrayIterator<O> implements Iterator<O> {
private final
O[] array;
private
int index = 0;

public
ArrayIterator(O[] array) {
this.array = array;
}

public
boolean hasNext() {
return
index < array.length;
}

public
O next() {
if ( !
hasNext())
throw new
NoSuchElementException('at end of array');
return array[
index++];
}

public
void remove() {
throw new
UnsupportedOperationException('remove() not supported in array');
}
}
?>

以下是 PHP 版本(使用适当的函数)

<?php
/**
* 由于数组不可变,因此应使用内部
* 索引而不是元素数量来进行 previous/next
* 验证。
*/
class ArrayIterator implements Iterator {
private
$array;
public function
__construct($array) {
if ( !
is_array($array))
throw new
IllegalArgumentException('argument 0 is not an array');
$this->array = array;
$this->rewind();
}
public function
valid() {
return
current($this->array) !== false;
// 这是错误的方法(应该使用 arrays_keys 和 index)
}
public function
key() {
return
key($this->array);
}
public function
current() {
return
current($this->array);
}
public function
next() {
if (
$this->valid())
throw new
NoSuchElementException('at end of array');
next($this->array);
}
public function
previous() {
// 如果 current() 等于数组的第一个项目,则失败
previous($this->array);
}
public function
rewind() {
reset($this->array);
}
}
?>

区别很明显:不要指望 next() 像 Java 中那样返回一些东西,而是使用 current()。 这也意味着你必须预先获取你的集合以设置 current() 对象。 例如,如果你尝试创建目录迭代器(如 PECL 提供的迭代器),rewind 应该调用 next() 来设置第一个元素,依此类推。(并且构造函数应该调用 rewind())

另外,还有另一个区别

<?php
class ArrayIterable<O> implements Iterable<O> {
private final
O[] array;

public
ArrayIterable(O[] array) {
this.array = array;
}

public
Iterator<O> iterator() {
return new
ArrayIterator(array);
}
}
?>

在使用 Iterable 时,在 Java 1.5 中,你可以执行以下循环

<?php
for ( String s : new ArrayIterable<String>(new String[] {"a", "b"})) {
...
}
?>
与以下代码相同

<?php
Iterator
<String> it = new ArrayIterable<String>(new String[] {"a", "b"});
while (
it.hasNext()) {
String s = it.next();
...
}
?>
而在 PHP 中则并非如此
<?php
foreach ( $iterator as $current ) {
...
}
?>
与以下代码相同

<?php
for ( $iterator->rewind(); $iterator->valid(); $iterator->next()) {
$current = $iterator->current();
...
}
?>

(我认为我们也可以使用 IteratorAggregate 来像 Iterable 一样实现它)。

如果你来自 Java,请牢记这一点。

希望这个解释不太长……
1
celsowm
5年前
我创建了一个 grzeniufication 代码的动态版本,允许对多个属性进行反序列化和序列化。

<?php

class Person implements \Serializable {

public
$id;
public
$name;
public
$birthDate;
public
$surname;

public function
serialize() {
return
serialize((array) $this);
}

public function
unserialize($serialized): void {

foreach (
unserialize($serialized) as $p => $v) {
$this->{$p} = $v;
}
}

}
1
phpnet at nicecupofteaandasitdown dot com
19 年前
你应该做好准备,你的迭代器的 current 方法可能会在它的 next 方法被调用之前被调用。 这肯定会在 foreach 循环中发生。 如果你的查找下一个项目的方法很昂贵,你可能想使用类似于以下的方法

private $item;

function next() {
$this->item = &$this->getNextItem();
return $this->item;
}

public function current() {
if(!isset($this->item)) $this->next();
return $this->item;
}
1
doctorrock83_at_gmail.com
17 年前
请记住,实际上唯一使用 Iterator 的 PHP 迭代结构是 foreach()。

任何应用于实现迭代器的对象的 each() 或 list() 不会提供预期的结果
-2
PrzemekG_ at poczta dot onet dot pl
19 年前
如果你想做这样的事情
<?php
foreach($MyObject as $key => &$value)
$value = 'new '.$value;
?>
你必须在你的迭代器对象中通过引用返回的值
<?php
class MyObject implements Iterator
{
/* ...... 其他迭代器函数 ...... */
/* 通过引用返回 */
public function &current()
{
return
$something;
}
?>

这不会改变值
<?php
foreach($MyObject as $key => $value)
$value = 'new '.$value;
?>

这将改变值
<?php
foreach($MyObject as $key => &$value)
$value = 'new '.$value;
?>

我认为这应该在文档中写明,但我找不到。
-16
jille at hexon dot cx
12 年前
请注意,如果您以这种方式实现迭代器而不是使用 IteratorAggregate,则无法嵌套 foreach 循环。 这是因为当内循环完成时,游标位于最后一个元素之后,然后外循环要求下一个元素,并发现游标位于最后一个元素之后,因为内循环将其留在那里。

<?php
// 不会工作!
foreach($collection as $a) {
foreach(
$collection as $b) {
var_dump($a->someFunc($b));
}
}
?>
-12
匿名
6 年前
你们觉得在对象上进行迭代是正常的吗?
To Top