Files
namaste/classes/gasConfig.class.inc
gramps 373ebc8c93 Archive: Namaste PHP AMQP framework v1.0 (2017-2020)
952 days continuous production uptime, 40k+ tp/s single node.
Original corpo Bitbucket history not included — clean archive commit.
2026-04-05 09:49:30 -07:00

580 lines
22 KiB
PHP

<?php
/**
* gasConfig -- static configuration class
*
* The data framework is intended to read two configuration files of varying types to create a single structure
* containing all of the configuration options.
*
* subsequent requests/instantiations to different configuration files *overwrite* the duplicated-tag sections.
*
* this approach allows us to load a core configuration, then to modify the structure generated with a second
* configuration file.
*
* class is designed as a singleton so that only one "authoritative" config file can exist.
*
* To invoke, call the singleton function with the path/filename and the file type:
*
* $foo = gasConfig::singleton('./config/base.xml', 'xml');
*
* all configuration file names, and their file types, are defined in the global constants file.
*
* all key-value-paired data is loaded, stored, and accessed in the classes' $settings member - given a key (tag,
* index, etc.), you can access/pull the data using the following:
*
* $foo = gasConfig::$settings[$key];
*
* which will either return a value or a sub-array depending on what $key indexes.
*
* KNOWN LIMITATIONS:
* ------------------
* Config files have to be supported by the corresponding PHP function that parses that config file type. As of this
* writing, only the following file types are supported:
*
* -- xml
* -- ini
* -- json
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-07-17 mks original coding
* 06-08-18 mks CORE-1035: console logging upgrade
* register $setting member function added
* 06-15-18 mks CORE-1045: deprecated CONFIG_ID_NODE XML tag
* 09-24-19 mks DB-136: deprecated getSyslog() method
* 01-08-20 mks DB-150: PHP7.4 class member type-casting
*
*/
class gasConfig
{
private static ?gasConfig $instance = null; // self-pointer
public static ?string $status = null; // used to validate successful instantiation
// private static $env = [ // defines the valid environments
// CONFIG_ID_NODE_NAMASTE,
// CONFIG_ID_NODE_ADMIN,
// CONFIG_ID_NODE_DEV
// ];
public static ?array $settings = null; // hold all the configs stuffs
CONST AUTO = 0; // config file format-type associations
CONST JSON = 2;
const PHP_INI = 4;
const XML = 16;
static private string $res = 'CNFG: '; // logger id tag
static private array $CONF_EXT_RELATION = ['json' => 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:
*
* <database>
* <mysql>
* <db_hostname>localhost</db_hostname>
* <db_username>user_name</db_username>
* <db_password>user_pass</db_password>
* <db_port>3306</db_port>
* <db_database>some_database_name</db_database>
* </mysql>
*
* 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:
*
* <database>
* <mysql>
* <db_database>some_database_name</db_database>
* </mysql>
*
* 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
}
}