Wednesday, September 10, 2008

CakePHP Model::findAllThreaded Frustrations

Been working on a shopping cart system at work, and we were having some weird issues with the category hierarchy manager we had built. For some reason (randomly it seemed at first) we would add a category and then shuffle its parent category and suddenly it would disappear from the threaded list. After about an hour of trying to debug the code for findAllThreaded (show below) I finally found the culprit in our case.


function __doThread($data, $root, $index = 0) {
$out = array();
$sizeOf = sizeof($data);

for ($ii = $index; $ii < $sizeOf; $ii++) {
if (($data[$ii][$this->alias]['parent_id'] == $root) || (($root === null) && ($data[$ii][$this->alias]['parent_id'] == '0'))) {
$tmp = $data[$ii];

if (isset($data[$ii][$this->alias][$this->primaryKey])) {
$tmp['children'] = $this->__doThread($data, $data[$ii][$this->alias][$this->primaryKey], 0);//$ii);
} else {
$tmp['children'] = null;
}

$out[] = $tmp;
}
}

return $out;
}


Basically, the way the function is structured if a child comes before a parent in the findAll results that findAllThreaded uses then it won't find it because it only looks after the parent for any children results, which forces you to do one of two things:

1. In our case, I edited the line $tmp['children'] = $this->__doThread(... to pass '0' for the $index parameter instead of $ii. This forces the function to go through all results each time. In our case we are only using findAllThreaded in the admin area of our cart, so we felt any hit in performance wasn't too serious. In your case, it might be.

2. Ensure the order you are using for the findAll results pulls results that are already structured somewhat hierarchal. You could have an order field that you sort by, but it needs to be somewhat static so that it can't be easily changed and mess up your list again (hence using something like the built in modified date field in CakePHP could cause some issues).

Hope this helps you bakers out there. If you have a better idea on how to ensure findAllThreaded works properly, drop a line in the comments.

EDIT: Actually, you can simplify all this by just ordering you results by the parent_id. So far that seems to work great without having to edit any of the cake core code. Plus it won't eat up as many resources as having to loop through all the results every time.