in_array для объектов с круговыми ссылками - PullRequest
12 голосов
/ 11 декабря 2011

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

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, это просто сравнить ссылки на объект, которые он хранит, с ссылкой на объект поля.Это предотвратит попытки пройти по всему дереву объектов и столкнуться с проблемой рекурсии.

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

Ответы [ 2 ]

19 голосов
/ 11 декабря 2011

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

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

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

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

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

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

1 голос
/ 11 декабря 2011

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

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

...