PHP 提供了四個核心預定義介面,讓物件能夠像陣列一樣被遍歷、計數、存取。這四個介面分別是 IteratorAggregate、Countable、ArrayAccess 和 Iterator。掌握它們不僅能寫出更優雅的程式碼,更是理解 Laravel、Yii 等主流框架核心原理的關鍵。本文將逐一深入解析並提供實戰範例。
IteratorAggregate extends Traversable {
abstract public Traversable getIterator(void)
}
當物件實作 IteratorAggregate 並使用 foreach 遍歷時,PHP 會呼叫 getIterator() 方法,該方法必須回傳一個實作 Traversable 的物件(如 ArrayIterator)。若未實作此介面,foreach 預設只會遍歷物件的 public 屬性。
// 未實作介面:foreach 遍歷 public 屬性
class My {
private $_data = [
'a' => '燕睿濤',
'b' => 'yanruitao',
'c' => 'LULU',
];
public function getIterator() {
return new ArrayIterator($this->_data);
}
}
$obj = new My;
foreach ($obj as $key => $value) {
echo "$key => $value\n";
}
// 輸出:空(因屬性為 private)
// 實作介面:自訂遍歷行為
class My implements IteratorAggregate {
private $_data = [
'a' => '燕睿濤',
'b' => 'yanruitao',
'c' => 'LULU',
];
public function getIterator() {
return new ArrayIterator($this->_data);
}
}
$obj = new My;
foreach ($obj as $key => $value) {
echo "$key => $value\n";
}
// 輸出:
// a => 燕睿濤
// b => yanruitao
// c => LULU
Countable {
abstract public int count(void)
}
實作 Countable 介面後,對物件呼叫 count() 函式會觸發物件的 count() 方法。未實作時,count() 永遠回傳 1。
class CountMe {
protected $_myCount = 3;
public function count() {
return $this->_myCount;
}
}
$countable = new CountMe();
echo count($countable); // 輸出 1(未實作介面)
class CountMe implements Countable {
protected $_myCount = 3;
public function count() {
return $this->_myCount;
}
}
$countable = new CountMe();
echo count($countable); // 輸出 3(實作介面)
ArrayAccess {
abstract public boolean offsetExists(mixed $offset)
abstract public mixed offsetGet(mixed $offset)
public void offsetSet(mixed $offset, mixed $value)
public void offsetUnset(mixed $offset)
}
| 方法 | 觸發操作 | 範例 |
|---|---|---|
| offsetExists | isset()、empty() | isset($obj['key']) |
| offsetGet | 讀取值 | $value = $obj['key'] |
| offsetSet | 設定值 | $obj['key'] = $value |
| offsetUnset | 刪除值 | unset($obj['key']) |
class myObj implements ArrayAccess {
public function offsetSet($offset, $value) {
echo "offsetSet : {$offset} => {$value}\n";
}
public function offsetExists($offset) {
echo "offsetExists : {$offset}\n";
}
public function offsetUnset($offset) {
echo "offsetUnset : {$offset}\n";
}
public function offsetGet($offset) {
echo "offsetGet : {$offset}\n";
}
}
$obj = new myObj;
$obj[1] = '燕睿濤';
isset($obj['name']);
unset($obj['name']);
$obj['yrt'];
// 輸出:
// offsetSet : 1 => 燕睿濤
// offsetExists : name
// offsetUnset : name
// offsetGet : yrt
class myObj implements ArrayAccess {
private $_data = [];
public function offsetSet($offset, $value) {
$this->_data[$offset] = $value;
}
public function offsetExists($offset) {
return isset($this->_data[$offset]);
}
public function offsetUnset($offset) {
unset($this->_data[$offset]);
}
public function offsetGet($offset) {
return $this->_data[$offset];
}
}
$obj = new myObj;
$obj['yrt'] = '燕睿濤';
var_dump($obj['yrt']); // string(9) "燕睿濤"
var_dump(isset($obj['yrt'])); // bool(true)
unset($obj['yrt']);
var_dump(isset($obj['yrt'])); // bool(false)
var_dump($obj['yrt']); // Notice: Undefined index: yrt
class myObj implements ArrayAccess, IteratorAggregate {
private $_data = [];
public function getIterator() {
return new ArrayIterator($this->_data);
}
// ... ArrayAccess 方法實作
}
$obj = new myObj;
$obj['yrt'] = '燕睿濤';
$obj[1] = '燕睿濤';
$obj['name'] = '燕睿濤';
$obj['age'] = 23;
foreach ($obj as $key => $value) {
echo "{$key} => {$value}\n";
}
// 輸出:
// yrt => 燕睿濤
// 1 => 燕睿濤
// name => 燕睿濤
// age => 23
Iterator extends Traversable {
abstract public mixed current(void)
abstract public scalar key(void)
abstract public void next(void)
abstract public void rewind(void)
abstract public boolean valid(void)
}
| 方法 | 職責 | 呼叫時機 |
|---|---|---|
| rewind() | 重置指標到第一個元素 | foreach 開始前 |
| valid() | 檢查目前位置是否有效 | 每次迭代前 |
| current() | 回傳目前元素值 | 每次迭代中 |
| key() | 回傳目前元素鍵名 | 每次迭代中 |
| next() | 移動指標到下一個元素 | 每次迭代結束 |
class myObj implements Iterator {
private $_data = [];
public function __construct(array $arr) {
$this->_data = $arr;
}
public function current() {
return current($this->_data);
}
public function key() {
return key($this->_data);
}
public function next() {
next($this->_data);
}
public function rewind() {
reset($this->_data);
}
public function valid() {
return $this->key() !== null;
}
}
$t = [
'yrt' => '燕睿濤',
'name' => '燕睿濤',
false,
'燕睿濤'
];
$obj = new myObj($t);
foreach ($obj as $key => $value) {
echo "{$key} => " . var_export($value, true) . "\n";
}
// 輸出:
// yrt => '燕睿濤'
// name => '燕睿濤'
// 0 => false
// 1 => '燕睿濤'
範例中使用 $this->key() !== null 判斷有效性,但若陣列值包含 null 或 false,key() 仍會回傳鍵名(數值索引或字串鍵),不會是 null。真正的陷阱在於使用 current() !== false 判斷,因為陣列值若為 false 會導致提前終止迭代。正確做法是檢查 key() !== null 或使用內部計數器。
| 介面 | 適用場景 | 複雜度 | 靈活度 |
|---|---|---|---|
| IteratorAggregate | 委派現有可遍歷物件(陣列、Iterator) | 低 | 中 |
| Iterator | 完全自訂迭代邏輯(資料庫游標、檔案流、API 分頁) | 高 | 最高 |
| ArrayAccess | 需陣列式語法存取物件內部資料 | 中 | 中 |
| Countable | 需支援 count() 函式 | 極低 | 低 |
IteratorAggregate + Countable + ArrayAccess,獲得完整陣列操作體驗Iterator 逐筆讀取,避免一次載入所有資料到記憶體BaseActiveRecord、Laravel 的 Collection 大量運用這些介面ArrayAccess + JsonSerializable 同時支援陣列存取與 JSON 序列化PHP 的四大預定義介面賦予物件陣列般的操作能力,是物件導向程式設計與陣列操作之間的橋樑。IteratorAggregate 適合簡單委派,Iterator 適合複雜邏輯,ArrayAccess 提供直觀存取語法,Countable 則是輕量級擴充。深入理解它們的運作機制,能讓你寫出更符合 PHP 慣用語、更易維護的程式碼,也是閱讀現代框架原始碼的必備基礎。