Статьи

Сложные объектные итераторы в PHP

В моем предыдущем посте, « Простые итераторы объектов в PHP» , мы узнали, как перебирать элементы массива, определенные в объекте, используя цикл foreach . Однако что, если вам нужно перебирать элементы, которые не хранятся в массиве, например записи из базы данных или строки текста, считанные из файла?

Для следующего примера мы создадим класс, который:

  1. подсчитывает количество пользователей, вошедших в веб-приложение путем извлечения записей из базы данных
  2. создает коллекцию «пользовательских» объектов, которые можно повторять с помощью цикла foreach.

В конечном итоге мы хотим иметь возможность запускать простой код, такой как:


$loggedin = new LoggedIn();

// count users
echo '<p>', count($loggedin), ' user(s) are currently logged in:</p>';

// iterate over users
foreach ($loggedin as $rec => $user) {
	echo
		'Record ', $rec,
		':   ID=', $user->id, 
		' Email=', $user->email,
		'<br/>';
}
примечание: наборы записей можно пройти

Наборы записей PDO уже доступны для обхода, так зачем же включать эту функциональность в класс?

Преимуществами являются инкапсуляция и повторное использование. Разработчику, использующему класс LoggedIn, не нужно беспокоиться о деталях реализации, таких как соединения с базой данных или структуры таблиц. Если данные пользователя или логина были перенесены в другую систему, вы можете изменить класс LoggedIn, не затрагивая другой код.

Кроме того, функциональность класса может быть расширена, не влияя на код, который его использует, например, для получения дополнительных сведений о пользователе, записи в файл журнала, создания предупреждений безопасности и т. Д.

Сначала мы определим базовый класс, в котором будут храниться идентификатор и адрес электронной почты для одного пользователя. Экземпляр этого класса будет возвращен, когда мы переберем зарегистрированных пользователей:

 
class User
{
	public $id, $email;
}

Теперь нам требуется класс LoggedIn, который обрабатывает коллекцию пользователей, вошедших в систему. Обратите внимание, что класс будет реализовывать интерфейсы Countable и Iterator:

 
class LoggedIn implements Countable, Iterator
{

	private $rec;		// database recordset
	private $cursor;		// number of current record
	private $item;		// current user in the collection

Конструктор немедленно вызовет закрытый метод FetchLoggedIn (), который выполняет запрос к базе данных:

 
	// constructor
	public function __construct() {
		$this->FetchLoggedIn();
	}
	
	
	// fetch logged in user records
	private function FetchLoggedIn() {
	
		$this->cursor = -1;
		$this->item = null;
	
		// find logged-in users
		$db = new PDO('mysql:host=localhost;dbname=dbname', 'dbuser', 'dbpass');
		$this->rec = $db->Prepare(
			'SELECT id, email FROM `user` WHERE loggedin=1;', 
			array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)
		);
		
		// convert returned records to a User object
		$this->rec->setFetchMode(PDO::FETCH_INTO, new User());
		
		// run query
		$this->rec->execute();
	
	}

Замечания:

  • $this->cursor Первоначально он установлен в -1, потому что мы не получили никаких записей.
  • $this->item
  • $this->rec
  • $this->rec->setFetchMode Когда мы выбираем строку, возвращается новый экземпляр класса User со свойствами $ id и $ email, сопоставленными с записью.

Поскольку наш класс реализует Countable, мы должны создать открытый метод count()

 
	public function count() {
		return $this->rec->rowCount();
	}

Теперь нам требуется 5 открытых методов, которые реализуют функциональность Iterator. Первый смущенно назван rewind() Он вызывается при запуске цикла foreach и должен «перематывать» на первый элемент коллекции:

 
	public function rewind() {
		if ($this->cursor >= 0) $this->FetchLoggedIn();
		$this->next();
	}

Этот код проверяет, была ли возвращена одна или несколько записей. Если это так, мы снова запускаем SQL-запрос, поскольку создали набор записей только для пересылки.

Следующая строка вызывает next()

 
	public function next() {
		$this->cursor++;
		$this->item = $this->rec->fetch(PDO::FETCH_ORI_NEXT);
	}

Этот метод вызывается во время каждой итерации цикла foreach. Он увеличивает $this->cursor$this->item

Следующие два метода Iterator имеют имена current()key()

 
	public function current() {
		return $this->item;
	}


	public function key() {
		return $this->cursor;
	}

Наконец, нам нужен публичный метод valid() Это вызывается сразу после того, как цикл foreach выполняет rewind()next()

 
	public function valid() {
		return ($this->cursor < $this->count());
    }
// end of class definition
}

Наш класс завершен, и мы можем посчитать или перебрать вошедших в систему пользователей, используя код в верхней части этой статьи.

Я надеюсь, что вы нашли эту серию полезной и рассмотрите Итераторы при следующем создании объекта, который содержит коллекцию элементов.