2, 'ini' => 4, 'xml' => 16]; /** * __construct() -- private method * * constructor function for the class - determines the type of configuration file to read (if none is provided, * then attempt to determine by the file's extension). * * depending on a file extension, invoke the appropriate function to parse the config file in an internal * data structure. * * * @author mshallop@pathway.com * @version 2.1.7 * * @param $_cFile * @param string $_fType * * * HISTORY: * ======== * 06-07-17 mks original coding * */ private function __construct(string $_cFile, string $_fType = gasConfig::AUTO) { //register_shutdown_function('pgsConfig::__destruct'); self::getConfig($_cFile, $_fType); if (gasConfig::$settings[CONFIG_DEBUG]) consoleLog(static::$res, CON_DEBUG, INFO_CONFIG_LOADED); } /** * registerEnvironment() -- private static method * * This method has no input parameters and returns a boolean to indicate successful processing. * * The method requires that the XML configuration be pre-loaded prior to invocation. * * Method creates an array of services and assigns a boolean value to each service (associative array) * which is then transferred to a member variable. * * Note that "available services" are relative to the local service and not indicative of overall service * ability for a distributed cluster. * * * * @author mike@givingassistant.org * @version 1.0 * * @return bool * * * HISTORY: * ======== * 06-08-18 mks CORE-1035: original coding (transferred from startBrokers.php) * 09-08-20 mks DB-168: updated for XML service locality re-configuration * 11-09-20 mks DB-171: update check for local service registration to also be qualified on ACTIVE setting * */ private static function registerEnvironment(): bool { $environments = []; // using the environment in the XML config -- generate a list of currently-active services. foreach (gasConfig::$settings[CONFIG_REGISTERED_SERVICES][CONFIG_DATABASE_MONGODB_ADMIN_REPLSET_SET] as $service) { if (isset(gasConfig::$settings[CONFIG_BROKER_SERVICES][$service]) and is_array(gasConfig::$settings[CONFIG_BROKER_SERVICES][$service])) { if (isset(gasConfig::$settings[$service][CONFIG_IS_LOCAL])) { if (isset(gasConfig::$settings[$service][CONFIG_ACTIVE])) { $environments[$service] = boolval(gasConfig::$settings[$service][CONFIG_IS_LOCAL]) && boolval(gasConfig::$settings[$service][CONFIG_ACTIVE]); } else { $hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__); $msg = $hdr . sprintf(CONFIG_XML_SERVICE_SETTING, CONFIG_BROKER_SERVICES . ARROW . $service . ARROW . CONFIG_IS_LOCAL); consoleLog(static::$res, CON_ERROR, $msg); return false; } } else { $hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__); $msg = $hdr . sprintf(CONFIG_XML_SERVICE_SETTING, CONFIG_BROKER_SERVICES . ARROW . $service . ARROW . CONFIG_IS_LOCAL); consoleLog(static::$res, CON_ERROR, $msg); return false; } } } if (gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] == ENV_PRODUCTION) { // if we're starting in a production environment -- this requires services (appServer, Admin, Segundo and Tercero) // to all be started on separate instances. This block enforces that all services are discrete per instance. if (array_sum($environments) > 1) { $msg = CONFIG_XML_SERVICE_VIOLATION; foreach ($environments as $key => $value) { if ($value == 1) $msg .= $key . ', '; } $msg = rtrim($msg, ', '); consoleLog(static::$res, CON_ERROR, $msg); return false; } } // save the local services to the gasConfig object static::$settings[CONFIG_REGISTERED_SERVICES] = $environments; return true; } /** * getConfig() -- private static method * * reads the configuration file (passed in by $_cFile) from the DIR_CONFIG directory and merges the files into * (or onto) the existing configuration structure ($settings) on subsequent calls to this method. * * supported file types: * -- json * -- php.ini * -- xml * * if the files cannot be accessed, or if an invalid file type is given, then dump the error to stdout (logfile) * and exit. * * * @author mike@givingassistant.org * @version 1.0 * * @param $_cFile -- config file path and name * @param string $_fType -- config file type * * * HISTORY: * ======== * 06-07-17 mks original coding * */ private static function getConfig(string $_cFile, string $_fType = gasConfig::AUTO) { $tSettings = null; // temporary holder for file settings if ($_fType == self::AUTO) { $_fType = self::$CONF_EXT_RELATION[pathinfo($_cFile, PATHINFO_EXTENSION)]; } switch ($_fType) { case self::JSON : $result = file_get_contents($_cFile, true); if (!$result) { consoleLog(static::$res, CON_ERROR, CONFIG_FTL_JSON . $_cFile); return; } else { $tSettings = json_decode($result); } break; case self::PHP_INI : $result = parse_ini_file($_cFile, true); if (!$result) { consoleLog(static::$res, CON_ERROR, CONFIG_FTL_INI . $_cFile); return; } else { $tSettings = $result; } break; case self::XML : $result = simplexml_load_file($_cFile); if (!$result) { consoleLog(static::$res, CON_ERROR, CONFIG_FTL_XML . $_cFile); return; } else { try { $tSettings = self::objectToArray($result); } catch (TypeError $t) { consoleLog(static::$res, CON_ERROR, ERROR_TYPE_EXCEPTION . COLON . $t->getMessage()); return; } } break; } if (!is_null($tSettings)) { if (is_null(self::$settings)) { self::$settings = $tSettings; } else { self::$settings = self::recursiveArrayMerge(self::$settings, $tSettings, true); } try { self::recursiveArrayPurge(self::$settings); } catch (TypeError $t) { consoleLog(static::$res, CON_ERROR, ERROR_TYPE_EXCEPTION . COLON . $t->getMessage()); return; } } // // validate the environment // if (!in_array(self::$settings[CONFIG_ID][CONFIG_ID_NODE], self::$env)) { // consoleLog(static::$res, CON_ERROR, CONFIG_UNK_ENV . self::$settings[CONFIG_ID][CONFIG_ID_NODE]); // exit(1); // } } /** * recursiveArrayMerge() -- private static method * * this takes the original configuration array and merges subsequent configuration files on top of it. * * for example, if you have a structure: * * $this[database][mysql] section defined: * * * * localhost * user_name * user_pass * 3306 * some_database_name * * * and you want to change the database name for your local environment, then the * subsequent configuration file would bear an identical parent structure, and an * identical element naming structure....changed data within the elements would be * copied over the existing parent structure: * * * * some_database_name * * * with the resulting output: * [database] => Array * ( * [mysql] => Array * ( * [db_hostname] => localhost * [db_username] => user * [db_password] => password * [db_port] => 3306 * [db_database] => some_database_name * ) * ) * * @author mike@givingassistant.org * @version 1.0 * * @param $array1 * @param $array2 * @param bool $overwrite * @return array * * HISTORY: * ======== * 06-07-17 mks original coding * 06-20-18 mks CORE-1045: trapped PHP fatal error -- if there's an error in the XML where you have * re-declared a variable (to a different value), the framework IPL will crash. * This fix will prevent the crash from happening and output a console message. * */ public static function recursiveArrayMerge(array $array1, array $array2, bool $overwrite = true) { foreach ($array2 as $key => $val) { if (isset($array1[$key])) { if (is_array($val)) { try { $array1[$key] = self::recursiveArrayMerge($array1[$key], $val); } catch (TypeError $t) { consoleLog(static::$res, CON_SYSTEM, CONFIG_XML_DUP_VAR . $key); consoleLog(static::$res, CON_SYSTEM, $t->getMessage()); } } elseif ((is_string($array1[$key]) or is_int($array1[$key])) && $overwrite) { $array1[$key] = $val; } } else { $array1[$key] = $val; } } return $array1; } /** * recursiveArrayPurge() -- local private method * * So, as it turns out, simplexml_load_file() does not ignore comments embedded into the XML... mostly... in truth, * placeholder indices are created within the structure that are also arrays, albeit empty. * * So this function, which is called by the getConfig method, recursively loops through the existing $settings * structure and removes any array with a key of "comment". This, of course, means that one cannot include this * particular value as an index key within the xml file. * * The input parameter to the method is a call-by-reference value which allows us to traverse the settings * structure recursively and retain changes to the overall array. * * * @author mike@givingassistant.org * @version 1.0 * * @param array $ary -- initially, should be the static::$settings array, thereafter, sub-arrays from within * * * HISTORY: * ======== * 06-13-17 mks original coding * 11-28-17 mks CORE-635: fixed bug in conditional that was causing 0th elements of XML sub-arrays * to be discarded * */ private static function recursiveArrayPurge(array &$ary) { foreach ($ary as $key => &$value) { if ($key === STRING_COMMENT) { unset($ary[$key]); } elseif (is_array($value)) { try { self::recursiveArrayPurge($value); } catch (TypeError $t) { consoleLog(static::$res, CON_ERROR, $t->getMessage()); } } } } /** * objectToArray() - private static method * * recursive function to flatten objects to a single array. * If the object contains embedded objects, then self-invoke. * * @author mike@givingassistant.org * @version 1.0 * * @param $_obj - a collection of either objects or arrays * @return array - the flattened object * * HISTORY: * ======== * 06-07-17 mks original coding * 07-21-17 mks CORE-468: if the XML value is numeric (float or int) then return a float or int instead * of a string. Do this by implicitly casting the value by adding 0 to the value. * */ private static function objectToArray($_obj) { $ph = null; // placeholder $ph = (is_object($_obj)) ? get_object_vars($_obj) : $_obj; foreach ($ph as $key => $val) { // todo - evaluate to see if is_array is ever true - if not, use strong typing (SimpleXMLElement) $ph[$key] = ((is_array($val)) or (is_object($val))) ? self::objectToArray($val) : (is_numeric($val) ? $val + 0 : $val); } return ($ph); } /** * __get() -- public method * * This is a magic function for accessing private data within the config class object. * Note that magic functions are required to be public and not static. * * input parameter ($_section) is the string (tag) referencing the (sub)section of the configuration file * to be returned. If $_section does not exist as an array key, or if self::$settings has yet to be set, * then return Boolean(false) -- otherwise return the sub-scripted array as referenced by $_section. * * LIMITATIONS: * ------------ * Sub-arrays cannot be deeper than one level. * * @author mike@givingassistant.org * @version 1.0 * * @param $_section * @return bool * * HISTORY: * ======== * 06-07-17 mks original coding * */ public function __get($_section) { return (is_array(self::$settings) and (array_key_exists($_section, self::$settings)) ? self::$settings[$_section] : false); } /** * getPedigree() -- public static function * * This function has no input parameters and returns an associative array. * * The function pulls the current configuration and returns selected parameters to the calling client. These values * are going to be string values, except for the current version which is cast to float, and will indicate if a * feature is enabled, disabled or, if there's a configuration error, set to an error message. * * It's the responsibility of the calling program to parse the return array and to compare/contrast the selected * values. * * * @author mike@givingassistant.org * @version 1.0 * * @return array * * * HISTORY: * ======== * 07-09-18 mks CORE-1017: original coding * */ public static function getPedigree(): array { $retData[PEDIGREE_ENV] = isset(gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV]) ? gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] : ERROR_STUB_NOTDEF; $retData[PEDIGREE_VER] = isset(gasConfig::$settings[CONFIG_ID][CONFIG_ID_VER]) ? floatval(gasConfig::$settings[CONFIG_ID][CONFIG_ID_VER]) : ERROR_STUB_NOTDEF; $retData[PEDIGREE_DEBUG] = isset(gasConfig::$settings[CONFIG_DEBUG]) ? ((intval(gasConfig::$settings[CONFIG_DEBUG]) == 1) ? STRING_ENABLED : STRING_DISABLED) : ERROR_STUB_NOTDEF; $retData[PEDIGREE_SYSLOG] = STRING_ENABLED; $retData[PEDIGREE_AUDIT] = isset(gasConfig::$settings[CONFIG_AUDIT_ON]) ? ((intval(gasConfig::$settings[CONFIG_AUDIT_ON]) == 1) ? STRING_ENABLED : STRING_DISABLED) : ERROR_STUB_NOTDEF; $retData[PEDIGREE_JOURNAL] = isset(gasConfig::$settings[CONFIG_JOURNAL_ON]) ? ((intval(gasConfig::$settings[CONFIG_JOURNAL_ON]) == 1) ? STRING_ENABLED : STRING_DISABLED) : ERROR_STUB_NOTDEF; $retData[PEDIGREE_SEGUNDO] = isset(gasConfig::$settings[CONFIG_BROKER_SEGUNDO]) ? ((intval(gasConfig::$settings[CONFIG_BROKER_SEGUNDO]) == 1) ? STRING_ENABLED : STRING_DISABLED) : ERROR_STUB_NOTDEF; $retData[PEDIGREE_TERCERO] = isset(gasConfig::$settings[CONFIG_BROKER_TERCERO]) ? ((intval(gasConfig::$settings[CONFIG_BROKER_TERCERO]) == 1) ? STRING_ENABLED : STRING_DISABLED) : ERROR_STUB_NOTDEF; $retData[PEDIGREE_QTAG] = isset(gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_QUEUE_TAG]) ? gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_QUEUE_TAG] : ERROR_STUB_NOTDEF; $retData[PEDIGREE_VHOST] = isset(gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_VHOST]) ? gasConfig::$settings[CONFIG_BROKER_SERVICES][CONFIG_BROKER_VHOST] : ERROR_STUB_NOTDEF; return $retData; } /** * singleton() -- public static function * * the problem with php is that it does not support true static classes. If you add a debug output to this method * when it's entered, you'll see it proc every time this method is called. * * The input parameters are the name of the configuration file and the configuration file type - which defaults * to the "auto" config type. * * The output is the array structure as defined by the config file itself since it's (more or less) read-in * as the input. * * @author mike@givingassistant.org * @version 1.0 * * @param $_iniFile -- path/filename.ext to the config file * @param string $_iniType -- extension type of the config file * @return gasConfig -- returns an array or false * * * HISTORY: * ======== * 06-07-17 mks original coding * */ public static function singleton(string $_iniFile, string $_iniType = gasConfig::AUTO): gasConfig { if (static::$instance === null) { $c = __CLASS__; static::$instance = new $c($_iniFile, $_iniType); } return static::$instance; } /** * addConfig() -- public static method * * subsequent calls to read-in additional configuration are handled by this method which invokes the private * method already used to load the 0th-case. * * @author mike@givingassistant.org * @version 1.0 * * @param $_file * @param string $_type * * HISTORY: * ======== * 06-07-17 mks original coding * 06-08-18 mks CORE-1035: adding service environments to $settings * */ public static function addConfig(string $_file, string $_type = gasConfig::AUTO) { self::getConfig($_file, $_type); // now that both env files are loaded, register the service environments: static::$status = false; try { if (!self::registerEnvironment()) { consoleLog(static::$res, CON_ERROR, ERROR_CONFIG_RESOURCE_404 . STRING_SVC_ENV); } else { static::$status = true; } } catch (TypeError $t) { consoleLog(static::$res, CON_ERROR, ERROR_TYPE_EXCEPTION . COLON . $t->getMessage()); } } /** * __clone() -- public static method * * disallow cloning by return an explicit null on the request * * @author mshallop@pathway.com * * @return null * * HISTORY: * ======== * 06-07-17 mks original coding * */ public function __clone() { return(null); // disallow cloning of this class } }