PHP Conference Japan 2024

对象迭代

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 迭代遍历了所有可以访问的 可见 属性。

添加笔记

用户贡献笔记 17 条笔记

21
php dot net dot nsp at cvogt dot org
12 年前
关于使用 current() 等与迭代器一起使用仍然存在一个未解决的 bug
https://bugs.php.net/bug.php?id=49369
18
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']="只是一个简单的字符串";
$data['number']=33;
$data['array']['another_string']="Alpha";
$data['array']['some_object']=new stdClass();
$data['array']['another_array']['x']['y']="LOL @ 任何说这做不到的人!";
$data['blank_array']=array();

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

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

?>

(在artur at jedlinski... 提到的两个链接中,他们说你不能使用引用,所以我没有使用它们。
我的实现使用了递归对象)

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

https://php.net/~helly/php/ext/spl/interfaceArrayAccess.html
5
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
5
chad 0x40 herballure 0x2e com
18 年前
为 valid() 给出的示例代码在数组包含 FALSE 值时会中断。当它到达 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 接口时,您的 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 年前
为了阐明moechofe帖子中关于PHP的内容,你可以使用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
rune at zedeler dot dk
17 年前
来自knj at aider dot dk的迭代器模板不会产生正确的结果。
如果你这样做
<?
reset($a);
next($a);
echo current($a);
?>
其中$a是根据建议的模板定义的,那么将输出第一个元素,而不是预期的第二个元素。
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
/**
* 由于数组是不可变的,因此应该使用内部索引遍历元素数量来进行前/后验证。
*/
class ArrayIterator implements Iterator {
private
$array;
public function
__construct($array) {
if ( !
is_array($array))
throw new
IllegalArgumentException('参数 0 不是数组');
$this->array = $array;
$this->rewind();
}
public function
valid() {
return
current($this->array) !== false;
// 这是一个不好的方法(应该使用 array_keys 和索引)
}
public function
key() {
return
key($this->array);
}
public function
current() {
return
current($this->array);
}
public function
next() {
if (
$this->valid())
throw new
NoSuchElementException('数组结尾');
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);
}
}
?>

在 Java 1.5 中使用 Iterable 时,您可以进行这样的循环

<?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()) {
$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 年前
您应该准备好,在迭代器的 `next` 方法被调用之前,其 `current` 方法可能会被调用。这在 `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()` 不会产生预期结果
0
mike at eastghost dot com
1年前
随意类型转换的美好时光即将结束。找到这个讨厌的 bug 花费了我们太多时间。

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 {...}

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

在我看来,迭代器手册页没有充分说明这种严格性。
-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;
?>

我认为这应该写在文档中的某个地方,但我找不到。
To Top