關於使用 current() 等與迭代器,仍然有一個未解決的錯誤。
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() 等與迭代器,仍然有一個未解決的錯誤。
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 implementation that behaves like a multi-level array<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>If attempting to read an offset that doesn't exist it returns a blank object! Use isset() to check if it exists!<br>";
echo "'non_existent' Isset? "; print_r(isset($data['array']['non_existent'])); echo "<br />";
echo "<pre>"; print_r($data['blank_array']); echo "</pre>A blank array unfortunately returns similar results :(<br />";
echo "'blank_array' Isset? "; print_r(isset($data['blank_array'])); echo "<hr />";
echo "<pre>"; print_r($data); echo "</pre> (non_existent remains in the structure. If someone can help to solve this I'll appreciate it)<hr />";
echo "Display some value that exists: ".$data['array']['another_string'];
?>
(在 artur at jedlinski... 提到的兩個連結中,他們說你不能使用參考,所以我沒有使用它們。
我的實作使用遞迴物件。)
如果有人找到更好(更簡潔)的解決方案,請發送電子郵件給我。
謝謝,
Wave.
使用 key() next() rewind() 的 Iterator 介面比使用 ArrayIterator::next()、ArrayIterator::rewind() 等擴展 ArrayIterator 還要慢。
使用 SPL ArrayAccess 介面來像陣列一樣呼叫物件
https://php.dev.org.tw/~helly/php/ext/spl/interfaceArrayAccess.html
上面 MyIterator::valid() 的方法不好,因為它
會在遇到 0 或空字串的項目時出錯,請改用 key()
<?php
public function valid()
{
return ! is_null(key($this->var));
}
?>
閱讀關於 current() 缺點的說明
https://php.dev.org.tw/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
為了釐清 php at moechofe 的文章,您可以使用 SPL 來覆寫類別的陣列運算子。這一點,加上物件的新功能和自動載入(以及其他許多功能),讓我完全愛上了 PHP5。您也可以在手冊的 SPL 部分找到這些資訊,但我還是會在這裡發布,以免被忽略。以下的 Collection 類別將允許您像使用陣列一樣使用該類別,同時也可以使用 foreach 迭代器
<?php
類別 Collection 實作 ArrayAccess,IteratorAggregate
{
公用 $objectArray = 陣列();
//**這些是必要的迭代器函式
函式 offsetExists($offset)
{
如果(isset($this->objectArray[$offset])) 傳回 真;
否則 傳回 假;
}
函式 & offsetGet($offset)
{
如果 ($this->offsetExists($offset)) 傳回 $this->objectArray[$offset];
否則 傳回 (假);
}
函式 offsetSet($offset, $value)
{
如果 ($offset) $this->objectArray[$offset] = $value;
否則 $this->objectArray[] = $value;
}
函式 offsetUnset($offset)
{
unset ($this->objectArray[$offset]);
}
函式 & getIterator()
{
傳回 新 ArrayIterator($this->objectArray);
}
//**結束必要的迭代器函式
公用函式 doSomething()
{
echo "我正在做一些事";
}
}
?>
我超愛 PHP 中新的 SPL 東西! 使用範例如下
<?php
類別 Contact
{
保護 $name = 空;
公用函式 set_name($name)
{
$this->name = $name;
}
公用函式 get_name()
{
傳回 ($this->name);
}
}
$bob = 新 Collection();
$bob->doSomething();
$bob[] = 新 Contact();
$bob[5] = 新 Contact();
$bob[0]->set_name("超人");
$bob[5]->set_name("某個人的名字");
foreach ($bob 作為 $aContact)
{
echo $aContact->get_name() . "\r\n";
}
?>
這樣就可以了。這讓程式碼變得更簡潔易懂,真是太棒了。這正是我所期望 PHP5 的發展方向!
如果您在字串中定義了實作 IteratorAggregate 的類別,
您就不能使用預設的;
<?
...
public function getIterator() {
return new MyIterator(\\$this-><任何東西>);
}
..
?>
至少在您想使用 eval(<該字串>) 時不行。
您必須使用
<?
...
public function getIterator() {
\\$arrayObj=new ArrayObject(\\$this-><任何東西>);
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 prev();
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;
// 這是個不好的方法 (應該使用 arrays_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()) {
String 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;
}
}
}
你應該預期迭代器的 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;
}
請記住,實際上唯一使用 Iterator 的 PHP 迭代結構是 foreach()。
任何應用於實作 Iterator 物件的 each() 或 list() 都將不會提供預期的結果。
隨意類型轉換的美好日子即將結束。找到這個棘手的錯誤花費了我們太多時間。
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 手冊頁面中沒有足夠清楚地說明這種極端的嚴格性。
如果你想做這樣的事情
<?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;
?>
我認為這應該寫在文件中的某個地方,但我找不到。