From 592adae6c1b742b380a8b1a3f61035f9fc2f4bac Mon Sep 17 00:00:00 2001 From: Stephen Nielson Date: Thu, 11 Aug 2022 15:32:52 -0400 Subject: [PATCH] Fixes #5643 ModulesClassLoader autoload module code without composer install (#5644) * Fixes #5643 ModulesClassLoader Implemented a modules class loader that allow module writers to register a namespace if its not already been registered. This makes it so the ModuleWriter doesn't have to use requires everywhere and a module can be just dropped into the filesystem and execute once its been registered and installed from the Manage Modules interface. * Style fixes --- src/Core/ModulesApplication.php | 10 +++-- src/Core/ModulesClassLoader.php | 81 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/Core/ModulesClassLoader.php diff --git a/src/Core/ModulesApplication.php b/src/Core/ModulesApplication.php index 262e8b830..b0207c24e 100644 --- a/src/Core/ModulesApplication.php +++ b/src/Core/ModulesApplication.php @@ -67,10 +67,12 @@ class ModulesApplication $db_modules[] = $row["mod_name"]; } - $this->bootstrapCustomModules($kernel->getEventDispatcher(), $customModulePath); + // let's get our autoloader that people can tie into if they need it + $autoloader = new ModulesClassLoader($webRootPath); + $this->bootstrapCustomModules($autoloader, $kernel->getEventDispatcher(), $customModulePath); } - private function bootstrapCustomModules($eventDispatcher, $customModulePath) + private function bootstrapCustomModules(ModulesClassLoader $classLoader, $eventDispatcher, $customModulePath) { // we skip the audit log as it has no bearing on user activity and is core system related... $resultSet = sqlStatementNoLog($statement = "SELECT mod_name, mod_directory FROM modules WHERE mod_active = 1 AND type != 1 ORDER BY `mod_ui_order`, `date`"); @@ -93,13 +95,13 @@ class ModulesApplication } } foreach ($db_modules as $module) { - $this->loadCustomModule($module, $eventDispatcher); + $this->loadCustomModule($classLoader, $module, $eventDispatcher); } // TODO: stephen we should fire an event saying we've now loaded all the modules here. // Unsure who'd be listening or care. } - private function loadCustomModule($module, $eventDispatcher) + private function loadCustomModule(ModulesClassLoader $classLoader, $module, $eventDispatcher) { try { // the only thing in scope here is $module and $eventDispatcher which is ok for our bootstrap piece. diff --git a/src/Core/ModulesClassLoader.php b/src/Core/ModulesClassLoader.php new file mode 100644 index 000000000..12c216e98 --- /dev/null +++ b/src/Core/ModulesClassLoader.php @@ -0,0 +1,81 @@ + + * // openemr.bootstrap.php + * namespace Acme\OpenEMR\Modules\MyUniqueModule; + * use OpenEMR\Core\ModulesClassLoader; + * // @global ModulesClassLoader $classLoader + * $classLoader->registerNamespaceIfNotExists('Acme\\OpenEMR\\Modules\\MyUniqueModule\\', __DIR__ . DIRECTORY_SEPARATOR . 'src'); + * // run any other custom module code needed here. + * + * + * @package OpenEMR + * @link http://www.open-emr.org + * + * @author Stephen Nielson + * @copyright Copyright (c) 2019 Stephen Nielson + * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 + */ + +namespace OpenEMR\Core; + +use Composer\Autoload\ClassLoader; + +class ModulesClassLoader +{ + /** + * @var ClassLoader + */ + private $classLoader; + + public function __construct($webRootPath) + { + $this->classLoader = require $webRootPath . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; + } + + /** + * Registers a set of PSR-4 directories for a given namespace. If the namespace already exists it skips registering + * the namespace (such as if the module has been installed via the main composer.json file) + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories to + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function registerNamespaceIfNotExists($namespace, $paths) + { + $prefixes = $this->classLoader->getPrefixesPsr4(); + if (empty($prefixes[$namespace])) { + $this->classLoader->addPsr4($namespace, $paths); + return true; + } + return false; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function registerClassmap($classMap) + { + $this->classLoader->addClassMap($classMap); + } +} -- 2.11.4.GIT