From 0d6a45d2febf98b9e4c65b6428e5d012c112faab Mon Sep 17 00:00:00 2001 From: Tom Dickman Date: Tue, 26 Feb 2019 16:34:28 +1100 Subject: [PATCH] MDL-64284 core: improved performance of component class searching 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 | 17 +----------- lib/classes/component.php | 52 +++++++++++++++++++---------------- lib/tests/component_test.php | 15 ++++++++-- search/classes/manager.php | 52 ++++++++--------------------------- 4 files changed, 54 insertions(+), 82 deletions(-) diff --git a/analytics/classes/manager.php b/analytics/classes/manager.php index 8fcf6e58b1f..8b506eeba8a 100644 --- a/analytics/classes/manager.php +++ b/analytics/classes/manager.php @@ -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; } diff --git a/lib/classes/component.php b/lib/classes/component.php index 8cd7889c556..6250f3a18fd 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -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); - - if ($namespace) { - - // We will add them later. - $namespace = trim($namespace, '\\'); - - // 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; + + // Only look for components if a component name is set or a namespace is set. + if (isset($component) || !empty($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 will add them later. + $namespace = trim($namespace, '\\'); + + // 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. + foreach ($it as $classname => $classpath) { + if (class_exists($classname)) { + $classes[$classname] = $classpath; + } } } diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index b0b995a6b61..da3d8a806f2 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -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, '')); } /** diff --git a/search/classes/manager.php b/search/classes/manager.php index 2abe63de5d4..b8d17359766 100644 --- a/search/classes/manager.php +++ b/search/classes/manager.php @@ -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; } }