Переход к PHP5 - Итерация по свойствам класса

ОГЛАВЛЕНИЕ

Итерация по свойствам класса

Все переменные класса, доступные в текущем контексте, могут быть перебранны циклом foreach. Такая итерация по свойствам класса может очень пригодится при клонировании объектов. Например, если необходимо создать клон объекта с большим количеством переменных класса, то можно сделать примерно так:

class Node {
private
$value;
private
$parent;
...
private
$type;

function
__clone() {
foreach (
$that as $propertyName => $propertyValue) {
$this->$propertyName = $propertyValue;
}
}

function
setParent($value) { $this->parent = $value; }
function
getParent() { return $this->parent; }
function
setValue($value) { $this->value = $value; }
function
getValue() { return $this->value; }
...
function
setType($value) { $this->type = $value; }
function
getType() { return $this->type; }
}

$myNode = new Node();
$myNode->setValue(10);
$myNextNode = $myNode->__clone();
print
$myNextNode->getValue();

Простой цикл очень хорошо заменяет большое количество присваиваний и избавляет от необходимости синхронизировать присваивания при клонировании со списком всех переменных класса. Очевидным образом, эта итерация не может быть применена к свойствам класса, реализованных через __get()/__set() функции.

Изменение стандартной итерации по свойствам

Стандартными элементами языка, позволящими итерацию, являются массивы. Но их недостатки очевидны - можно по ошибке поместить в массив элемент другого типа. К массиву не добавишь методы проверки содержимого и т.п.

Для введения дополнительных сущностей (классов), позволяющих итерацию по своим элементам, предусмотренно 2 интерфейса: IteratorAggregate и Iterator.

interface IteratorAggregate {
function
getIterator(); // возвращает массив или объект
}

IteratorAggregate может использоваться, когда данные для итерации можно предоставить в одной из стандарных конструкций PHP, позволяющих итерацию: массива или объекта, реализующего Iterator.

Пример использования IteratorAggregate с итерацией по элементам массива:

/**
Замечание:
Этот пример в PHP5 beta 3 не работает.
Тем не менее, в документации заявленно, что getIterator()
может возвращать массив или объект, реализующий Iterator.
Так что к release, надеюсь, исправят.
*/
class Control implements IteratorAggregate {
private
$controls;
private
$name;

function
__construct() {
$this->controls = array();
}

function
addControl($obj) {
$this->controls[$obj->getName()] = $obj;
}

function
setName($value) { $this->name = $value; }
function
getName() { return $this->name; }

// эта функция из IteratorAggregate
function getIterator() {
return
$this->controls;
}
}

$c1 = new Control();
$c1->setName('userId');
$c2 = new Control();
$c2->setName('userName');
$form = new Control();
$form->addControl($c1);
$form->addControl($c2);

foreach (
$form as $ctrl) {
echo
$ctrl->getName() . '
'
;
}

Этот вариант является только надстройкой над стандартной функциональностью PHP и может использоваться когда до начала итерации можно получить все элементы, участвующие в итрации. Если же этого сделать нельзя (в случае получения записей из базы данных или чтении строк из большого файла), используется другой механизм расширения итерации - реализация интерфейса Iterator.

interface Iterator {
function
rewind(); // переводит итерацию к первому элементу
function next(); // подготавливает к выводу следующий элемент
function key(); // возвращает ключ текущего элемента
function current(); // возвращает текущий элемент
function hasMore(); // возвращает true, если есть еще элементы, иначе false
}

Следующий пример показывает итерацию до нахождения нужного элемента:

class Control {
private
$name;

function
setName($value) { $this->name = $value; }
function
getName() { return $this->name; }
}

class
Controls implements Iterator {
private
$controls;
private
$controlNames;
private
$num;

function
__construct() {
$this->controls = array();
}

function
addControl($obj) {
$this->controls[$obj->getName()] = $obj;
}

// функция использует возможности итерации класса для
// поиска контрола с заданным именем
function findControl($name) {
foreach (
$this as $control) {
if (
$control->getName() == $name)
return
$control;
}

return
null;
}

// следующие функции из Iterator
function rewind() {
$this->controlNames = array_keys($controls);
$this->num = 0;
}

function
next() {
$this->num++;
}

function
key() {
return
$this->controlNames($this->num);
}

function
current() {
return
$this->controls[$this->key()];
}

function
hasMore() {
return
$this->num < count($this->controlNames);
}

}

$c1 = new Control();
$c1->setName('userId');
$c2 = new Control();
$c2->setName('userName');
$formControls = new Controls();
$formControls->addControl($c1);
$formControls->addControl($c2);

$userId = $formControls->findControl('userId');
$userName = $formControls->findControl('userName');