MDL-64284 core: improved performance of component class searching
authorTom Dickman <tomdickman@catalyst-au.net>
Tue, 26 Feb 2019 05:34:28 +0000 (16:34 +1100)
committerTom Dickman <tomdickman@catalyst-au.net>
Fri, 29 Mar 2019 00:44:43 +0000 (11:44 +1100)
Added static caching of classes to reduce load times and reduce calls to `get_component_classes`
by altering to accept a null component value to search classmap only once.

analytics/classes/manager.php
lib/classes/component.php
lib/tests/component_test.php
search/classes/manager.php

index 8fcf6e5..8b506ee 100644 (file)
@@ -650,22 +650,7 @@ class manager {
         // Just in case...
         $element = clean_param($element, PARAM_ALPHANUMEXT);
 
-        // Core analytics classes (analytics subsystem should not contain uses of the analytics API).
-        $classes = \core_component::get_component_classes_in_namespace('core', 'analytics\\' . $element);
-
-        // Plugins.
-        foreach (\core_component::get_plugin_types() as $type => $unusedplugintypepath) {
-            foreach (\core_component::get_plugin_list($type) as $pluginname => $unusedpluginpath) {
-                $frankenstyle = $type . '_' . $pluginname;
-                $classes += \core_component::get_component_classes_in_namespace($frankenstyle, 'analytics\\' . $element);
-            }
-        }
-
-        // Core subsystems.
-        foreach (\core_component::get_core_subsystems() as $subsystemname => $unusedsubsystempath) {
-            $componentname = 'core_' . $subsystemname;
-            $classes += \core_component::get_component_classes_in_namespace($componentname, 'analytics\\' . $element);
-        }
+        $classes = \core_component::get_component_classes_in_namespace(null, 'analytics\\' . $element);
 
         return $classes;
     }
index 8cd7889..6250f3a 100644 (file)
@@ -923,32 +923,38 @@ $cache = '.var_export($cache, true).';
      *
      * e.g. get_component_classes_in_namespace('mod_forum', 'event')
      *
-     * @param string $component A valid moodle component (frankenstyle)
-     * @param string $namespace Namespace from the component name or empty if all $component namespace classes.
-     * @return array The full class name as key and the class path as value.
+     * @param string|null $component A valid moodle component (frankenstyle) or null if searching all components
+     * @param string $namespace Namespace from the component name or empty string if all $component classes.
+     * @return array The full class name as key and the class path as value, empty array if $component is `null`
+     * and $namespace is empty.
      */
