From 0a42d8f1988f1f1affc62e0c73e62064cab7b585 Mon Sep 17 00:00:00 2001 From: frytimo Date: Sat, 15 Mar 2025 12:13:36 -0300 Subject: [PATCH] Add caching to the auto_loader for interfaces (#7320) * update auto loader to load and cache interfaces * use new method to update the auto loader cache * use new interface * use new interface to trigger a cache flush * update the console upgrade menu * update the missing implements syntax * add the clear_cache to the flush cache button --- app/sip_status/cmd.php | 4 + core/upgrade/app_languages.php | 27 ++++ core/upgrade/upgrade.php | 15 +- core/upgrade/upgrade_menu.php | 19 +-- resources/classes/auto_loader.php | 211 ++++++++++++++++++++------- resources/classes/settings.php | 34 +---- resources/interfaces/clear_cache.php | 39 +++++ 7 files changed, 251 insertions(+), 98 deletions(-) create mode 100644 resources/interfaces/clear_cache.php diff --git a/app/sip_status/cmd.php b/app/sip_status/cmd.php index 071c9bd55b..601ad49e73 100644 --- a/app/sip_status/cmd.php +++ b/app/sip_status/cmd.php @@ -101,6 +101,10 @@ case "cache-flush": $cache = new cache; $response = $cache->flush(); + //trigger clear cache for any classes that require it + foreach ($autoload->get_interface_list('clear_cache') as $class) { + $class::clear_cache(); + } message::add($response, 'alert'); break; case "reloadxml": diff --git a/core/upgrade/app_languages.php b/core/upgrade/app_languages.php index cc45a5e5ff..3fcf70d89d 100644 --- a/core/upgrade/app_languages.php +++ b/core/upgrade/app_languages.php @@ -621,6 +621,33 @@ $text['label-update_filesystem_permissions']['zh-cn'] = ""; $text['label-update_filesystem_permissions']['ja-jp'] = ""; $text['label-update_filesystem_permissions']['ko-kr'] = ""; +$text['message-updated_autoloader']['en-us'] = 'Updated autoloader'; +$text['message-updated_autoloader']['en-gb'] = 'Updated autoloader'; +$text['message-updated_autoloader']['ar-eg'] = 'تم تحديث أداة التحميل التلقائي'; +$text['message-updated_autoloader']['de-at'] = 'Aktualisierter Autoloader'; +$text['message-updated_autoloader']['de-ch'] = 'Aktualisierter Autoloader'; +$text['message-updated_autoloader']['de-de'] = 'Aktualisierter Autoloader'; +$text['message-updated_autoloader']['el-gr'] = 'Ενημερώθηκε η αυτόματη φόρτωση'; +$text['message-updated_autoloader']['es-cl'] = 'Cargador automático actualizado'; +$text['message-updated_autoloader']['es-mx'] = 'Cargador automático actualizado'; +$text['message-updated_autoloader']['fr-ca'] = 'Chargeur automatique mis à jour'; +$text['message-updated_autoloader']['fr-fr'] = 'Chargeur automatique mis à jour'; +$text['message-updated_autoloader']['he-il'] = 'טוען אוטומטי מעודכן'; +$text['message-updated_autoloader']['it-it'] = 'Caricatore automatico aggiornato'; +$text['message-updated_autoloader']['ka-ge'] = 'განახლებულია ავტოჩამტვირთავი'; +$text['message-updated_autoloader']['nl-nl'] = 'Bijgewerkte autoloader'; +$text['message-updated_autoloader']['pl-pl'] = 'Zaktualizowany autoloader'; +$text['message-updated_autoloader']['pt-br'] = 'Carregador automático atualizado'; +$text['message-updated_autoloader']['pt-pt'] = 'Carregador automático atualizado'; +$text['message-updated_autoloader']['ro-ro'] = 'Încărcare automată actualizată'; +$text['message-updated_autoloader']['ru-ru'] = 'Обновленный автозагрузчик'; +$text['message-updated_autoloader']['sv-se'] = 'Uppdaterad autoloader'; +$text['message-updated_autoloader']['uk-ua'] = 'Оновлений автозавантажувач'; +$text['message-updated_autoloader']['tr-tr'] = 'Güncellenmiş otomatik yükleyici'; +$text['message-updated_autoloader']['zh-cn'] = '更新的自动加载器'; +$text['message-updated_autoloader']['ja-jp'] = 'オートローダーの更新'; +$text['message-updated_autoloader']['ko-kr'] = '업데이트된 자동 로더'; + $text['label-upgrade_menu']['en-us'] = "Menu Defaults"; $text['label-upgrade_menu']['en-gb'] = "Menu Defaults"; $text['label-upgrade_menu']['ar-eg'] = "افتراضيات القائمة"; diff --git a/core/upgrade/upgrade.php b/core/upgrade/upgrade.php index 0d999aea99..6869ef7f8f 100644 --- a/core/upgrade/upgrade.php +++ b/core/upgrade/upgrade.php @@ -141,9 +141,6 @@ fclose($file_handle); } -//include files - require dirname(__DIR__, 2) . "/resources/require.php"; - //check the permission if(defined('STDIN')) { $display_type = 'text'; //html, text @@ -177,11 +174,13 @@ exit(); } -//always update the auto_loader cache just-in-case the source files have updated - $auto_loader = new auto_loader(); - $auto_loader->reload_classes(); - $auto_loader->update_cache(); - $auto_loader->clear_cache(); +//always update the now global autoload cache just-in-case the source files have updated + $autoload->update(); + +//trigger clear cache for any classes that require it + foreach ($autoload->get_interface_list('clear_cache') as $class) { + $class::clear_cache(); + } //get the version of the software if ($upgrade_type == 'version') { diff --git a/core/upgrade/upgrade_menu.php b/core/upgrade/upgrade_menu.php index f44373c479..b00b534064 100644 --- a/core/upgrade/upgrade_menu.php +++ b/core/upgrade/upgrade_menu.php @@ -147,18 +147,13 @@ function show_upgrade_menu() { * @global type $text */ function do_upgrade_auto_loader() { - global $text; - //remove temp file - unlink(sys_get_temp_dir() . '/' . auto_loader::FILE); + global $text, $autoload; + //remove temp files + unlink(sys_get_temp_dir() . '/' . auto_loader::CLASSES_FILE); + unlink(sys_get_temp_dir() . '/' . auto_loader::INTERFACES_FILE); //create a new instance of the autoloader - $loader = new auto_loader(); - //reload the classes - $loader->reload_classes(); - echo "{$text['label-reloaded_classes']}\n"; - //re-create cache file - if ($loader->update_cache()) { - echo "{$text['label-updated_cache']}\n"; - } + $autoload->update(); + echo "{$text['message-updated_autoloader']}\n"; } /** @@ -195,7 +190,7 @@ function do_filesystem_permissions($text, settings $settings) { $directories[] = $log_directory . '/xml_cdr'; } //update the auto_loader cache permissions file - $directories[] = sys_get_temp_dir() . '/' . auto_loader::FILE; + $directories[] = sys_get_temp_dir() . '/' . auto_loader::CLASSES_FILE; //execute chown command for each directory foreach ($directories as $dir) { if ($dir !== null) { diff --git a/resources/classes/auto_loader.php b/resources/classes/auto_loader.php index 7befa402e8..442b0db85a 100644 --- a/resources/classes/auto_loader.php +++ b/resources/classes/auto_loader.php @@ -34,8 +34,10 @@ */ class auto_loader { - const FILE = 'autoloader_cache.php'; - const CACHE_KEY = 'autoloader_classes'; + const CLASSES_KEY = 'autoloader_classes'; + const CLASSES_FILE = 'autoloader_cache.php'; + const INTERFACES_KEY = "autoloader_interfaces"; + const INTERFACES_FILE = "autoloader_interface_cache.php"; private $classes; @@ -46,23 +48,36 @@ class auto_loader { private $apcu_enabled; /** - * Cache path and file name + * Cache path and file name for classes * @var string */ - private static $cache_file = null; + private static $classes_file = null; - public function __construct($project_path = '') { + /** + * Maps interfaces to classes + * @var array + */ + private $interfaces; + + /** + * Cache path and file name for interfaces + * @var string + */ + private static $interfaces_file = null; + + public function __construct() { //set if we can use RAM cache $this->apcu_enabled = function_exists('apcu_enabled') && apcu_enabled(); //set cache location - self::$cache_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::FILE; + self::$classes_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::CLASSES_FILE; + self::$interfaces_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::INTERFACES_FILE; //classes must be loaded before this object is registered if (!$this->load_cache()) { //cache miss so load them - $this->reload_classes($project_path); + $this->reload_classes(); //update the cache after loading classes array $this->update_cache(); } @@ -70,7 +85,16 @@ class auto_loader { spl_autoload_register(array($this, 'loader')); } - public function update_cache(string $file = ''): bool { + /** + * Update the auto loader + */ + public function update() { + $this->clear_cache(); + $this->reload_classes(); + $this->update_cache(); + } + + public function update_cache(): bool { //guard against writing an empty file if (empty($this->classes)) { return false; @@ -78,65 +102,74 @@ class auto_loader { //update RAM cache when available if ($this->apcu_enabled) { - $success = apcu_store(self::CACHE_KEY, $this->classes); + $classes_cached = apcu_store(self::CLASSES_KEY, $this->classes); + $interfaces_cached = apcu_store(self::INTERFACES_KEY, $this->interfaces); //do not save to drive when we are using apcu - if ($success) return true; - } - - //ensure we have somewhere to put the file - if (empty($file)) { - $file = self::$cache_file; + if ($classes_cached && $interfaces_cached) return true; } //export the classes array using PHP engine - $data = var_export($this->classes, true); + $classes_array = var_export($this->classes, true); //put the array in a form that it can be loaded directly to an array - $result = file_put_contents($file, "interfaces, true); - return false; + //put the array in a form that it can be loaded directly to an array + $interface_result = file_put_contents(self::$interfaces_file, "classes = []; + $this->interfaces = []; //use apcu when available - if ($this->apcu_enabled && apcu_exists(self::CACHE_KEY)) { - $this->classes = apcu_fetch(self::CACHE_KEY, $success); - if ($success) return true; - } - - //use a standard file - if (empty($file)) { - $file = self::$cache_file; + if ($this->apcu_enabled && apcu_exists(self::CLASSES_KEY)) { + $this->classes = apcu_fetch(self::CLASSES_KEY, $classes_cached); + $this->interfaces = apcu_fetch(self::INTERFACES_KEY, $interfaces_cached); + //don't use files when we are using apcu caching + if ($classes_cached && $interfaces_cached) return true; } //use PHP engine to parse it - if (file_exists($file)) { - $this->classes = include $file; + if (file_exists(self::$classes_file)) { + $this->classes = include self::$classes_file; + } + + //do the same for interface to class mappings + if (file_exists(self::$interfaces_file)) { + $this->interfaces = include self::$interfaces_file; } //catch edge case of first time using apcu cache if ($this->apcu_enabled) { - apcu_store(self::CACHE_KEY, $this->classes); + apcu_store(self::CLASSES_KEY, $this->classes); + apcu_store(self::INTERFACES_KEY, $this->interfaces); } //return true when we have classes and false if the array is still empty - return !empty($this->classes); + return (!empty($this->classes) && !empty($this->interfaces)); } - public function reload_classes($project_path = '') { + public function reload_classes() { //set project path using magic dir constant - if (empty($project_path)) { - $project_path = dirname(__DIR__, 2); - } + $project_path = dirname(__DIR__, 2); //build the array of all locations for classes in specific order $search_path = [ @@ -156,7 +189,7 @@ class auto_loader { } //reset the current array - $this->classes = []; + $class_list = []; //store PHP language declared classes, interfaces, and traits $current_classes = get_declared_classes(); @@ -184,7 +217,7 @@ class auto_loader { $classes = array_diff($new_classes, $current_classes); if (!empty($classes)) { foreach ($classes as $class) { - $this->classes[$class] = $file; + $class_list[$class] = $file; } //overwrite previous array with new values $current_classes = $new_classes; @@ -194,7 +227,7 @@ class auto_loader { $interfaces = array_diff($new_interfaces, $current_interfaces); if (!empty($interfaces)) { foreach ($interfaces as $interface) { - $this->classes[$interface] = $file; + $class_list[$interface] = $file; } //overwrite previous array with new values $current_interfaces = $new_interfaces; @@ -204,12 +237,24 @@ class auto_loader { $traits = array_diff($new_traits, $current_traits); if (!empty($traits)) { foreach ($traits as $trait) { - $this->classes[$trait] = $file; + $class_list[$trait] = $file; } //overwrite previous array with new values $current_traits = $new_traits; } } + + //add only new classes + if (!empty($class_list)) { + foreach ($class_list as $class => $location) { + if (!isset($this->classes[$class])) { + $this->classes[$class] = $location; + } + } + } + + //load interface to class mappings + $this->reload_interface_list(); } /** @@ -276,6 +321,24 @@ class auto_loader { return false; } + public function reload_interface_list() { + $this->interfaces = []; + if (!empty($this->classes)) { + $classes = array_keys($this->classes); + foreach ($classes as $class) { + $reflection = new ReflectionClass($class); + $interfaces = $reflection->getInterfaces(); + foreach ($interfaces as $interface) { + $name = $interface->getName(); + if (empty($this->interfaces[$name])) { + $this->interfaces[$name] = []; + } + $this->interfaces[$name][] = $class; + } + } + } + } + /** * Returns a list of classes loaded by the auto_loader. If no classes have been loaded an empty array is returned. * @return array List of classes loaded by the auto_loader or empty array @@ -287,26 +350,70 @@ class auto_loader { return []; } - public static function clear_cache(string $file = '') { + /** + * Returns a list of classes implementing the interface + * @param string $interface_name + * @return array + */ + public function get_interface_list(string $interface_name): array { + //make sure we can return values + if (empty($this->classes) || empty($this->interfaces)) { + return []; + } + //check if we have an interface with that name + if (!empty($this->interfaces[$interface_name])) { + //return the list of classes associated with that interface + return $this->interfaces[$interface_name]; + } + //interface is not implemented by any classes + return []; + } + + public function get_interfaces(): array { + if (!empty($this->interfaces)) { + return $this->interfaces; + } + return []; + } + + public static function clear_cache(string $classes_file = '', string $interfaces_file = '') { //check for apcu cache if (function_exists('apcu_enabled') && apcu_enabled()) { - apcu_delete(self::CACHE_KEY); + apcu_delete(self::CLASSES_KEY); + apcu_delete(self::INTERFACES_KEY); } //set default file - if (empty(self::$cache_file)) { - self::$cache_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::FILE; + if (empty(self::$classes_file)) { + self::$classes_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::CLASSES_FILE; } //set file to clear - if (empty($file)) { - $file = self::$cache_file; + if (empty($classes_file)) { + $classes_file = self::$classes_file; } //remove the file when it exists - if (file_exists($file)) { - @unlink($file); + if (file_exists($classes_file)) { + @unlink($classes_file); + $error_array = error_get_last(); + //send to syslog when debugging with either environment variable or debug in the url + self::log(LOG_WARNING, $error_array['message'] ?? ''); + } + + if (empty(self::$interfaces_file)) { + self::$interfaces_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::INTERFACES_FILE; + } + + //set interfaces file to clear + if (empty($interfaces_file)) { + $interfaces_file = self::$interfaces_file; + } + + //remove the file when it exists + if (file_exists($interfaces_file)) { + @unlink($interfaces_file); $error_array = error_get_last(); //send to syslog when debugging with either environment variable or debug in the url self::log(LOG_WARNING, $error_array['message'] ?? ''); diff --git a/resources/classes/settings.php b/resources/classes/settings.php index f0105c68a9..9d19e0667f 100644 --- a/resources/classes/settings.php +++ b/resources/classes/settings.php @@ -9,7 +9,7 @@ * @access public * @author Mark Crane */ -class settings { +class settings implements clear_cache { /** * Set in the constructor. String used to load a specific domain. Must be a value domain UUID before sending to the constructor. @@ -446,31 +446,13 @@ class settings { } } - /** - * Clears the settings cache - * @param string $type Empty clears all settings. Values can be global, domain, or user. - */ - public static function clear_cache(string $type = '') { - if (function_exists('apcu_enabled') && apcu_enabled()) { - switch ($type) { - case 'all': - case '': - default: - $type = ""; - break; - case 'global': - case 'domain': - case 'user': - $type .= "_"; - break; - } - $cache = apcu_cache_info(false); - if (!empty($cache['cache_list'])) { - foreach ($cache['cache_list'] as $entry) { - $key = $entry['info']; - if (str_starts_with($key, 'settings_' . $type)) { - apcu_delete($key); - } + public static function clear_cache() { + $cache = apcu_cache_info(false); + if (!empty($cache['cache_list'])) { + foreach ($cache['cache_list'] as $entry) { + $key = $entry['info']; + if (str_starts_with($key, 'settings_')) { + apcu_delete($key); } } } diff --git a/resources/interfaces/clear_cache.php b/resources/interfaces/clear_cache.php new file mode 100644 index 0000000000..da3837db6b --- /dev/null +++ b/resources/interfaces/clear_cache.php @@ -0,0 +1,39 @@ + + * Portions created by the Initial Developer are Copyright (C) 2008-2025 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark J Crane + * Tim Fry + */ + +/** + * Any class that implements the clear_cache interface will automatically be called when the cache button is pressed + * @author Tim Fry + */ +interface clear_cache { + /** + * The clear_cache method is called automatically for any class that implements the clear_cache interface. + * The function declared here ensures that all clear_cache methods have the same number of parameters being passed, which in this case, is no parameters. + */ + public static function clear_cache(); +}