关于使用 current() 等与迭代器一起使用仍然存在一个未解决的 bug
https://bugs.php.net/bug.php?id=49369
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
关于使用 current() 等与迭代器一起使用仍然存在一个未解决的 bug
https://bugs.php.net/bug.php?id=49369
通过阅读下面的帖子,我想知道是否真的不可能使 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。
使用 key() next() rewind() 的 Iterator 接口比使用 ArrayIterator::next(),ArrayIterator::rewind() 等扩展 ArrayIterator 更慢。
使用 SPL ArrayAccess 接口将对象作为数组调用
https://php.net/~helly/php/ext/spl/interfaceArrayAccess.html
上面的 MyIterator::valid() 方法不好,因为它
在值为 0 或空字符串的条目上会中断,请改用 key()
<?php
public function valid()
{
return ! is_null(key($this->var));
}
?>
阅读关于 current() 缺点的信息
https://php.net/current
为 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);
}
?>
一些我注意到的东西
似乎,当您实现 Iterator 接口时,您的 key() 方法必须返回字符串或整数。
我试图返回一个对象,并得到了这个错误
MyClass::key() 返回的类型非法
应该注意的是,“just_somedood at yahoo dot com”下面描述的 ArrayAccess 功能目前已损坏,因此它几乎无法使用。
阅读以下链接以了解更多信息
http://bugs.php.net/bug.php?id=34783
http://bugs.php.net/bug.php?id=32983
为了阐明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发展方向!
如果你在一个字符串中定义实现了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();
}
...
?>
来自knj at aider dot dk的迭代器模板不会产生正确的结果。
如果你这样做
<?
reset($a);
next($a);
echo current($a);
?>
其中$a是根据建议的模板定义的,那么将输出第一个元素,而不是预期的第二个元素。
如果你来自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,请记住这一点。
我希望这个解释不太长……
我创建了 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;
}
}
}
您应该准备好,在迭代器的 `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;
}
请记住,实际上唯一使用 Iterator 的 PHP 迭代结构是 `foreach()`。
任何应用于实现迭代器的对象的 `each()` 或 `list()` 不会产生预期结果
随意类型转换的美好时光即将结束。找到这个讨厌的 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 {...}
我们添加了缺失的/现在非常重要的返回类型到我们的函数/方法声明中,一切立即恢复正常。
在我看来,迭代器手册页没有充分说明这种严格性。
如果您想做这样的事情
<?php
foreach($MyObject as $key => &$value)
$value = 'new '.$value;
?>
您必须通过引用返回迭代器对象中的值
<?php
class MyObject implements Iterator
{
/* ...... 其他迭代器函数 ...... */
/* 通过引用返回 */
public function ¤t()
{
return $something;
}
?>
这不会更改值
<?php
foreach($MyObject as $key => $value)
$value = 'new '.$value;
?>
这将更改值
<?php
foreach($MyObject as $key => &$value)
$value = 'new '.$value;
?>
我认为这应该写在文档中的某个地方,但我找不到。