Added in-memory hash of child objects

From: Brandon Turner <brandont@thinkwell.com>

Improves performance by not having to recursively search the admin tree each
time a child is added.  As an admin user, admin_category->locate can easily be
called over 3,000 times!  In my tests, this improved load times of an admin
user's page by about 7%.
---
 lib/adminlib.php |   18 +++++++++++++++++-
 1 files changed, 17 insertions(+), 1 deletions(-)

diff --git a/lib/adminlib.php b/lib/adminlib.php
index 76e1e10..93557f8 100644
--- a/lib/adminlib.php
+++ b/lib/adminlib.php
@@ -750,6 +750,8 @@ class admin_category implements parentable_part_of_admin_tree {
     public $path;
     /** @var mixed Either a string or an array or strings */
     public $visiblepath;
+    /** @var array A cached array of child objects, indexed by name */
+    public $childCache;
 
     /**
      * Constructor for an empty admin category
@@ -763,6 +765,7 @@ class admin_category implements parentable_part_of_admin_tree {
         $this->name        = $name;
         $this->visiblename = $visiblename;
         $this->hidden      = $hidden;
+        $this->childCache  = array();
     }
 
     /**
@@ -770,10 +773,11 @@ class admin_category implements parentable_part_of_admin_tree {
      *
      * @param string $name The internal name of the object we want.
      * @param bool $findpath initialize path and visiblepath arrays
+     * @param bool $disablecache Do not use child cache, force searching for $name
      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
      *                  defaults to false
      */
-    public function locate($name, $findpath=false) {
+    public function locate($name, $findpath=false, $disablecache=false) {
         if ($this->name == $name) {
             if ($findpath) {
                 $this->visiblepath[] = $this->visiblename;
@@ -782,6 +786,11 @@ class admin_category implements parentable_part_of_admin_tree {
             return $this;
         }
 
+        if(!$disablecache && !$findpath && isset($this->childCache[$name]))
+        {
+            return $this->childCache[$name];
+        }
+
         $return = NULL;
         foreach($this->children as $childid=>$unused) {
             if ($return = $this->children[$childid]->locate($name, $findpath)) {
@@ -794,6 +803,7 @@ class admin_category implements parentable_part_of_admin_tree {
             $return->path[]        = $this->name;
         }
 
+        $this->childCache[$name] = $return;
         return $return;
     }
 
@@ -828,6 +838,10 @@ class admin_category implements parentable_part_of_admin_tree {
             return false;  //can not remove itself
         }
 
+        // Remove from cache
+        // TODO: How to remove from parent caches?
+        unset($this->childCache[$name]);
+
         foreach($this->children as $precedence => $child) {
             if ($child->name == $name) {
             // found it!
@@ -860,6 +874,7 @@ class admin_category implements parentable_part_of_admin_tree {
                 debugging('error - parts of tree can be inserted only into parentable parts');
                 return false;
             }
+            $parent->childCache[$something->name] = $something;
             $parent->children[] = $something;
             return true;
 
@@ -958,6 +973,7 @@ class admin_root extends admin_category {
         $this->children = array();
         $this->fulltree = ($requirefulltree || $this->fulltree);
         $this->loaded   = false;
+        $this->childCache = array();
     }
 }
 
