Monday 25 July 2016

php - in_array on objects with circular references



I'm building an array of objects. I need this array to only contain once instance of a given object, having multiple references to the same object should throw an exception. I'm using the following code to achieve this:



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);
}



This started leading to problems when I started implementing the objects that implement the Node interface, as they can include circular references (they hold a collection of their child nodes, with each child holding a reference to its parent). Trying to add a field can result in the following error being generated:




PHP Fatal error: Nesting level too deep - recursive dependency?




I suspect that PHP is trying to traverse the entire object array, rather than just comparing the object references to see if they hold the same value and therefore point to the same object.



What I need in_array to do is just compare the object references it stores with the object reference of field. This would prevent it trying to traverse the whole object tree and running into the recursion problem.




Is there a way of doing this?


Answer



Turns out the answer is extraordinarally simple. It would appear that by default in_array does non-strict comparison (equivalent to an == operation) when testing the haystack for the needle. This means that it checks that all the properties are equal, which means it starts traversing the object graph, and that can get you into trouble if you have circular references in that graph.



The in_array function has a strict mode, however, which as far as I can tell does the equivalent of an === operation. This seems to cause it to check the references to see if they point at the same object instead of comparing all the properties.



Simply changing the code to:



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



makes the method behave as I wanted it to behave without it triggering the recursion error.



I have to say that I'm a little surprised that PHP doesn't do this mode by default. On the other hand I guess I really ought not to be surprised that PHP's weak typing has caused me an issue again. :)


No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...