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"
...