PHP Puking on recursive object :(

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

PHP Puking on recursive object :(

Post by Chris Corbyn »

I'm working with a tree structure of objects which are all a subclass of the same base class.

Because it's a tree, it has nodes. I need to track the locations of certain nodes so I group information about them (their ID's) into respective containers. I also keep a reference to the node they are stored in here. It's tricky to explain so let's just look at the code eh? :) Simplified....

(I think it's the composite pattern, but recursive?)

Code: Select all

<?php

/**
 * Each type of node must extend Node
 */
abstract class Node
{
	// ... SNIP ....

	protected $children = array();
    
    	/**
	 * Nest a child node in this document
	 * @param Node
	 * @param string The identifier to use, optional
	 * @param int Add the node before (-1) or after (+1) the other nodes
	 * @return string The identifier for this node
	 */
	public function addChild(Node $node, $id=null, $after=1)
	{
		if (empty($id))
		{
			do
			{
				$id = uniqid();
			} while (array_key_exists($id, $this->children));
		}
		$id = (string) $id;
		if ($after == -1) $this->children = array_merge(array($id => $node), $this->children);
		else $this->children[$id] = $node;
		
		return $id;
	}
	/**
	 * Check if a child exists identified by $id
	 * @param string Identifier to look for
	 * @return boolean
	 */
	public function hasChild($id)
	{
		return array_key_exists($id, $this->children);
	}
	/**
	 * Get a child node, identified by $id
	 * @param string The identifier for this child
	 * @return Node The child node
	 * @throws NodeException If no such child exists
	 */
	public function getChild($id)
	{
		if ($this->hasChild($id))
		{
			return $this->children[$id];
		}
		else throw new NodeException(
			"Cannot retrieve child node identified by '" . $id . "' as it does not exist.  Consider using hasChild() to check.");
	}
	/**
	 * Remove a node from the document
	 * @param string The identifier of the child
	 * @throws NodeException If no such part exists
	 */
	public function removeChild($id)
	{
		$id = (string) $id;
		if (!$this->hasChild($id)) throw new NodeException(
			"Cannot remove child part identified by '" . $id . "' as it does not exist. Consider using hasChild() to check.");
		else
		{
			$this->children[$id] = null;
			unset($this->children[$id]);
		}
	}

	// .... SNIP ....
}

Code: Select all

<?php

class FooNode extends Node
{
	//some specific stuff here
}

class BarNode extends Node
{
	//more specifics
}

Code: Select all

<?php

class NodeTree extends Node
{
	protected $fooNodes = array();
	protected $barNodes = array();
	
	public function __construct()
	{
		$this->fooNodes["___location"] =& $this; //Where Nodes of Type FooNode are found
		$this->barNodes["___location"] =& $this; //Where nodes of type BarNode are found
	}
	
	public function attachBranch(Node $node)
	{
		//Add nodes to the relevant branch
		switch (get_class($node))
		{
			case "FooNode":
				$id = $this->fooNodes["___location"]->addChild($node);
				$this->fooNodes[$id] = $id;
				break;
			case "BarNode": default:
				$id = $this->barNodes["___location"]->addChild($node);
				$this->barNodes[$id] = $id;
				break;
		}
	}
}
At one point in NodeTree I need to switch the reference for $this->fooNodes["___location"] *if it's currently $this*. The problem is that because I have a reference to $this inside the class itself it won't allow me to make that comparison and throws an error that the recursion is too deep. Anybody got any better ideas?

Basically, PHP is choking on this situation:

Code: Select all

<?php

class MyClass
{
    protected $ref = null;
    
    public function __construct()
    {
        $this->setRef($this);
    }

    public function setRef($obj)
    {
        $this->ref =& $this;
    }

    public function refIsMine()
    {
        return ($this->ref == $this);
    }
}

$obj = new MyClass();
//Error that recursion is too deep
if ($obj->refIsMine()) echo "Reference not yet changed";
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

From what I remember, $ref1 === $ref2 will do a simple check if the objects are the same instance.

Wouldn't $this->fooNodes['__location'] always be $this though? Unless you've removed bits of code that interacted with the element, there's nothing you have that would alter it. Worst case, you could use instanceof against NodeTree.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

feyd wrote:From what I remember, $ref1 === $ref2 will do a simple check if the objects are the same instance.

Wouldn't $this->fooNodes['__location'] always be $this though? Unless you've removed bits of code that interacted with the element, there's nothing you have that would alter it. Worst case, you could use instanceof against NodeTree.
Yeah I couldn't be bothered to finish my example :lol: it was just going to get too complicated that people would stop reading. But basically I *do* change where the reference points to at runtime since under certain situations I need to add branches and move existing nodes onto a new branch etc. It's all very clever but fairly complex.

I totally never thought of using ===, I'll give it a go thanks :)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Indeed the identical operator does the trick :)
Post Reply