-    public static function get_component_classes_in_namespace($component, $namespace = '') {
+    public static function get_component_classes_in_namespace($component = null, $namespace = '') {
 
-        $component = self::normalize_componentname($component);
+        $classes = array();
 
-        if ($namespace) {
+        // Only look for components if a component name is set or a namespace is set.
+        if (isset($component) || !empty($namespace)) {
 
-            // We will add them later.
-            $namespace = trim($namespace, '\\');
+            // If a component parameter value is set we only want to look in that component.
+            // Otherwise we want to check all components.
+            $component = (isset($component)) ? self::normalize_componentname($component) : '\w+';
+            if ($namespace) {
 
-            // We need add double backslashes as it is how classes are stored into self::$classmap.
-            $namespace = implode('\\\\', explode('\\', $namespace));
-            $namespace = $namespace . '\\\\';
-        }
+                // We will add them later.
+                $namespace = trim($namespace, '\\');
 
-        $regex = '|^' . $component . '\\\\' . $namespace . '|';
-        $it = new RegexIterator(new ArrayIterator(self::$classmap), $regex, RegexIterator::GET_MATCH, RegexIterator::USE_KEY);
+                // We need add double backslashes as it is how classes are stored into self::$classmap.
+                $namespace = implode('\\\\', explode('\\', $namespace));
+                $namespace = $namespace . '\\\\';
+            }
+            $regex = '|^' . $component . '\\\\' . $namespace . '|';
+            $it = new RegexIterator(new ArrayIterator(self::$classmap), $regex, RegexIterator::GET_MATCH, RegexIterator::USE_KEY);
 
-        // We want to be sure that they exist.
-        $classes = array();
-        foreach ($it as $classname => $classpath) {
-            if (class_exists($classname)) {
-                $classes[$classname] = $classpath;
+            // We want to be sure that they exist.
+            foreach ($it as $classname => $classpath) {
+                if (class_exists($classname)) {
+                    $classes[$classname] = $classpath;
+                }
             }
         }
 
index b0b995a..da3d8a8 100644 (file)
@@ -473,7 +473,7 @@ class core_component_testcase extends advanced_testcase {
         $this->assertEquals(array(), array_keys($list));
     }
 
-    public function test_get_component_classes_int_namespace() {
+    public function test_get_component_classes_in_namespace() {
 
         // Unexisting.
         $this->assertCount(0, core_component::get_component_classes_in_namespace('core_unexistingcomponent', 'something'));
@@ -493,7 +493,7 @@ class core_component_testcase extends advanced_testcase {
         $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', 'task'));
         $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', '\\task'));
 
-        // Core as a component works, the funcion can normalise the component name.
+        // Core as a component works, the function can normalise the component name.
         $this->assertCount(7, core_component::get_component_classes_in_namespace('core', 'update'));
         $this->assertCount(7, core_component::get_component_classes_in_namespace('', 'update'));
         $this->assertCount(7, core_component::get_component_classes_in_namespace('moodle', 'update'));
@@ -507,6 +507,17 @@ class core_component_testcase extends advanced_testcase {
         // Without namespace it returns classes/ classes.
         $this->assertCount(3, core_component::get_component_classes_in_namespace('tool_mobile', ''));
         $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes'));
+
+        // When no component is specified, classes are returned for the namespace in all components.
+        // (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed).
+        $this->assertGreaterThan(
+            count(\core_component::get_component_classes_in_namespace('core', 'output')),
+            count(\core_component::get_component_classes_in_namespace(null, 'output')));
+
+        // Without either a component or namespace it returns an empty array.
+        $this->assertEmpty(\core_component::get_component_classes_in_namespace());
+        $this->assertEmpty(\core_component::get_component_classes_in_namespace(null));
+        $this->assertEmpty(\core_component::get_component_classes_in_namespace(null, ''));
     }
 
     /**
index 2abe63d..b8d1735 100644 (file)
@@ -336,50 +336,20 @@ class manager {
 
         static::$allsearchareas = array();
         static::$enabledsearchareas = array();
+        $searchclasses = \core_component::get_component_classes_in_namespace(null, 'search');
 
-        $plugintypes = \core_component::get_plugin_types();
-        foreach ($plugintypes as $plugintype => $unused) {
-            $plugins = \core_component::get_plugin_list($plugintype);
-            foreach ($plugins as $pluginname => $pluginfullpath) {
-
-                $componentname = $plugintype . '_' . $pluginname;
-                $searchclasses = \core_component::get_component_classes_in_namespace($componentname, 'search');
-                foreach ($searchclasses as $classname => $classpath) {
-                    $areaname = substr(strrchr($classname, '\\'), 1);
-
-                    if (!static::is_search_area($classname)) {
-                        continue;
-                    }
-
-                    $areaid = static::generate_areaid($componentname, $areaname);
-                    $searchclass = new $classname();
-
-                    static::$allsearchareas[$areaid] = $searchclass;
-                    if ($searchclass->is_enabled()) {
-                        static::$enabledsearchareas[$areaid] = $searchclass;
-                    }
-                }
+        foreach ($searchclasses as $classname => $classpath) {
+            $areaname = substr(strrchr($classname, '\\'), 1);
+            $componentname = strstr($classname, '\\', 1);
+            if (!static::is_search_area($classname)) {
+                continue;
             }
-        }
-
-        $subsystems = \core_component::get_core_subsystems();
-        foreach ($subsystems as $subsystemname => $subsystempath) {
-            $componentname = 'core_' . $subsystemname;
-            $searchclasses = \core_component::get_component_classes_in_namespace($componentname, 'search');
 
-            foreach ($searchclasses as $classname => $classpath) {
-                $areaname = substr(strrchr($classname, '\\'), 1);
-
-                if (!static::is_search_area($classname)) {
-                    continue;
-                }
-
-                $areaid = static::generate_areaid($componentname, $areaname);
-                $searchclass = new $classname();
-                static::$allsearchareas[$areaid] = $searchclass;
-                if ($searchclass->is_enabled()) {
-                    static::$enabledsearchareas[$areaid] = $searchclass;
-                }
+            $areaid = static::generate_areaid($componentname, $areaname);
+            $searchclass = new $classname();
+            static::$allsearchareas[$areaid] = $searchclass;
+            if ($searchclass->is_enabled()) {
+                static::$enabledsearchareas[$areaid] = $searchclass;
             }
         }