Sorting objects hierarchically in recursive multidimensional arrays in PHP

When retrieving a group of objects from a database, sometimes we need to recreate some hierarchy inside a multidimensional array or object for further processing. However, since the language wasn’t developed with such usage scenarios in mind, the way to achieve this is anything but intuitive. Many developers just adjust their DB structure in order to make such sorting easier, but it’s always good to have a choice. I recently came across this problem myself, and think the solution I came up with is not only interesting, but could be useful to other developers out there. If you find this useful, or would like to propose a different solution, I’d love to read your comments below.

The Problem

Let’s say, as an example, that you have a DB table with columns id and parent, where id is an auto-generated primary key, and parent refers to id by a foreign key. The idea is every entry has either a parent in the same table, or a NULL parent and hence is at the top level of the hierarchy. We do not know how deep the hierarchy goes, and must be able to recursively populate a multidimensional array or object in order to recreate it.

The (Generic) Solution

First of all, we assume we are retrieving all entries into a plain array. We ORDER BY parent ASC, with NULLS first so we process the top level entries first. We will need to create 2 arrays, one for storing the entries hierarchically, and the other to keep track of the path of each entry inside the hierarchy. We will be dealing with arrays exclusively in this example, but using objects instead is pretty straightforward.

$entries = yourGetEntriesFromDbFunction();  //The array with our retrieved DB entries
$entryRecords = array();                    //Our hierarchy will be created in this array
//We will track the path of each entry here, with an array
//for each entry containing the ids of all its ancestors:
$entryPaths = array();
foreach ($entries as $entry) {
    //Create a new key inside each entry to store its children:
    $entry['children'] = array();
    $entryPaths[$entry['id']] = array();    //Make a space for the path of the entry
    if ($entry['parent'] === NULL) {        //If top level, just insert into the hierarchy array
        $entryRecords[$entry['id']] = $entry;
    } else {
    	$pathRef =& $entryRecords;          //Save a reference to the top level of our hierarchy
        //Traverse our current hierarchy based on the path of the entry's parent by reassigning a
        //single variable by reference to the children container of each level in the hierarchy:
        foreach ($entryPaths[$entry['parent']] as $path) {
    		$pathRef =& $pathRef[$path]['children'];
    	}
        //Once we reach the entry's parent's children container, just add the entry to it:
        $pathRef[] = $entry;
        //Register the entry's path by first copying parent's path
        $entryPaths[$entry['id']] = $entryPaths[$entry['parent']];
    }
    $entryPaths[$entry['id']][] = $entry['id']; //Finally add the id of the entry to its path
}

As you can see, the main idea is to save the path of each entry we insert in order to know how to get to it, and then traverse the path of the parent by reassigning references to the children container of each post in the path. This way we can always reach the parent of a post inside our hierarchy, no matter how complicated, provided that all parents are inserted before their children into it.

Speak Yii to me

Actually, I came up with this algorithm while working with Active Records on Yii Framework, so might as well share the code that inspired this post. I have a Section Model, with id, parent, and order columns. id and parent are as in the above example, but the order column provides the order in which the children in a post should be sorted. I added a public property named “children” to the Model in order to be able to store a record’s children in it, but that’s all. Since it’s the same principle as above, it’s mostly uncommented:

$sections = Section::model()->findAll(
    array('order'=>'parent ASC NULLS FIRST,"order"'));  //PostgreSQL FTW
$entryRecords = array();
$entryPaths = array();
foreach ($sections as $section) {
	$section->children = array();
    $entryPaths[$section->id] = array();
    if ($section->parent === NULL) {
        $entryRecords[$section->order] = $section;
    } else {
    	$pathRef =& $entryRecords;
        foreach ($entryPaths[$section->parent] as $path) {
    		$pathRef =& $pathRef[$path]->children;
    	}
        $pathRef[] = $section;
        $entryPaths[$section->id] = $entryPaths[$section->parent]; 
    }
    $entryPaths[$section->id][] = $section->order;
}

Hope you find some of this as useful as I did, and if you find this solution pretty obvious or even intuitive, congratulations: you’re a better web developer than I am.

Retrofitting jQuery prop() for when using both older and newer jQuery versions

When upgrading your existing website to jQuery 1.6 or higher, you may have noticed that the attr() method stopped working like it has since time immemorial, breaking all previous code that used it. Supposedly jQuery 1.61 added some backwards compatibility fixes, but they don’t really work, at least for me. They say “just replace all attr() calls for prop() and be done”, however it’s not that simple if your code is supposed to work on different versions of jQuery, for example when developing extensions or modules for constantly updated frameworks, since prop() didn’t exist before jQuery 1.6. Actually, there is a simple way to make your code work on both older and newer jQuery versions. All you need to do is replace all attr() calls for prop() (it takes the same arguments so don’t worry), and add the following before your code:

       if (typeof jQuery.fn.prop != 'function') {
            jQuery.fn.prop = jQuery.fn.attr;
        }
    

All this does is check if the prop() method exists, and if not, it creates it by cloning attr(). Hope this saves you some time.