From b2381002bed6b75c682386ca857ffed0ce584a80 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 20 Oct 2017 09:45:53 -0400 Subject: [PATCH] Improve error reporting ability of $modules->get(), $modules->getModule(), primarily for debugging purposes. --- wire/core/Modules.php | 110 +++++++++++++++++++++++--------- wire/core/ProcessController.php | 33 ++++++++-- 2 files changed, 106 insertions(+), 37 deletions(-) diff --git a/wire/core/Modules.php b/wire/core/Modules.php index ce93ed00..ee90d20e 100644 --- a/wire/core/Modules.php +++ b/wire/core/Modules.php @@ -567,14 +567,19 @@ class Modules extends WireArray { * Initialize a single module * * @param Module $module - * @param bool $clearSettings If true, module settings will be cleared when appropriate to save space. + * @param array $options + * - `clearSettings` (bool): When true, module settings will be cleared when appropriate to save space. (default=true) + * - `throw` (bool): When true, exceptions will be allowed to pass through. (default=false) * @return bool True on success, false on fail + * @throws \Exception Only if the `throw` option is true. * */ - protected function initModule(Module $module, $clearSettings = true) { + protected function initModule(Module $module, array $options = array()) { $result = true; $debugKey = null; + $clearSettings = isset($options['clearSettings']) ? (bool) $options['clearSettings'] : true; + $throw = isset($options['throw']) ? (bool) $options['throw'] : false; if($this->debug) { static $n = 0; @@ -601,6 +606,7 @@ class Modules extends WireArray { try { $module->init(); } catch(\Exception $e) { + if($throw) throw($e); $this->error(sprintf($this->_('Failed to init module: %s'), $moduleName) . " - " . $e->getMessage()); $result = false; } @@ -1167,11 +1173,13 @@ class Modules extends WireArray { * This is the same as `$modules->get()` except that you can specify additional options to modify default behavior. * These are the options you can specify in the `$options` array argument: * - * - `noPermissionCheck` (bool): Specify true to disable module permission checks (and resulting exception). - * - `noInstall` (bool): Specify true to prevent a non-installed module from installing from this request. - * - `noInit` (bool): Specify true to prevent the module from being initialized. - * - `noSubstitute` (bool): Specify true to prevent inclusion of a substitute module. - * - `noCache` (bool): Specify true to prevent module instance from being cached for later getModule() calls. + * - `noPermissionCheck` (bool): Specify true to disable module permission checks (and resulting exception). (default=false) + * - `noInstall` (bool): Specify true to prevent a non-installed module from installing from this request. (default=false) + * - `noInit` (bool): Specify true to prevent the module from being initialized. (default=false) + * - `noSubstitute` (bool): Specify true to prevent inclusion of a substitute module. (default=false) + * - `noCache` (bool): Specify true to prevent module instance from being cached for later getModule() calls. (default=false) + * - `noThrow` (bool): Specify true to prevent exceptions from being thrown on permission or fatal error. (default=false) + * - `returnError` (bool): Return an error message (string) on error, rather than null. (default=false) * * If the module is not installed, but is installable, it will be installed, instantiated, and initialized. * If you don't want that behavior, call `$modules->isInstalled('ModuleName')` as a condition first, OR specify @@ -1179,69 +1187,111 @@ class Modules extends WireArray { * * @param string|int $key Module name or database ID. * @param array $options Optional settings to change load behavior, see method description for details. - * @return Module|_Module|null Returns ready-to-use module or NULL if not found. - * @throws WirePermissionException If module requires a particular permission the user does not have + * @return Module|_Module|null|string Returns ready-to-use module or NULL|string if not found (string if `returnError` option used). + * @throws WirePermissionException|\Exception If module requires a particular permission the user does not have * @see Modules::get() * */ public function getModule($key, array $options = array()) { - if(empty($key)) return null; $module = null; $needsInit = false; + $error = ''; + + if(empty($key)) { + return empty($options['returnError']) ? null : "No module specified"; + } // check for optional module ID and convert to classname if found if(ctype_digit("$key")) { - if(!$key = array_search($key, $this->moduleIDs)) return null; + $moduleID = (int) $key; + if(!$key = array_search($key, $this->moduleIDs)) { + return empty($options['returnError']) ? null : "Unable to find module ID $moduleID"; + } } else { $key = wireClassName($key, false); } - $module = parent::get($key); - if(!$module && empty($options['noSubstitute'])) { - if($this->isInstallable($key) && empty($options['noInstall'])) { - // module is on file system and may be installed, no need to substitute + + if(!$module) { + if(empty($options['noSubstitute'])) { + if($this->isInstallable($key) && empty($options['noInstall'])) { + // module is on file system and may be installed, no need to substitute + } else { + $module = $this->getSubstituteModule($key, $options); + if($module) return $module; // returned module is ready to use + } } else { - $module = $this->getSubstituteModule($key, $options); - if($module) return $module; // returned module is ready to use + $error = "Module '$key' not found and substitute not allowed (noSubstitute=true)"; } } if($module) { - // check if it's a placeholder, and if it is then include/instantiate/init the real module // OR check if it's non-singular, so that a new instance is created if($module instanceof ModulePlaceholder || !$this->isSingular($module)) { $placeholder = $module; $class = $this->getModuleClass($placeholder); - if($module instanceof ModulePlaceholder) $this->includeModule($module); - $module = $this->newModule($class); + try { + if($module instanceof ModulePlaceholder) $this->includeModule($module); + $module = $this->newModule($class); + } catch(\Exception $e) { + if(empty($options['noThrow'])) throw $e; + return empty($options['returnError']) ? null : "Module '$key' - " . $e->getMessage(); + } // if singular, save the instance so it can be used in later calls if($module && $this->isSingular($module) && empty($options['noCache'])) $this->set($key, $module); $needsInit = true; } - } else if(empty($options['noInstall']) && array_key_exists($key, $this->getInstallable())) { - // check if the request is for an uninstalled module - // if so, install it and return it - $module = $this->install($key); - $needsInit = true; + } else if(empty($options['noInstall'])) { + // module was not available to get, see if we can install it + if(array_key_exists($key, $this->getInstallable())) { + // check if the request is for an uninstalled module + // if so, install it and return it + try { + $module = $this->install($key); + } catch(\Exception $e) { + if(empty($options['noThrow'])) throw $e; + if(!empty($options['returnError'])) return "Module '$key' install failed: " . $e->getMessage(); + } + $needsInit = true; + if(!$module) $error = "Module '$key' not installed and install failed"; + } else { + $error = "Module '$key' is not present or listed as installable"; + } + } else { + $error = "Module '$key' is not present and not installable (noInstall=true)"; } - if($module && empty($options['noPermissionCheck'])) { + if(!$module) { + if(!$error) $error = "Unable to get module '$key'"; + return empty($options['returnError']) ? null : $error; + } + + if(empty($options['noPermissionCheck'])) { + // check that user has permission required to use module if(!$this->hasPermission($module, $this->wire('user'), $this->wire('page'))) { - throw new WirePermissionException($this->_('You do not have permission to execute this module') . ' - ' . wireClassName($module)); + $error = $this->_('You do not have permission to execute this module') . ' - ' . wireClassName($module); + if(empty($options['noThrow'])) throw new WirePermissionException($error); + return empty($options['returnError']) ? null : $error; } } // skip autoload modules because they have already been initialized in the load() method // unless they were just installed, in which case we need do init now - if($module && $needsInit) { + if($needsInit && empty($options['noInit'])) { // if the module is configurable, then load it's config data // and set values for each before initializing the module - if(empty($options['noInit'])) { - if(!$this->initModule($module, false)) $module = null; + try { + if(!$this->initModule($module, array('clearSettings' => false, 'throw' => true))) { + return empty($options['returnError']) ? null : "Module '$module' failed init"; + $module = null; + } + } catch(\Exception $e) { + if(empty($options['noThrow'])) throw $e; + return empty($options['returnError']) ? null : "Module '$module' throw Exception on init - " . $e->getMessage(); } } diff --git a/wire/core/ProcessController.php b/wire/core/ProcessController.php index 86847927..35c37218 100644 --- a/wire/core/ProcessController.php +++ b/wire/core/ProcessController.php @@ -52,7 +52,15 @@ class ProcessController extends Wire { * @var string * */ - protected $processName; + protected $processName; + + /** + * Error message if unable to load Process module + * + * @var string + * + */ + protected $processError = ''; /** * The name of the method to execute in this process @@ -141,9 +149,13 @@ class ProcessController extends Wire { */ public function getProcess() { - if($this->process) $processName = $this->process->className(); - else if($this->processName) $processName = $this->processName; - else return null; + if($this->process) { + $processName = $this->process->className(); + } else if($this->processName) { + $processName = $this->processName; + } else { + return null; + } // verify that there is adequate permission to execute the Process $permissionName = ''; @@ -151,11 +163,18 @@ class ProcessController extends Wire { if(!empty($info['permission'])) $permissionName = $info['permission']; $this->hasPermission($permissionName, true); // throws exception if no permission + if(!$this->process) { - $this->process = $this->modules->getModule($processName); + $module = $this->modules->getModule($processName, array('returnError' => true)); + if(is_string($module)) { + $this->processError = $module; + $this->process = null; + } else { + $this->process = $module; + } } - // set a proces fuel, primarily so that certain Processes can determine if they are the root Process + // set a process fuel, primarily so that certain Processes can determine if they are the root Process // example: PageList when in PageEdit $this->wire('process', $this->process); @@ -259,7 +278,7 @@ class ProcessController extends Wire { } } else { - throw new ProcessController404Exception("The requested process does not exist"); + throw new ProcessController404Exception("The requested process does not exist - $this->processError"); } if(empty($content) || is_bool($content)) {