in_array для объектов с циклическими ссылками

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

public function addField ($name, iface\Node $field)
{
    // Prevent the same field being added multiple times
    if (!in_array ($field, $this -> fields))
    {
        $this -> fields [$name] = $field;
        $field -> setParent ($this);
    }
    else
    {
        throw new \InvalidArgumentException ('This field cannot be added to this group');
    }
    return ($this);
}

Это начало приводить к проблемам, когда я начал реализовывать объекты, реализующие интерфейс Node, поскольку они могут включать циклические ссылки (они содержат коллекцию своих дочерних узлов, причем каждый дочерний узел содержит ссылку на своего родителя). Попытка добавить поле может привести к возникновению следующей ошибки:

Неустранимая ошибка PHP: слишком глубокий уровень вложенности - рекурсивная зависимость?

Я подозреваю, что PHP пытается обойти весь массив объектов, а не просто сравнить ссылки на объекты, чтобы увидеть, содержат ли они одно и то же значение и, следовательно, указывают на один и тот же объект.

Мне нужно, чтобы in_array просто сравнил хранящиеся в нем ссылки на объекты со ссылкой на объект поля. Это предотвратило бы попытку обойти все дерево объектов и столкнуться с проблемой рекурсии.

Есть ли способ сделать это?


person GordonM    schedule 10.12.2011    source источник
comment
Попробуйте переопределить __equals для вашего объекта, чтобы реализовать метод проверки на равенство, более подходящий для ваших целей.   -  person Michael Mior    schedule 11.12.2011


Ответы (2)


Оказывается, ответ необычайно прост. Похоже, что по умолчанию in_array выполняет нестрогое сравнение (эквивалентное операции ==) при проверке стога сена на наличие иглы. Это означает, что он проверяет, равны ли все свойства, что означает, что он начинает обход графа объектов, и это может привести к проблемам, если у вас есть циклические ссылки в этом графе.

Однако функция in_array имеет строгий режим, который, насколько я могу судить, эквивалентен операции ===. Кажется, это заставляет его проверять ссылки, чтобы увидеть, указывают ли они на один и тот же объект, вместо того, чтобы сравнивать все свойства.

Просто изменив код на:

if (!in_array ($field, $this -> fields, true))

заставляет метод вести себя так, как я хотел, чтобы он не вызывал ошибку рекурсии.

Должен сказать, что я немного удивлен тем, что PHP не использует этот режим по умолчанию. С другой стороны, я думаю, мне действительно не следует удивляться тому, что слабая типизация PHP снова вызвала у меня проблему. :)

person GordonM    schedule 10.12.2011

Я бы просто использовал либо SplObjectStorage, либо spl_object_hash.

И вы правы, когда php сравнивает вещи, он рекурсивно обходит структуры (массивы тоже).

person goat    schedule 10.12.2011
comment
@mifki Вы можете сделать строгое === сравнение. Он будет сравнивать точки для объектов. - person NikiC; 11.12.2011
comment
Спасибо за ответ, но я думаю, что нашел решение. - person GordonM; 11.12.2011