ArrayAccess 和 Iterator 介面

另一個有用的功能是在 PHP 中將自定義物件集合作為陣列訪問。PHP(> = 5.0.0)核心有兩個介面支援:ArrayAccessIterator。前者允許你以陣列形式訪問自定義物件。

ArrayAccess 介面

假設我們有一個使用者類和一個儲存所有使用者的資料庫表。我們想建立一個 UserCollection 類,它將:

  1. 允許我們通過其使用者名稱唯一識別符號來解決某些使用者
  2. 在我們的使用者集合上執行基本(不是所有 CRUD,但至少是建立,檢索和刪除)操作

考慮以下來源(以下我們使用自 5.4 版以來可用的短陣列建立語法 []):

class UserCollection implements ArrayAccess {
    protected $_conn;
    
    protected $_requiredParams = ['username','password','email'];
    
    public function __construct() {
        $config = new Configuration();

        $connectionParams = [
            //your connection to the database
        ];
        
        $this->_conn = DriverManager::getConnection($connectionParams, $config);
    }
    
    protected function _getByUsername($username) {
        $ret = $this->_conn->executeQuery('SELECT * FROM `User` WHERE `username` IN (?)',
            [$username]
        )->fetch();
        
        return $ret;
    }
    
    // START of methods required by ArrayAccess interface
    public function offsetExists($offset) {
        return (bool) $this->_getByUsername($offset);
    }

    public function offsetGet($offset) {
        return $this->_getByUsername($offset);
    }

    public function offsetSet($offset, $value) {
        if (!is_array($value)) {
            throw new \Exception('value must be an Array');
        }

        $passed = array_intersect(array_values($this->_requiredParams), array_keys($value));
        if (count($passed) < count($this->_requiredParams)) {
            throw new \Exception('value must contain at least the following params: ' . implode(',', $this->_requiredParams));
        }
        $this->_conn->insert('User', $value);
    }

    public function offsetUnset($offset) {
        if (!is_string($offset)) {
            throw new \Exception('value must be the username to delete');
        }
        if (!$this->offsetGet($offset)) {
            throw new \Exception('user not found');
        }
        $this->_conn->delete('User', ['username' => $offset]);
    }
    // END of methods required by ArrayAccess interface
}

然後我們可以:

$users = new UserCollection();

var_dump(empty($users['testuser']),isset($users['testuser']));
$users['testuser'] = ['username' => 'testuser', 
                      'password' => 'testpassword',
                      'email'    => 'test@test.com'];
var_dump(empty($users['testuser']), isset($users['testuser']), $users['testuser']);
unset($users['testuser']);
var_dump(empty($users['testuser']), isset($users['testuser']));

假設在我們啟動程式碼之前沒有 testuser,它將輸出以下內容:

bool(true)
bool(false)
bool(false)
bool(true)
array(17) {
  ["username"]=>
  string(8) "testuser"
  ["password"]=>
  string(12) "testpassword"
  ["email"]=>
  string(13) "test@test.com"
}
bool(true)
bool(false)

重要提示: 當你檢查是否存在具有 array_key_exists 功能的鍵時,不會呼叫 offsetExists。所以下面的程式碼將輸出 false 兩次:

var_dump(array_key_exists('testuser', $users));
$users['testuser'] = ['username' => 'testuser', 
                      'password' => 'testpassword',
                      'email'    => 'test@test.com'];
var_dump(array_key_exists('testuser', $users));

迭代器

讓我們用 Iterator 介面的一些函式從上面擴充套件我們的類,以允許用 foreachwhile 迭代它。

首先,我們需要新增一個儲存當前迭代器索引的屬性,讓我們將其新增到類屬性 $_position

// iterator current position, required by Iterator interface methods
protected $_position = 1;

其次,讓我們將 Iterator 介面新增到我們的類實現的介面列表中:

class UserCollection implements ArrayAccess, Iterator {

然後新增介面函式本身所需的:

// START of methods required by Iterator interface
public function current () {
    return $this->_getById($this->_position);
}
public function key () {
    return $this->_position;
}
public function next () {
    $this->_position++;
}
public function rewind () {
    $this->_position = 1;
}
public function valid () {
    return null !== $this->_getById($this->_position);
}
// END of methods required by Iterator interface

所以這裡所有這些都是實現兩個介面的類的完整原始碼。請注意,此示例並不完美,因為資料庫中的 ID 可能不是順序的,但這只是為了給你一個主要想法:你可以通過實現 ArrayAccessIterator 介面以任何可能的方式處理物件集合:

class UserCollection implements ArrayAccess, Iterator {
    // iterator current position, required by Iterator interface methods
    protected $_position = 1;
    
    // <add the old methods from the last code snippet here>
    
    // START of methods required by Iterator interface
    public function current () {
        return $this->_getById($this->_position);
    }
    public function key () {
        return $this->_position;
    }
    public function next () {
        $this->_position++;
    }
    public function rewind () {
        $this->_position = 1;
    }
    public function valid () {
        return null !== $this->_getById($this->_position);
    }
    // END of methods required by Iterator interface
}

以及迴圈遍歷所有使用者物件的 foreach:

foreach ($users as $user) {
    var_dump($user['id']);
}

這將輸出類似的東西

string(2) "1"
string(2) "2"
string(2) "3"
string(2) "4"
...