Recursively Fetch Nested Ar Relations

Suppose you have a relation like the following, and you want to find all accounts that are under a given customer:

  • Customer 1

    [list]

  • Account 1

  • Customer 2

      [list]
    
  • Account 2

  • Account 3

      [*]Customer 3
    
  • Customer 4

      	[list]
    
  • Account 4

  • Account 5

      [/list]
    

    [/list]

[/list]

(For Customer 1, it’s accounts 1, 2, 3, 4, and 5; for Customer 3, it’s accounts 4 and 5; etc.)

How would you do that?

This has come up a few times in my projects and I’m curious how others have solved it.

Here’s the gist of my solution:




public function getNestedRelated($nested, $from, $nestedCriteria = array(), $fromCriteria = array(), $maxLevels = false, $includeFirst = true, $_currentLevel = 0, &$_recursedSoFar = array())

{

	// Always return an array (for array_merge)

	$related = array();


	// Prevent infinite recursion

	if (in_array($this->primaryKey, $_recursedSoFar)) {

		return $related;

	}

	$_recursedSoFar[] = $this->primaryKey;


	// Nested records at this level

	if ($_currentLevel > 0 || $includeFirst) {

		// Whether to refresh nested records at this level. If criteria are

		// provided, the db is queried anyway.

		$refreshNested = false;

		$related       = $this->getRelated($nested, $refreshNested, $nestedCriteria);

	}


	// Handle singular ("HAS_ONE", "BELONGS_TO") relations

	if (!is_array($related)) {

		$related = array($related);

	}


	// Don't recurse past the max # of levels

	if ($maxLevels !== false && $_currentLevel > $maxLevels) {

		return $related;

	}


	// Whether to refresh children of this record. If criteria are provided,

	// the db is queried anyway.

	$refreshFrom = false;


	// Go down one more level

	$_currentLevel++;

	foreach ($this->getRelated($from, $refreshFrom, $fromCriteria) as $child) {

		// Recursive step

		$nestedRelated = $child->getNestedRelated($nested, $from, $nestedCriteria, $fromCriteria, $maxLevels, $includeFirst, $_currentLevel, $_recursedSoFar);

		$related       = array_merge($related, $nestedRelated);

	}

	return $related;

}



Are you using SQL? If not, don’t bother reading further.

It would seem you have two relations, a tree of clients and client HAS_MANY accounts. It doesn’t matter if you use adjacency list or nested set to implement that tree. Just create a query that would fetch the client with all his children and then join the accounts.

I use adjacency lists with a closure table and I can define a ‘descendants’ relation in AR. I could add a ‘descendantsAccounts’ relation using ‘descendants’ in ‘through’. No recursive calls are needed.