952 days continuous production uptime, 40k+ tp/s single node. Original corpo Bitbucket history not included — clean archive commit.
2129 lines
92 KiB
PHP
2129 lines
92 KiB
PHP
<?php
|
|
/**
|
|
* gasCache -- static class
|
|
*
|
|
* Class handles all memcache operations.
|
|
*
|
|
* All data is serialized JSON objects - which requires this class to fetch the items from memcache based on
|
|
* not only the data obfuscation, but also the key generation under which the object is stored.
|
|
*
|
|
* Keys are composed of five parts and all parts are required. The general format of key is:
|
|
*
|
|
* {FW_VERSION}_{CDN_VERSION}_{DATA_CLASS}_{RECORD_GUID}
|
|
*
|
|
* this class has full support for both generation keys based on the memcache version string and on the CDN version
|
|
* key, both of which are defined in the global configuration file. All "parts" of the key are separated by the
|
|
* underscore (_) character.
|
|
*
|
|
* To invalidate the current cache, one needs only to tweak one of these two key values in the configuration file.
|
|
* Old/invalid entries will be purged through time expiration under the cache management system.
|
|
*
|
|
* Note: Version key allows us to invalidate caches when changes are made to the release software. CDN keys
|
|
* allow us to invalidate caches when changes are made to the CDN server. Though as of initial coding, this is
|
|
* not a planned feature of the infrastructure - it is, however, supported in the interest of future expansion.
|
|
*
|
|
* Note that we're not ever going to delete anything from cache - this is because for all objects added to the
|
|
* cache, we've set an expiration timer of 1-hour -- items will naturally purge themselves via the memcache
|
|
* services, thus saving us the overhead of making a delete call.
|
|
*
|
|
* Programmer Notes:
|
|
* -----------------
|
|
* The $cacheName variable is defined in core-abstraction and is set in the class instantiation -- this was designed
|
|
* intentionally so as to NOT expose the collection/table name in the cache key.
|
|
*
|
|
* Generated keys should be self-documenting once one understands how they are constructed.
|
|
*
|
|
* Each method that returns a status requires an input parameter to be passed which represents an array error
|
|
* stack. If an error was raised during the method, it is the responsibility of the calling stub to validate
|
|
* the operation by parsing this ephemeral error stack variable.
|
|
*
|
|
*
|
|
* METHOD CATALOG:
|
|
* ---------------
|
|
* __construct: primary method, connect to memcached server, general initialization
|
|
* auto_connect: helper method: parses server list from configuration
|
|
* add_server: helper method: adds servers to memcache server pool
|
|
* add: primary method: adds new entry to cache or replaces if already exists
|
|
* set: primary method: identical to add() but replace is implicit
|
|
* get: primary method: fetch from cache method
|
|
* getByKey: primary method: fetches from cache based only on the passed key
|
|
* getAllKeys: helper method: returns array-list of all keys currently in cache
|
|
* delete: primary method: permanently deletes item (by key) from cache
|
|
* replace: primary method: replaces existing cached item with new item
|
|
* makeKey: primary method: build the cache storage key
|
|
* singleton: primary method: entry point for class instantiation
|
|
* dumpErrors: helper method: returns current error catalog as block of formatted text
|
|
* errorReset: helper method: resets the error stack with option to save to log
|
|
* getStats: helper method: queries cache server and returns cache stats
|
|
* flush_cache: helper method: deletes all items from cache
|
|
* __clone: helper method: prevents cloning of the singleton class
|
|
* __destruct: helper method: release class resources
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
* 03-02-18 mks CORE-680: deprecated trace logging
|
|
* 08-02-18 mks CORE-774: PHP7.2 Exception Handling
|
|
* 01-08-20 mks DB-150: PHP7.4 member variable type-casting
|
|
*
|
|
*/
|
|
class gasCache
|
|
{
|
|
private static ?gasCache $instance = null; // pointer to self for instantiation
|
|
private static Memcached $mco; // connector to memcache
|
|
private static string $class; // this class so we don't have to keep calling get_class()
|
|
private static bool $debug; // copied from global debug setting
|
|
private static array $config;
|
|
private static bool $isCluster;
|
|
public static ?array $errors;
|
|
public static bool $available; // indicates if the memcache servers are available
|
|
public static string $resultMessage; // result message array from memcached operation
|
|
public static int $resultCode;
|
|
public static int $cacheTTL; // make the default time-to-live publicly available
|
|
private static gacErrorLogger $logger; // Error Logger instantiation container
|
|
private static string $res = 'MEMC: '; // console resource id
|
|
|
|
/**
|
|
* __construct() -- private function for singleton class instantiation
|
|
*
|
|
* the constructor requires that the global config object be available so that we can read the memcache
|
|
* configuration... and that the global error object be available so that we can log errors. If either
|
|
* is unavailable, log messages to the console and (sic) the error stack and exit.
|
|
*
|
|
* Once the configuration is read, attempt to connect to the memcache server(s)...if we do not connect,
|
|
* generate a FATAL and exit.
|
|
*
|
|
* Read in the server list (support for memcache cluster, yes) and add these servers to the memcache
|
|
* service. as long as we can connect to at least one server, we're good - otherwise, generate a
|
|
* FATAL message and exit.
|
|
*
|
|
* Depending on the ultimate status of the object, output a status message to the console and return.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
* 02-12-19 mks DB-116: cache the template cacheMaps into memcached
|
|
*
|
|
*/
|
|
private function __construct()
|
|
{
|
|
register_shutdown_function(array($this, '__destruct'));
|
|
|
|
try {
|
|
static::$logger = new gacErrorLogger();
|
|
} catch (Throwable $t) {
|
|
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
|
|
static::$errors[] = $msg;
|
|
if (isset(static::$logger) and static::$logger->available)
|
|
static::$logger->error($msg);
|
|
else
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
return false;
|
|
}
|
|
if (!is_array(gasConfig::$settings[CONFIG_CACHE])) {
|
|
static::$logger->error(ERROR_CONFIG_RESOURCE_404 . CONFIG_CACHE);
|
|
exit(0);
|
|
}
|
|
// get configuration and initialize locals
|
|
static::$debug = gasConfig::$settings[CONFIG_DEBUG];
|
|
static::$class = get_class($this);
|
|
static::$available = true;
|
|
static::$config = gasConfig::$settings[CONFIG_CACHE];
|
|
static::$isCluster = intval(static::$config[CONFIG_CACHE_IS_CLUSTER]);
|
|
try {
|
|
static::$mco = new Memcached(static::$config[CONFIG_CACHE_SERVER][CONFIG_CACHE_SERVER_PERSISTENT_ID]);
|
|
static::$mco->setOption(Memcached::OPT_BINARY_PROTOCOL, false);
|
|
static::$cacheTTL = static::$config[CONFIG_CACHE_EXPIRES];
|
|
static::$resultMessage = static::$mco->getResultMessage();
|
|
if (!static::auto_connect()) {
|
|
static::$logger->error(ERROR_CACHE_RESOURCE_404);
|
|
static::$errors[] = ERROR_CACHE_RESOURCE_404;
|
|
static::$available = false;
|
|
}
|
|
} catch (Throwable $t) {
|
|
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
|
|
static::$errors[] = $msg;
|
|
if (isset(static::$logger) and static::$logger->available)
|
|
static::$logger->error($msg);
|
|
else
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
static::$available = false;
|
|
}
|
|
// load the lookup-grid for the templates into memcached
|
|
if (!static::loadCacheMaps()) {
|
|
static::$logger->fatal(FAIL_CACHE_MAP_LOAD);
|
|
consoleLog(static::$res, CON_ERROR, FAIL_CACHE_MAP_LOAD);
|
|
}
|
|
// output state message to console dependant on if we successfully added a memcache server
|
|
if (isset($_SERVER['SHELL'])) {
|
|
if (static::$available) {
|
|
if (static::$debug) {
|
|
static::$logger->info(SUCCESS_CONNECT_CACHE);
|
|
consoleLog(static::$res, CON_SUCCESS, SUCCESS_CONNECT_CACHE . PHP_EOL);
|
|
}
|
|
} else {
|
|
consoleLog(static::$res, CON_ERROR, 'memcache reports no servers available');
|
|
}
|
|
}
|
|
return static::$available;
|
|
}
|
|
|
|
|
|
/**
|
|
* auto_connect() -- private method
|
|
*
|
|
* attempts to connect to each of the servers in the configuration file.
|
|
*
|
|
* posts status of each connection attempt to the log and adds an error to the error stack if a
|
|
* connection failed.
|
|
*
|
|
* returns a boolean indicating if ANY servers were successfully connected.
|
|
*
|
|
* NOTE:
|
|
* -----
|
|
* commented-out code is to support cache pools... if we're using cache pools/clusters, then update this code.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
private static function auto_connect(): bool
|
|
{
|
|
$connected = true;
|
|
// foreach(static::$config[CONFIG_CACHE_SERVER] as $key => $server) {
|
|
if (!static::add_server(gasConfig::$settings[CONFIG_CACHE][CONFIG_CACHE_SERVER])) {
|
|
static::$logger->warn(ERROR_CACHE_RESOURCE_404);
|
|
static::$errors[] = ERROR_CACHE_RESOURCE_404;
|
|
$connected = false;
|
|
}
|
|
// }
|
|
return($connected);
|
|
}
|
|
|
|
|
|
/**
|
|
* add_server() -- private method
|
|
*
|
|
* takes a single unit of memcache server configuration information and attempts to add that server to the
|
|
* memcache pool.
|
|
*
|
|
* input parameter is an array block of configuration information (../config/memcached.php)
|
|
*
|
|
* return value is a boolean indicated if we successfully added the named server to the memcache pool.
|
|
*
|
|
* Note:
|
|
* -----
|
|
* At this time of creation, pooled servers were not configured. However, the code is supporting them so, if
|
|
* GA is using a pool of servers, need to take a devOps break and get info about server instance weights.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param $_server
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
private static function add_server($_server): bool
|
|
{
|
|
$hostName = null;
|
|
$portNum = null;
|
|
$weight = null;
|
|
extract($_server);
|
|
try {
|
|
if (static::$isCluster) {
|
|
/** @noinspection PhpUndefinedClassConstantInspection */
|
|
static::$mco->setOption(Memcached::OPT_CLIENT_MODE, Memcached::DYNAMIC_CLIENT_MODE);
|
|
}
|
|
return (static::$mco->addServer($hostName, $portNum, $weight));
|
|
} catch (Throwable $t) {
|
|
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
|
|
static::$errors[] = $msg;
|
|
if (isset(static::$logger) and static::$logger->available)
|
|
static::$logger->error($msg);
|
|
else
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
static::$available = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* fetchTemplateCacheMap() -- public static method
|
|
*
|
|
* This method is used to fetch the cacheMap vector for a specific $_template. The name of the template is
|
|
* the first parameter required. Template names should be in the form of gat{template_name}.
|
|
*
|
|
* The second parameter is optional: $_map, if overridden to true, will return the entire cacheMap instead of
|
|
* just the template specified in the first parameter.
|
|
*
|
|
* The method uses the locally instantiated $mco cache reference to directly query cache for the cacheMap. On
|
|
* successful retrieval, we return the vector of data referenced in the array by the key: $_template. If the
|
|
* key exists, we return that record back to the calling client.
|
|
*
|
|
* In all other cases, if an error is trapped, or if we're not able to fetch the template vector, the method
|
|
* will return a null value.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param null|string $_template - name of the template to fetch (format: "gat"{templateName})
|
|
* @param bool $_map - defaults to false, if true - returns the entire cacheMap instead
|
|
* @return array|null - returns the data record on success, null on fail
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 02-14-19 mks DB-116: original coding
|
|
* 02-25-19 mks DB-116: added second (and optional) input param to return entire cacheMap and fixed warning
|
|
* being generated if there was no cacheMap in cache; now returns null immediately
|
|
* 07-27-20 mks DB-156: refactored making template optional as a param, better error handling
|
|
*
|
|
*/
|
|
public static function fetchTemplateCacheMap(?string $_template = null, bool $_map = false): ?array
|
|
{
|
|
$msg = '';
|
|
$hdr = '';
|
|
try {
|
|
// first, fetch the cacheMap from cache
|
|
$cacheMap = static::$mco->get(CACHE_MAP);
|
|
if (is_null($cacheMap)) {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$msg = ERROR_CACHE_FETCH_FAIL . CACHE_MAP;
|
|
} elseif (is_null($cacheMap = json_decode($cacheMap, true))) {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$msg = ERROR_JSON_DECODE_FAIL;
|
|
}
|
|
if (strlen($msg)) {
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
static::$logger->warn($hdr . $msg);
|
|
return null;
|
|
}
|
|
} catch (Throwable $t) {
|
|
$msg = ERROR_CACHE_FETCH_FAIL . CACHE_MAP;
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
static::$logger->warn($msg);
|
|
return null;
|
|
}
|
|
// if we've request the entire cacheMap, return it...or if the cacheMap is null, return null
|
|
if ($_map) return $cacheMap;
|
|
// ...otherwise, just return the vector identified by $_template
|
|
if (array_key_exists($_template, $cacheMap)) {
|
|
return $cacheMap[$_template];
|
|
} else {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$msg = sprintf(ERROR_CACHE_MAP_KEY_404, $_template);
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
static::$logger->warn($hdr . $msg);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* mapIncomingData() -- public static method
|
|
*
|
|
* The purpose of this method is to perform the cacheMapping on all incoming data payloads to the Namaste
|
|
* service. Note that the invoking method already makes the check for AppServer so we don't have to. ;)
|
|
*
|
|
* The following input parameters are expected:
|
|
*
|
|
* $_data -- this is a call-by-reference value and should be the BROKER_DATA part of the $request payload
|
|
* $_map -- this is the data-template vector pulled from the global cacheMap container and contains all necessary
|
|
* data about the current class being cache-mapped.
|
|
* $_msg -- call-by-reference array acting as an error container for the invoking client
|
|
*
|
|
* Basically, this method gets the $request[BROKER_DATA] part of the payload. For all events, this payload will
|
|
* either be an associative array of data containers, each with a designated purpose, each requiring cacheMapping.
|
|
* If the array received in an indexed array, then the request is to create one, or more, new records and each
|
|
* tuple in the incoming indexed array is potentially a new record.
|
|
*
|
|
* If $_data is an indexed array, we'll handle that as it's own event by passing the entire array down to the
|
|
* cacheMapping function.
|
|
*
|
|
* Otherwise, we'll quality the keys of the associative array and well pass the arrays they reference down to the
|
|
* same cacheMapping routine we used for the create event.
|
|
*
|
|
* For both cases, the targeted (sub)array is replaced in it's entirety by the new cacheMapped array. However, we're
|
|
* not going to return the array in exactly the same format.
|
|
*
|
|
* We're going to create two sub-containers at the top level of the array keyed under the constants STATUS_VALID
|
|
* ('valid') and STATUS_INVALID ('invalid') respectively. If no fields were successfully cacheMapped, then
|
|
* the STATUS_VALID sub-container will be set to null. Otherwise, the container will be an array of the cacheMapped
|
|
* data. If any field failed to be cacheMapped, that field will be returned under the STATUS_INVALID container
|
|
* label. It is entirely the parent-processes' responsibility to mitigate any invalid fields.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_data
|
|
* @param array $_map
|
|
* @param array $_msg
|
|
* @return bool
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 02-15-19 mks DB-116: original coding completed
|
|
* 04-21-20 mks ECI-107: updated for sub-collection fetch
|
|
*
|
|
*/
|
|
public static function mapIncomingData(array &$_data, array $_map, array &$_msg): bool
|
|
{
|
|
if (array_key_first($_data) === 0) {
|
|
// if the array is indexed...
|
|
try {
|
|
if (is_null($data = static::cacheMapIndexedArray($_data, $_map, false, $_msg))) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . STRING_INCOMING_DATA;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
$_data = $data;
|
|
} catch (TypeError $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . STRING_INCOMING_DATA;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
} else {
|
|
// ...else the array is associative - we want to process all the top level array keys and call the
|
|
// appropriate processing method (hint: STRING_RETURN_DATA is indexed)
|
|
$records = null;
|
|
foreach ($_data as $task => $value) {
|
|
switch ($task) {
|
|
case STRING_QUERY_DATA : // CRUD(fetch) event
|
|
case STRING_SORT_DATA : // CRUD(fetch) event
|
|
case STRING_UPDATE_DATA : // CRUD(update) event
|
|
case STRING_ORDER_BY_DATA : // Order-by directive
|
|
// edge case: a QUERY_DATA event with a null value is permissible
|
|
if ($task == STRING_QUERY_DATA and is_null($value)) return true;
|
|
// for all other situations, convert...
|
|
try {
|
|
if (is_null($data = static::cacheMapAssociativeArray($value, $_map, $_msg))) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . $task;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
$_data[$task] = $data;
|
|
} catch (TypeError $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . $task;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
break;
|
|
case STRING_DATA : // sub-Collection create event
|
|
try {
|
|
if (is_null($data = static::cacheMapIndexedArray($value, $_map, false, $_msg))) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . $task;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
$_data[$task] = $data;
|
|
} catch (TypeError $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . $task;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
break;
|
|
case STRING_RETURN_DATA :
|
|
try {
|
|
if (is_null($data = static::cacheMapIndexedArray($value, $_map, true, $_msg))) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . $task;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
$_data[$task] = $data;
|
|
} catch (TypeError $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MAP_FAIL . $task;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
break;
|
|
// sub-collection event GUIDs
|
|
case STRING_GUID_KEY :
|
|
case STRING_SUBC_GUID :
|
|
// if a guid-key field is present, it should contain a string value that is a valid guid
|
|
if (!validateGUID($value)) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_INVALID_GUID . $value;
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
break;
|
|
case STRING_SUBC_FIELD :
|
|
// subC field has to be present in the cacheMap
|
|
if ($_map[CACHE_SUBC] == STRING_NOT_DEFINED or !in_array($value, $_map[CACHE_MAP])) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = sprintf(ERROR_SUBC_KEY_404, $value);
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
} else {
|
|
// $oldField = $task;
|
|
$mappedColumnName = array_search($value, $_map[CACHE_MAP]);
|
|
// validate the cacheMapped col name against the CACHE_SUBC keys ensuring it's a subC field
|
|
if (!array_key_exists($mappedColumnName, $_map[CACHE_SUBC])) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = sprintf(ERROR_SUBC_RECORD_NO_KEY, $mappedColumnName);
|
|
$_msg[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
$_data[STRING_SUBC_FIELD] = $mappedColumnName;
|
|
}
|
|
break;
|
|
case STRING_QUERY_OPTIONS :
|
|
// this is used for the multi => true field, so let's just return true
|
|
case STRING_SUBC_COL :
|
|
case STRING_SUBC_DATA :
|
|
// keys for sub-collection fetch; just return true
|
|
return true;
|
|
break;
|
|
default:
|
|
// error -- unknown label/index in array payload
|
|
$msg = sprintf(ERROR_UNKNOWN_KEY, $task, BROKER_DATA);
|
|
$_msg[] = $msg;
|
|
static::$logger->data($msg);
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// // todo -- test that the unset won't undo all the other changes made to the $_data payload
|
|
// if (isset($oldField) and array_key_exists($oldField, $_data[STRING_SUBC_FIELD]))
|
|
// unset($_data[STRING_SUBC_FIELD][$oldField]);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* mapOutboundPayload() -- public static method
|
|
*
|
|
* This method required the following input parameters:
|
|
*
|
|
* $_obj -- call-by-reference "copy" of the class object
|
|
* $_errs - call-by-reference error catalog
|
|
*
|
|
* If, during processing, there is a failure, we'll record an error associated with the failure and we'll return
|
|
* immediately. Because we only modify the $data member on complete success, the contents of $data remain
|
|
* untouched and can be returned back to the calling client.
|
|
*
|
|
* If the current class object supports caching, then we're going to fetch the cacheMap from cache and we'll
|
|
* use the cacheMap to convert the records currently stored in $data to the cacheMapped payload. (obfuscation)
|
|
*
|
|
* If we return successfully from the cacheMapping, we'll then spin through the payload and slice out chunks
|
|
* of the record payload in sizes of $queryRecordLimit and cache those records under a single key, and we will
|
|
* replace the contents of $data with the list of cacheKeys (1 key per every $queryRecordLimit records).
|
|
*
|
|
* The method returns a boolean value back to the calling client indicating if the cache operation completed
|
|
* successfully or not. If so, then as previously stated, the cache-key catalog will have replaced the
|
|
* contents of the $data member as an indexed array of string values.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param object $_obj
|
|
* @param array $_errs
|
|
* @return bool|null
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 03-05-19 mks DB-116: original coding completed
|
|
* 05-29-19 mks DB-122: added support for META_DONUT_FILTER, restricted retVal to bool only
|
|
* 06-27-19 mks DB-34: one last check to remove any records from being cached that have status = deleted
|
|
* 11-05-20 mks DB-171:
|
|
*
|
|
*/
|
|
public static function mapOutboundPayload(object &$_obj, array &$_errs): bool
|
|
{
|
|
$cacheKeys = [];
|
|
$vector = null;
|
|
$recordLimit = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_RECORD_LIMIT];
|
|
// support for META_DO_CACHE -- if set, and set to false, then we're not caching the return payload
|
|
/** @var gacMongoDB | gacPDO $_obj */
|
|
$md = $_obj->getMetaDataPayload();
|
|
// if META_DONUT_FILTER is set, and is set to true, then do NOT filter (cachemap) the return payload
|
|
$doFilter = (isset($md[META_DONUT_FILTER]) and boolval($md[META_DONUT_FILTER])) ? false : (gasConfig::$settings[ENV_APPSERVER][CONFIG_IS_LOCAL] === 1);
|
|
$doCache = (isset($md[META_DO_CACHE])) ? boolval($md[META_DO_CACHE]) : $_obj->useCache;
|
|
// start the cache-mapping...
|
|
try {
|
|
// make a copy of the data for the conversion process
|
|
if (is_null($newData = $_obj->getData())) {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$msg = ERROR_NO_DATA;
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
return false;
|
|
}
|
|
// Map the payload if appServer ... this can be overridden only if META_DONUT_FILTER is set.
|
|
if ($doFilter) {
|
|
// grab the template map
|
|
if (is_null($vector = gasCache::fetchTemplateCacheMap(($md[META_TLTI] . $md[META_TEMPLATE])))) {
|
|
$_errs[] = ERROR_CACHE_MAP_404;
|
|
return false;
|
|
}
|
|
if (is_null($newData = static::cacheMapIndexedArray($newData, $vector, false, $_errs))) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = $hdr . ERROR_CACHE_MAP_FAIL . $_obj->class . ' ' . STRING_DATA;
|
|
$_errs[] = $msg;
|
|
static::$logger->data($msg);
|
|
return false;
|
|
}
|
|
// save the cache-mapped data to the object $data member
|
|
$_obj->replaceData($newData);
|
|
}
|
|
} catch (TypeError $t) {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$_errs[] = ERROR_TYPE_EXCEPTION;
|
|
@handleExceptionMessaging($hdr, $t->getMessage(), $foo, true);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* here's the explanation about caching and cache-mapping...
|
|
*
|
|
* Each class has the ability to toggle caching -- which means that data returned to the client is cached
|
|
* if this setting is set to true. If set to false, the return data payload contains the actual data and not
|
|
* a cache key pointing to the data.
|
|
*
|
|
* This is not the same as cache-mapping. If this code is exec'ing on APPSERVER, then all return payloads
|
|
* are mapped so as to enforce schema obfuscation.
|
|
*
|
|
* The mapping can be overridden by use of the META_DONUT_FILTER which explicitly disables filtering. The
|
|
* return payload can still, however, be cached.
|
|
*
|
|
* THIS IS THE ONLY SETTING THAT CAN OVERRIDE DATA FILTERING ON APPSERVER!
|
|
*
|
|
* Other namaste services (Admin, Segundo, Tercero) do not, by design, cache-map return data payloads.
|
|
*
|
|
*/
|
|
|
|
// if doCache is true, then cache the data payload
|
|
if ($doCache) {
|
|
try {
|
|
// we have more than maxRecordLimit records in the payload so loop through the list
|
|
$numRecs = count($newData);
|
|
for ($index = 1, $iteration = 1; $index <= $numRecs; $index+=$recordLimit, $iteration++) {
|
|
$newSlice = array_slice($newData, ($index - 1), $recordLimit);
|
|
if (empty($newSlice)) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_DATA_SLICE . $iteration;
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
// remove any slices previously cached
|
|
static::deleteSlices($cacheKeys);
|
|
return false;
|
|
} else {
|
|
// DB-34: remove any record that has a deleted status just in-case one slipped through...
|
|
$status = array_column($newSlice, CM_TST_FIELD_TEST_STATUS);
|
|
if ($status[0] == STATUS_DELETED) continue;
|
|
$keyList = array_keys($status, STATUS_DELETED);
|
|
if (!empty($keyList))
|
|
foreach ($keyList as $k)
|
|
unset($newSlice[$k]);
|
|
/*
|
|
* if there is only one record in the return payload, then we'll cache that record under
|
|
* it's own guid -- otherwise, generate a new guid under which to cache all the records.
|
|
*/
|
|
$newGUID = (count($newSlice) == 1) ? $newSlice[0][CM_TST_TOKEN] : guid();
|
|
$newSlice = jsonHandler($newSlice, JSON_DIR_ENC, $_errs, true);
|
|
$cacheTime = (isset($vector[CACHE_TIMER]) and is_numeric($vector[CACHE_TIMER])) ? intval($vector[CACHE_TIMER]) : gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_RECORD_LIMIT];
|
|
$newKey = static::add($newGUID, gzcompress($newSlice), $cacheTime);
|
|
if (empty($newSlice)) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_DATA_SLICE . $iteration;
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
// remove any slices previously cached
|
|
static::deleteSlices($cacheKeys);
|
|
return false;
|
|
}
|
|
if (is_null($newKey)) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = $hdr . ERROR_CACHE_ADD_FAIL . $newGUID . COLON . STRING_CLASS . COLON . $_obj->class;
|
|
$_errs[] = $msg;
|
|
static::$logger->warn($msg);
|
|
// remove previously cached slices
|
|
static::deleteSlices($cacheKeys);
|
|
return false;
|
|
}
|
|
}
|
|
$cacheKeys[] = $newKey;
|
|
}
|
|
$_obj->replaceData($cacheKeys);
|
|
} catch (TypeError | Throwable $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$_errs[] = ERROR_TYPE_EXCEPTION;
|
|
@handleExceptionMessaging($hdr, $t->getMessage(), $foo, true);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* add() -- public method
|
|
*
|
|
* this is method which adds new information (data) to memcache.
|
|
*
|
|
* input parameters are a well-formed key, the data-payload to cache (pre-compressed(json)) and an optional
|
|
* expire parameter (in seconds) to keep the data in-cache.
|
|
*
|
|
* if the add fails and we're using Memcached, then the method will attempt to replace the data, assuming that
|
|
* the fail-return-code was one that indicated that the data already existed. (The add method will fail if the
|
|
* object key already exists.)
|
|
*
|
|
* the method returns the cache-key on success and a null on anything else.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_key - the key should be the class name, underscore character, the the idHash key of the record
|
|
* @param string $_value - the value is a compressed(json) string object
|
|
* @param int $_expires - time to expire from cache in seconds
|
|
* @return null | string - indicates successful addition to cache
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
* 07-24-17 mks refactored to return either the cache-key or a null
|
|
*
|
|
*/
|
|
public static function add(string $_key = null, string $_value = null, int $_expires = NUMBER_CACHE_DEFAULT): ?string
|
|
{
|
|
if (!isset($_expires)) {
|
|
$_expires = static::$config[CONFIG_CACHE_EXPIRES];
|
|
}
|
|
if (empty($_key)) {
|
|
$msg = ERROR_PARAM_404 . UDASH . STRING_KEY;
|
|
static::$errors[] = $msg;
|
|
static::$logger->error($msg);
|
|
return(null);
|
|
}
|
|
if (empty($_value)) {
|
|
$msg = ERROR_PARAM_404 . UDASH . STRING_VALUE;
|
|
static::$errors[] = $msg;
|
|
static::$logger->error($msg);
|
|
return(null);
|
|
}
|
|
/** @var $mc Memcached() */
|
|
$mc = static::$mco;
|
|
// add a checksum to the payload if it's not a system key
|
|
static::addChecksum($_value);
|
|
$key = static::makeKey($_key);
|
|
$mc->set($key, $_value, $_expires);
|
|
static::$resultCode = $mc->getResultCode();
|
|
static::$resultMessage = $mc->getResultMessage();
|
|
if (static::$resultCode != 47 and static::$resultCode != 0) {
|
|
static::$logger->warn(ERROR_STUB_SET_MEMCACHED . static::$resultCode);
|
|
static::$logger->warn(ERROR_STUB_RESULT_CODE . static::$resultMessage);
|
|
} else {
|
|
static::$resultCode = 0;
|
|
}
|
|
return((static::$resultCode) ? null : $key);
|
|
}
|
|
|
|
|
|
/**
|
|
* sysAdd() -- public static method
|
|
*
|
|
* this method should only be called for use in system functions. the function requires two parameters:
|
|
*
|
|
* $_key: the key string (should be a guid)
|
|
* $_value: the guid string
|
|
*
|
|
* We're not validating or processing the inputs, just adding them to cache with a 0-expiry.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_key
|
|
* @param $_value -- can be varying types (string, array, etc.)
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 08-21-17 mks CORE-500: original coding
|
|
* 12-06-17 mks CORE-591: removed secondary reference to cache resource object and trace message
|
|
* changed param-2 to type mixed from string
|
|
*
|
|
*/
|
|
public static function sysAdd(string $_key, $_value): bool
|
|
{
|
|
static::$mco->set($_key, $_value, 0);
|
|
static::$resultCode = static::$mco->getResultCode();
|
|
static::$resultMessage = static::$mco->getResultMessage();
|
|
if (static::$resultCode != 47 and static::$resultCode != 0) {
|
|
static::$logger->warn(ERROR_STUB_SET_MEMCACHED . static::$resultCode);
|
|
static::$logger->warn(ERROR_STUB_RESULT_CODE . static::$resultMessage);
|
|
return(false);
|
|
} else {
|
|
return(true);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* sysGet() -- public static function
|
|
*
|
|
* this is another system-level function that's meant to be used sparingly for namaste/system-level storage.
|
|
* As such, there's not validation or processing on this, or the other system, function. We assume you know
|
|
* what you're doing and will handle errors appropriately.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param $_key
|
|
* @return string | array | null
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 08-21-17 mks CORE-500: original coding
|
|
* 12-06-17 mks CORE-591: removed secondary reference to resource object and trace message
|
|
* PHP7 function typedef headers
|
|
*
|
|
*/
|
|
public static function sysGet(string $_key)
|
|
{
|
|
return(static::$mco->get($_key));
|
|
}
|
|
|
|
|
|
/**
|
|
* sysDel() -- public static method
|
|
*
|
|
* the last of the system-level cache handlers, this function deletes a named key from cache. As a system
|
|
* function, we're not validating or processing input nor are we interested in the return code. Either it
|
|
* deletes the key, or it does not.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param $_key
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 08-21-17 mks CORE-500: original coding
|
|
* 12-06-17 mks CORE-591: removed secondary reference to resource object and trace message
|
|
* PHP7 function typedef headers
|
|
*
|
|
*/
|
|
public static function sysDel(string $_key): void
|
|
{
|
|
static::$mco->delete($_key);
|
|
}
|
|
|
|
|
|
/**
|
|
* addCheckSum() -- private static method
|
|
*
|
|
* this method will calculate the md5-checksum of a cache-payload ($_value) and will then
|
|
* decode and store the checksum internally to the payload under the index "checksum".
|
|
*
|
|
* the input parameter is the array payload - in gzip'd, json-encoded format. Once decoded,
|
|
* if the $_value is an array, then store the checksum as a new indexed-array value and re-assemble
|
|
* the payload.
|
|
*
|
|
* since the input parameter is passed by reference, the new payload is implicitly returned.
|
|
*
|
|
* todo return a success/fail code
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param $_value
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
private static function addChecksum(string &$_value)
|
|
{
|
|
$aryData = json_decode(gzuncompress($_value), true);
|
|
if (is_array($aryData)) {
|
|
$checksum = md5($_value);
|
|
$aryData[STRING_CHECKSUM] = $checksum;
|
|
$_value = gzcompress(json_encode($aryData));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* validateChecksum() -- public static method
|
|
*
|
|
* this method requires three input parameters described as follows:
|
|
*
|
|
* $_data -- this is the unpacked data array presumably just fetched from cache
|
|
* $_checksum -- this is the checksum value that was previously extracted from the $_data
|
|
* $_errors -- array container implicitly returned with diagnostics
|
|
*
|
|
* This method is meant to take an array payload that was just fetched from cache, was unpacked and the checksum
|
|
* extracted (as evidenced by the input parameters) and passed to this method so that we can validate the calculated
|
|
* checksum.
|
|
*
|
|
* We're going to see if the array still contains the checksum index in case the client forgot to remove it and,
|
|
* if so, we'll do a validation check to ensure we're working with the correct array. If we are, then remove
|
|
* the checksum from the array container.
|
|
*
|
|
* Next calculate the checksum on the dynamically-repacked array and compare that value against the passed
|
|
* checksum parameter to ensure equivalency and generate the appropriate response.
|
|
*
|
|
* The return value, the boolean, indicates if the checksum correctly matched (true) -- anything else will
|
|
* return a boolean(false) value to the calling client.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_data
|
|
* @param string $_checksum
|
|
* @param array $_errors
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 09-20-17 mks CORE-562: original coding
|
|
*
|
|
*/
|
|
public static function validateChecksum(array $_data, string $_checksum, array &$_errors): bool
|
|
{
|
|
if (array_key_exists(STRING_CHECKSUM, $_data) and $_data[STRING_CHECKSUM] != $_checksum) {
|
|
$_errors[] = ERROR_CACHE_CKSUM_DATA;
|
|
if (static::$debug) static::$logger->debug(ERROR_CACHE_CKSUM_DATA);
|
|
return(false);
|
|
} elseif (array_key_exists(STRING_CHECKSUM, $_data)) {
|
|
unset($_data[STRING_CHECKSUM]);
|
|
}
|
|
$cs = md5(gzcompress(json_encode($_data)));
|
|
if ($cs !== $_checksum) {
|
|
$msg = sprintf(ERROR_CACHE_CKSUM_MISMATCH, $_checksum, $cs);
|
|
$_errors[] = $msg;
|
|
if (static::$debug) static::$logger->debug($msg);
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
|
|
/**
|
|
* get() -- public method
|
|
*
|
|
* this is the method responsible for obtaining information from memcache based on the provided key.
|
|
*
|
|
* method will either return the data stored in-cache, or a boolean false indicating that the data was not found.
|
|
*
|
|
* this method should be checked before most database reads where the key in already known.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_key
|
|
* @return null|string
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
* 07-31-17 mks refactored for explicit data typing
|
|
* 08-07-17 mks CORE-497: return explicit null if get() fails
|
|
*
|
|
*/
|
|
public static function get(string $_key = null): ?string
|
|
{
|
|
$data = null;
|
|
if (static::$mco) {
|
|
if (empty($_key)) {
|
|
$msg = ERROR_PARAM_404 . UDASH . STRING_KEY;
|
|
static::$logger->error($msg);
|
|
static::$errors[] = $msg;
|
|
return(null);
|
|
}
|
|
if (static::$debug) {
|
|
static::$logger->debug('Memcache: Get(' . static::makeKey($_key) . ')');
|
|
}
|
|
try {
|
|
$key = static::makeKey($_key);
|
|
$data = static::$mco->get($key);
|
|
} catch (TypeError $t) {
|
|
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
|
|
static::$errors[] = $msg;
|
|
if (isset(static::$logger) and static::$logger->available)
|
|
static::$logger->error($msg);
|
|
else
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
}
|
|
return( (empty($data) ? null : $data) );
|
|
}
|
|
static::$errors[] = ERROR_CACHE_RESOURCE_404;
|
|
static::$logger->warn(ERROR_CACHE_RESOURCE_404);
|
|
return(null);
|
|
}
|
|
|
|
|
|
/**
|
|
* isCached() -- static public method
|
|
*
|
|
* This method checks to see if a record has been cached by it's guid value -- which is the first input parameter
|
|
* to the method. The second is a call-by-reference array that will allow us to return diagnostic messages to the
|
|
* calling client.
|
|
*
|
|
* The method validates the $_key as a guid and then attempts to get the record from cache. If successful, we
|
|
* return the cache-key back to the client. Otherwise a null is returned.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_key
|
|
* @param array $_errors
|
|
* @return null|string
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 09-28-17 mks CORE-572: original coding
|
|
*
|
|
*/
|
|
public static function isCached(string $_key, array &$_errors): ?string
|
|
{
|
|
if (static::$mco) {
|
|
if (!validateGUID($_key)) {
|
|
$msg = ERROR_INVALID_GUID . $_key;
|
|
static::$logger->data($msg);
|
|
$_errors[] = $msg;
|
|
return null;
|
|
}
|
|
return (!is_null(static::get($_key)) ? static::makeKey($_key) : null);
|
|
}
|
|
$_errors[] = ERROR_CACHE_RESOURCE_404;
|
|
static::$logger->warn(ERROR_CACHE_RESOURCE_404);
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* getByKey() -- public method
|
|
*
|
|
* in some cases, we know all of the key and there's no need to build a key dynamically - this method provides
|
|
* fetch functionality without building a key by assuming that the calling stub passed in the entire key to
|
|
* the method.
|
|
*
|
|
* returns an error if no key was received or if we've lost the connection to the memcache instance.
|
|
*
|
|
* note that the returned data (if found in-cache) is returned unmodified from it's cached state.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
* @noinspection PhpUnused
|
|
*
|
|
* @param $_key - string containing the cache-key value and can be of any length
|
|
* @return bool - returns boolean false on error or a data structure of cached data
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks initial coding
|
|
*
|
|
*/
|
|
public static function getByKey($_key)
|
|
{
|
|
if (static::$mco) {
|
|
if (empty($_key)) {
|
|
$msg = ERROR_PARAM_404. UDASH . STRING_KEY;
|
|
static::$logger->error($msg);
|
|
static::$errors[] = $msg;
|
|
return(false);
|
|
}
|
|
return(static::$mco->get($_key));
|
|
}
|
|
static::$errors[] = ERROR_CACHE_RESOURCE_404;
|
|
static::$logger->warn(ERROR_CACHE_RESOURCE_404);
|
|
return(false);
|
|
}
|
|
|
|
|
|
/**
|
|
* getAllKeys() -- public static method
|
|
*
|
|
* helper method that returns all keys currently stored in-cache
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @return array
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks initial coding
|
|
*
|
|
*/
|
|
public static function getAllKeys():array
|
|
{
|
|
return(static::$mco->getAllKeys());
|
|
}
|
|
|
|
|
|
/**
|
|
* delete() -- public method
|
|
*
|
|
* method that removes (permanently) an item (accessed by the KEY), from memcache.
|
|
*
|
|
* if the object exists in-cache by key, then it will be removed from cache and, if this happens, the
|
|
* method will return a boolean(true)...otherwise, a boolean(false) should be returned.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param $_key
|
|
* @param int $_expires
|
|
* @param bool $_makeKey - decides whether or not to prepare the key before deleting the cache value
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
public static function delete($_key, $_expires = 0, $_makeKey = true)
|
|
{
|
|
if (empty($_key)) {
|
|
$msg = ERROR_PARAM_404 . UDASH . STRING_KEY;
|
|
static::$errors[] = $msg;
|
|
static::$logger->error($msg);
|
|
return(false);
|
|
}
|
|
if ($_makeKey) {
|
|
$key = static::makeKey($_key);
|
|
return(static::$mco->delete($key, $_expires));
|
|
} else {
|
|
return(static::$mco->delete($_key, $_expires));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* flushTokens() -- public static method
|
|
*
|
|
* This method is used to remove a list of cached records based on their cache-key/GUID value.
|
|
*
|
|
* The input parameter to the method is a list of records, each record being a k->v paired associative array
|
|
* containing the field name: token{_ext} and referencing a record's GUID value.
|
|
*
|
|
* The method will spin through the arrays and will make a call to the static::delete() method in this class
|
|
* for every token value found.
|
|
*
|
|
* Data validation is minimal -- any validation error will silently return a boolean-false value to the calling
|
|
* client without notification as to why the request failed.
|
|
*
|
|
* If the token list is successfully processed, a boolean true is returned which does not infer that any cached
|
|
* records were actually deleted (they may have all expired if they existed) but only that processing of the
|
|
* list completed successfully.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_list
|
|
* @return bool
|
|
* @noinspection PhpUnused
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 10-25-17 mks CORE-587: original coding
|
|
*
|
|
*/
|
|
public static function flushTokens(array $_list): bool
|
|
{
|
|
if (!is_array($_list)) return false;
|
|
// process the list of tokens
|
|
foreach ($_list as $record) {
|
|
if (count($record) > 1) return false;
|
|
foreach ($record as $key => $token) {
|
|
try {
|
|
@static::delete($token);
|
|
} catch (TypeError $t) {
|
|
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
|
|
static::$errors[] = $msg;
|
|
if (isset(static::$logger) and static::$logger->available)
|
|
static::$logger->error($msg);
|
|
else
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* makeKey() --- private static method
|
|
*
|
|
* method makes a key from the configuration and the passed key-part.
|
|
*
|
|
* this is a function so that we have only one point in the code where we're generating keys.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_part
|
|
* @return string
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
private static function makeKey(string $_part):string
|
|
{
|
|
$preKey = gasConfig::$settings[CONFIG_APPLICATION_ID][CONFIG_ID_ENV] . UDASH;
|
|
// $pos = strpos($_part, $preKey);
|
|
$key = (strpos($_part, $preKey) === false) ? $preKey . $_part : $_part;
|
|
if (static::$debug) {
|
|
static::$logger->debug(STRING_KEY . COLON . $key);
|
|
}
|
|
return($key);
|
|
}
|
|
|
|
|
|
/**
|
|
* buildMappedDataArray() -- private method
|
|
*
|
|
* this method recursively builds a cacheMap array. (schema generates cacheMap)
|
|
*
|
|
* the method accepts (requires) three input parameters:
|
|
*
|
|
* $_data -- the array of key-value paired associative array containing the tuple data
|
|
* $_map -- the key-value paired associative array containing the classes' cache-map build in instantiation
|
|
* $_ext -- the class extension
|
|
*
|
|
* spin through each row of $_data array as a set of key->values...
|
|
* for every key found in the map index,
|
|
* assign the map index reference value (map[$key]) to the tmpArray variable and assign the data[key] (value).
|
|
* ensure a token exists and side-save it...
|
|
*
|
|
* if any data was successfully mapped, then build the return array and overwrite $_data with this structure.
|
|
*
|
|
* otherwise, for all other cases, a null value is returned and the $_data remains unchanged.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param $_data -- call by reference parameter - returns array of cached-mapped data
|
|
* @param $_map -- the cache map
|
|
* @param $_ext -- the class extension
|
|
* @return array|null
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 07-14-17 mks original coding
|
|
* 01-04-21 mks DB-180: recycled from deprecated, updated for CONS processing where we're receiving a non-
|
|
* cache-mapped payload, this sub checks the cacheMap container values instead of the
|
|
* keys, for matching input data fields - also updated exception handling
|
|
*
|
|
*/
|
|
public static function buildMappedDataArray(array &$_data, array $_map, string $_ext): ?array
|
|
{
|
|
$mappedData = null;
|
|
$keyList = null;
|
|
$tmpArray = null;
|
|
if (is_array($_data) and array_key_exists(0, $_data)) {
|
|
for ($index = 0, $last = count($_data); $index < $last; $index++) {
|
|
if (is_array($_data[$index])) {
|
|
foreach ($_data[$index] as $key => $value) {
|
|
// if we encounter a nexted sub-collection, recursively call self to process
|
|
if (is_array($value) and is_numeric(key($value))) {
|
|
try {
|
|
$tmpArray = static::buildMappedDataArray($value, $_map, $_ext);
|
|
} catch (Throwable | TypeError $t) {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
@handleExceptionMessaging($hdr, $t->getMessage(), $foo, true);
|
|
return null;
|
|
}
|
|
}
|
|
// convert to cacheMap keys
|
|
if (false === ($newKey = array_search($key, $_map))) {
|
|
static::$logger->data(ERROR_KEY_404 . $value);
|
|
} else {
|
|
if (is_array($tmpArray)) {
|
|
$mappedData[$index][$newKey] = $tmpArray;
|
|
$tmpArray = null;
|
|
} else {
|
|
$mappedData[$index][$newKey] = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} elseif (is_array($_data)) {
|
|
foreach ($_data as $k => $v) {
|
|
if (array_key_exists($k, $_map)) {
|
|
$mappedData[$_map[$k]] = $v;
|
|
} elseif (array_key_exists(($k . $_ext), $_map)) {
|
|
$mappedData[$_map[($k . $_ext)]] = $v;
|
|
}
|
|
}
|
|
}
|
|
return($mappedData ?? null);
|
|
}
|
|
|
|
|
|
/**
|
|
* validateCacheKey() -- public static method
|
|
*
|
|
* this method validates a Namaste cache key which is in the format of:
|
|
*
|
|
* {currentEnvironment} + "_" + {guid}
|
|
*
|
|
* We validate the cache key by pulling the current environment value from the global configuration and seeing if
|
|
* that string exists within the key, which was passed as the input parameter. If the env string was not found
|
|
* at the beginning of the cache-key string, immediately return a false.
|
|
*
|
|
* Otherwise, invoke the general function: validateGUID by passing in the cacheKey with the env stripped out and
|
|
* pass the return from that method onto the calling client.
|
|
*
|
|
* Note: this does NOT validate system cache keys!
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_key
|
|
* @return bool|int
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 08-29-17 mks CORE-494: original coding (needed for unit testing)
|
|
*
|
|
*/
|
|
public static function validateCacheKey(string $_key)
|
|
{
|
|
$env = gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH;
|
|
return ((strpos($_key, $env) !== 0) ? false : validateGUID(ltrim($_key, $env)));
|
|
}
|
|
|
|
|
|
/**
|
|
* singleton() -- public method
|
|
*
|
|
* method to instantiate the singleton class (called externally)
|
|
*
|
|
* developer notes:
|
|
* ----------------
|
|
* it's the responsibility of the invoking stub to check for a non-null value in the class variable $instance.
|
|
* if you don't do this: bad things, man -- and it will all be on you.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks initial coding
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public static function singleton():self
|
|
{
|
|
if (static::$instance === null) {
|
|
$c = __CLASS__;
|
|
static::$instance = new $c();
|
|
}
|
|
return(static::$instance);
|
|
}
|
|
|
|
|
|
/**
|
|
* dumpErrors() -- public static method
|
|
*
|
|
* method to access the aggregated error messages within the class and return the errors as a single text block.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @return string
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 03-29-13 mks original coding
|
|
*
|
|
*/
|
|
public static function dumpErrors():string
|
|
{
|
|
global $eos;
|
|
$errorBlock = '';
|
|
if (count(static::$errors)) {
|
|
foreach (static::$errors as $key) {
|
|
$errorBlock .= $key . $eos;
|
|
}
|
|
}
|
|
return($errorBlock);
|
|
}
|
|
|
|
|
|
/**
|
|
* errorReset() -- public static method
|
|
*
|
|
* resets the error stack - requires explicit call
|
|
*
|
|
* input param (_safe) will first dump the existing error catalog to the log file before clearing the stack.
|
|
* (default for this is off, so must be explicitly invoked.)
|
|
* if _safe is invoked, then default will always (and should) be console dump.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
* @noinspection PhpUnused
|
|
*
|
|
* @param bool $_safe
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 03-29-13 mks original coding
|
|
*
|
|
*/
|
|
public static function errorReset(bool $_safe = false): void
|
|
{
|
|
if ($_safe) {
|
|
$errorDump = static::dumpErrors();
|
|
$errorDump = (is_null($errorDump)) ? SUCCESS_NO_ERRORS_FOUND : $errorDump;
|
|
static::$logger->debug($errorDump);
|
|
}
|
|
static::$errors = null;
|
|
}
|
|
|
|
/**
|
|
* get_stats() -- public static method
|
|
*
|
|
* this method returns the stats of the memcache server to the calling client.
|
|
* pid Process id of this server process
|
|
* uptime Number of seconds this server has been running
|
|
* time Current UNIX time according to the server
|
|
* version Version string of this server
|
|
* rusage_user Accumulated user time for this process
|
|
* rusage_system Accumulated system time for this process
|
|
* curr_items Current number of items stored by the server
|
|
* total_items Total number of items stored by this server ever since it started
|
|
* bytes Current number of bytes used by this server to store items
|
|
* curr_connections Number of open connections
|
|
* total_connections Total number of connections opened since the server started running
|
|
* connection_structures Number of connection structures allocated by the server
|
|
* cmd_get Cumulative number of retrieval requests
|
|
* cmd_set Cumulative number of storage requests
|
|
* get_hits Number of keys that have been requested and found present
|
|
* get_misses Number of items that have been requested and not found
|
|
* bytes_read Total number of bytes read by this server from network
|
|
* bytes_written Total number of bytes sent by this server to network
|
|
* limit_maxbytes Number of bytes this server is allowed to use for storage.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* HISTORY:
|
|
* --------
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
public static function get_stats(): ?array
|
|
{
|
|
return(static::$mco->getStats());
|
|
}
|
|
|
|
|
|
/**
|
|
* flush() -- public method
|
|
*
|
|
* this method invokes the memcache flush() which removes all existing items from cache.
|
|
*
|
|
* please don't call this method unless you absolutely need to nuke all of the data.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-09-17 mks original coding
|
|
*
|
|
*/
|
|
public static function flush_cache(): bool
|
|
{
|
|
return(static::$mco->flush());
|
|
}
|
|
|
|
|
|
/**
|
|
* smashCache() -- public static method
|
|
*
|
|
* This method is for mass-removing cached items. There are two input parameters to this method:
|
|
*
|
|
* $_listGUIDs - this is an indexed list of the cacheKeys or GUIDs that will be removed from cache
|
|
* $_errors - this is a call-by-reference container for error messages raised during processing
|
|
*
|
|
* We begin by setting the environment pre-key.
|
|
*
|
|
* Next, we check to see if the input array is associative - if not, we'll generate error messages, populate
|
|
* the $errors container and return a boolean(false) value back to the calling client.
|
|
*
|
|
* Next, we'll spin through all the keys in the list and check to see if we need to prepend the env onto the key
|
|
* creating a keyList in the process.
|
|
*
|
|
* Once that's completed, we'll pass the newly-created keyList to the memcached method deleteMulti().
|
|
*
|
|
* What's returned is an array of cacheKeys (keys) and values indicating the delete success or failure. For now,
|
|
* we really don't care about these values -- so we'll squash the return-payload and just return a Boolean(true).
|
|
*
|
|
* Programmer's Notes:
|
|
* -------------------
|
|
* The deleteMulti() method is incorrectly cast to return a Boolean value which is why the method (doc) reports
|
|
* three different return types and there's no return type specified in the function header
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_listGUIDs
|
|
* @param array $_errors
|
|
* @return bool
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 04-11-19 mks DB-116: original coding
|
|
*
|
|
*/
|
|
public static function smashCache(array $_listGUIDs, array &$_errors): bool
|
|
{
|
|
$preKey = gasConfig::$settings[CONFIG_APPLICATION_ID][CONFIG_ID_ENV] . UDASH;
|
|
if (array_key_first($_listGUIDs) !== 0) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_DATA_ARRAY_NOT_IDX . STRING_RECORD_GUIDS;
|
|
static::$logger->data($hdr . $msg);
|
|
$_errors[] = $msg;
|
|
return false;
|
|
}
|
|
$newList = null;
|
|
// if the keys are not prepped with the environment prepended, do so
|
|
foreach ($_listGUIDs as &$record)
|
|
$newList[] = (strpos($record, $preKey) === false) ? gasCache::makeKey($record) : $record;
|
|
// deleteMulti() is cast as returning a bool (even though it doesn't) so we'll do this in two lines
|
|
@static::$mco->deleteMulti($newList);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* cacheMash() -- static public method
|
|
*
|
|
* This method was written to support the ability to cache an entire array of items in a single command instead
|
|
* of onsies. There are three input parameters required:
|
|
*
|
|
* $_data -- this should be the processed $data content
|
|
* $_expires -- This is an optional value that otherwise defaults to the system limit (in seconds)
|
|
* $_errors -- This is a call-by-reference used as an error container
|
|
*
|
|
* When we're caching records, we'll cache an amount up the limit set in the XML config (queryRecordLimit) - if the
|
|
* amount of records to be cached is less than this, then we'll simply cache a single payload of the records and
|
|
* return the guid key back to the calling client. If the record count exceeds the max limit, then we'll cache
|
|
* payload bundles in the amount of queryRecordLimit and we'll return a list of keys back to the calling client.
|
|
*
|
|
* We will return an array on successful cache even if only one record was cached.
|
|
*
|
|
* If an error was raised during processing, we return a null value. The following can cause a null return:
|
|
*
|
|
* -- if the input array is empty
|
|
* -- if the first key of the $_data array does not pass the guid-validation test
|
|
* -- if the setMulti() memcached call fails
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_data
|
|
* @param int $_expires
|
|
* @param array $_errors
|
|
* @return null|array
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 04-11-19 mks original coding
|
|
*
|
|
*/
|
|
public static function cacheMash(array $_data, int $_expires = NUMBER_CACHE_DEFAULT, array &$_errors = []): ?array
|
|
{
|
|
if (empty($_data) or !is_array($_data)) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_PARAM_404 . UDASH . STRING_VALUE;
|
|
static::$errors[] = $msg;
|
|
static::$logger->error($hdr . $msg);
|
|
return null;
|
|
}
|
|
$newData = null;
|
|
$keyList = null;
|
|
// get the record limit for splitting the payload
|
|
$recLimit = intval(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_RECORD_LIMIT]);
|
|
if (count($_data <= $recLimit)) {
|
|
$keyList[] = static::makeKey(guid());
|
|
// $storedData = json_encode();
|
|
|
|
}
|
|
foreach ($_data as $record) {
|
|
foreach ($record as $key => $value) {
|
|
$newKey = static::makeKey($key);
|
|
$newData[$newKey] = $value;
|
|
$keyList[] = $key;
|
|
}
|
|
}
|
|
if (!static::$mco->setMulti($newData, $_expires)) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_CACHE_MASH_FAIL;
|
|
static::$logger->data($hdr . $msg);
|
|
$_errors[] = $msg;
|
|
return null;
|
|
}
|
|
return $keyList;
|
|
}
|
|
|
|
|
|
/**
|
|
* getCacheMap() -- public static method
|
|
*
|
|
* This method has the following input parameters:
|
|
*
|
|
* $_template - a string containing the template as presented in the brokers' meta-data payload
|
|
* $_tlti - a string containing the TLTI value passed in the brokers' meta-data payload
|
|
*
|
|
* This method was culled from functions.php when it became dual-purpose (called by tercero now, too) and
|
|
* was broken out so that it's accessible from multiple points.
|
|
*
|
|
* The method calls the gasStatic method to fetch the current template as defined by $_template (parameter)
|
|
* to see if one exists - if not, then we'll attempt to instantiate using the default TLTI value.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param string $_template
|
|
* @param string $_tlti
|
|
* @return array|null
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 09-21-20 mks DB-168: original coding
|
|
*
|
|
*/
|
|
public static function getCacheMap(string $_template, string &$_tlti = STRING_CLASS_GAT):?array
|
|
{
|
|
$vector = gasCache::fetchTemplateCacheMap(($_tlti . $_template));
|
|
if (is_null($vector) and $_tlti != STRING_CLASS_GAT) {
|
|
// did not fetch template based on partner tlti - see if this is a GA class instead...
|
|
$vector = gasCache::fetchTemplateCacheMap((STRING_CLASS_GAT . $_template));
|
|
$_tlti = STRING_CLASS_GAT;
|
|
}
|
|
return $vector;
|
|
}
|
|
|
|
|
|
/**
|
|
* deleteSlices() -- private static method
|
|
*
|
|
* This method is in support of mapOutboundPayload() -- where we're slicing the data payload into chunks and
|
|
* caching the chunk (collection of $queryRecordLimit) under a single key. This method spins through the array
|
|
* of previously-cached keys and calls the local cache-delete method to remove the slice.
|
|
*
|
|
* The method takes a single payload - this is the list of previously-cached keys.
|
|
*
|
|
* The method returns void.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_keys
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 04-15-19 mks DB-116: original coding
|
|
*
|
|
*/
|
|
private static function deleteSlices(array $_keys): void
|
|
{
|
|
foreach ($_keys as $key)
|
|
static::delete($key);
|
|
}
|
|
|
|
|
|
/**
|
|
* loadCacheMaps() -- static private function
|
|
*
|
|
* This function is invoked from the constructor and it's intent is to load all of the template files, extract
|
|
* certain fields, and create a memcache db (grid), keyed off the template name, containing the following
|
|
* information about the template file:
|
|
*
|
|
* 1. SCHEMA -- defines the schema for the current template (mongoDB, mySQL, etc.)
|
|
* 2. SERVICE -- defines which service the data class can be instantiated on (namaste, segundo, admin, etc.)
|
|
* 3. IS_CACHED -- boolean indicating if the current class supports cache-mapping
|
|
* 4. CACHE_TIMER -- integer value indicating the amount of time the data class will be cached for
|
|
* 5. CACHE_MAP -- key-value associative array containing the cache map (public -> private)
|
|
* 6. CLOSED_CLASS -- indicates if a GA class can be instantiated by a Partner
|
|
*
|
|
* If any template files fails loading, generate both an log and console error message.
|
|
*
|
|
* Once we've spun through the list of templates, cache the data-grid into memcache by calling the
|
|
* memcached->set() function directly and store the entire mess under the key STRING_CACHE_MAP. Clever, right?
|
|
* The expiry for this array is 0 meaning that it won't expire.
|
|
*
|
|
* The method returns a boolean value indicating successful completion of the method.
|
|
*
|
|
* NOTES:
|
|
* ------
|
|
* All templates are cached. Whether or not cache has been enabled for a particular class will be resolved
|
|
* during cacheMap processing at the broker in/out level.
|
|
*
|
|
* Since PHP doesn't really have true static classes, it's ok for this to continuously refresh the memcached
|
|
* object ensuring we always have the latest templates stored and available. (Yes. Totally lame. I know.)
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @return bool
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 02-12-19 mks DB-116: original coding
|
|
* 07-27-20 mks DB-156: support for $closedClass template member setting
|
|
*
|
|
*/
|
|
private static function loadCacheMaps(): bool
|
|
{
|
|
$cacheMap = [];
|
|
try {
|
|
$templateList = gasStatic::loadValidTemplateNames();
|
|
// todo: delete next line
|
|
consoleLog(static::$res, CON_DEBUG, 'templateCount: ' . count($templateList));
|
|
/** @var gatProdRegistrations $obj */
|
|
foreach ($templateList as $template) {
|
|
/** @var gatTestMongo $obj */
|
|
$obj = new $template();
|
|
if (is_object($obj)) {
|
|
$cacheMap[$template][STRING_SCHEMA] = (isset($obj->schema)) ? $obj->schema : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][STRING_SERVICE] = (isset($obj->service)) ? $obj->service : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][STRING_IS_CACHED] = (isset($obj->setCache)) ? $obj->setCache : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][CACHE_TIMER] = (isset($obj->cacheTimer)) ? $obj->cacheTimer : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][CACHE_EXT] = (isset($obj->extension)) ? $obj->extension : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][CACHE_SUBC] = (isset($obj->subC) and !empty($obj->subC)) ? $obj->subC : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][CACHE_MAP] = (isset($obj->cacheMap)) ? $obj->cacheMap : STRING_NOT_DEFINED;
|
|
$cacheMap[$template][CACHE_CLOSED] = (isset($obj->closedClass)) ? $obj->closedClass : STRING_NOT_DEFINED;
|
|
if (is_object($obj)) $obj->__destruct();
|
|
unset($obj);
|
|
} else {
|
|
$msg = ERROR_TEMPLATE_INSTANTIATE . $template;
|
|
static::$logger->warn($msg);
|
|
consoleLog(static::$res, CON_ERROR, $msg);
|
|
}
|
|
}
|
|
if (!empty($cacheMap)) {
|
|
$mc = static::$mco;
|
|
// add a checksum to the payload if it's not a system key
|
|
if (true === $mc->set(CACHE_MAP, json_encode($cacheMap), 0)) {
|
|
return true;
|
|
}
|
|
}
|
|
static::$logger->warn(FAIL_CACHE_MAP_CACHE);
|
|
consoleLog(static::$res, CON_ERROR, FAIL_CACHE_MAP_CACHE);
|
|
return false;
|
|
} catch (Throwable | TypeError $t) {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
@handleExceptionMessaging($hdr, $t->getMessage(), $foo, true);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* cacheMapAssociativeArray() -- private static method
|
|
*
|
|
* The purpose of this method is to convert an incoming associative array, where the keys at the top level of
|
|
* the array, are cacheMap key names, over to database schema. This function is used to processing incoming
|
|
* data payloads only.
|
|
*
|
|
* This method requires the following input parameters:
|
|
*
|
|
* $_data -- this is the incoming data payload to be mapped. This should be limited to the following named
|
|
* data payload containers: STRING_QUERY_DATA, STRING_SORT_DATA, and STRING_UPDATE_DATA.
|
|
* $_map -- this is an array containing the entire cacheMap catalog for the class named in the event meta data.
|
|
* $_errs -- a call-by-reference array container to store any processing errors raised in the method.
|
|
*
|
|
* We wrap processing in a TypeError exception handler because we're calling two API methods in processing. If
|
|
* a type error is raised, execution will conclude immediately and a null value will be returned to the calling
|
|
* client.
|
|
*
|
|
* If processing is successful, we return the replacement data array back to the calling client.
|
|
*
|
|
* NOTE:
|
|
* -----
|
|
* This is the second-version of the cache-replacement function. The first version was based on the container
|
|
* key values -- for the second version, the processing is based on the array type and not the content.
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_data
|
|
* @param array $_map
|
|
* @param array $_errs
|
|
* @return array|null
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 03-12-19 mks DB-116: original coding completed (second version)
|
|
*
|
|
*/
|
|
private static function cacheMapAssociativeArray(array $_data, array $_map, array &$_errs): ?array
|
|
{
|
|
$data = null;
|
|
$validOperands = [ OPERAND_AND, OPERAND_OR, OPERAND_NOT, OPERAND_NOR ];
|
|
try {
|
|
if (array_key_first($_data) === 0) {
|
|
// process an array of associative records
|
|
// for ($index = 0, $max = count($_data); $index < $max; $index++) {}
|
|
foreach ($_data as $record) {
|
|
foreach ($record as $key => $value) {
|
|
if (array_key_exists($key, $_map[CACHE_MAP])) {
|
|
$data[($key . $_map[CACHE_EXT])] = $value;
|
|
} elseif (in_array($key, $_map[CACHE_MAP])) {
|
|
$data[array_search($key, $_map[CACHE_MAP]) . $_map[CACHE_EXT]] = $value;
|
|
} else {
|
|
// key not in cache-map so we'll drop it
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = sprintf(ERROR_CACHE_MAP_KEY_404, $key);
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
foreach ($_data as $label => $content) {
|
|
if (array_key_exists($label, $_map[CACHE_MAP])) {
|
|
$data[$label . $_map[CACHE_EXT]] = $content; // does nothing b/c data is schema
|
|
} elseif (!in_array($label, $validOperands) and in_array($label, $_map[CACHE_MAP])) {
|
|
$data[array_search($label, $_map[CACHE_MAP]) . $_map[CACHE_EXT]] = $content;
|
|
} elseif (in_array($label, $validOperands)) {
|
|
$data[$label] = $content;
|
|
} else {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = sprintf(ERROR_CACHE_MAP_KEY_404, $label);
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
}
|
|
}
|
|
}
|
|
} catch (TypeError $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_TYPE_EXCEPTION;
|
|
$_errs[] = $msg;
|
|
static::$logger->error($hdr . $msg);
|
|
static::$logger->error($hdr . $t->getMessage());
|
|
return null;
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
|
|
/**
|
|
* cacheMapIndexedArray() -- private static method
|
|
*
|
|
* The purpose of this method is to convert an indexed array from cacheKeys to schemaKeys (aka cacheMapping).
|
|
* This method is used for the BROKER_REQUEST_CREATE payload as well STRING_RETURN_PAYLOAD containers. Note that
|
|
* this method is only used to processing incoming request data. Additionally, this method can process subC
|
|
* payloads (mongoDB), arrays and objects embedded within the data payload.
|
|
*
|
|
* The method has the following required input parameters:
|
|
*
|
|
* $_data -- this is the indexed array of payload data to be processed.
|
|
* $_map -- this is the cacheMap vector for the declared class.
|
|
* $_isRD -- this is a boolean value indicating is this is a STRING_RETURN_DATA payload which invokes easier
|
|
* (less-complicated) processing.
|
|
* $_errs -- call-by-reference array container for any errors raised during processing
|
|
*
|
|
* If successful, the method returns an array that's been converted from cacheKeys to schemaKeys. Otherwise,
|
|
* a null value is returned. Errors returns to the client are obtuse compared to the errors logged.
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_data
|
|
* @param array $_map
|
|
* @param bool $_isRD
|
|
* @param array $_errs
|
|
* @return array|null
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 03-13-19 mks DB-116: original coding completed
|
|
* 06-11-19 mks DB-122: fixed error in outbound object processing
|
|
* 06-18-19 mks DB-122: suppressed warning about unknown field in $_map
|
|
*
|
|
*/
|
|
private static function cacheMapIndexedArray(array $_data, array $_map, bool $_isRD, array &$_errs): ?array
|
|
{
|
|
$data = null;
|
|
try {
|
|
if ($_isRD) {
|
|
// this is STRING_RETURN_DATA processing:
|
|
// we're only mapping the values in the array and there is no recursive processing
|
|
for ($i = 0, $max = count($_data); $i < $max; $i++) {
|
|
if (in_array($_data[$i], $_map[CACHE_MAP])) {
|
|
$data[$i] = array_search($_data[$i], $_map[CACHE_MAP]) . $_map[CACHE_EXT];
|
|
} elseif (in_array(rtrim($_data[$i] . $_map[CACHE_EXT]), $_map[CACHE_MAP])) {
|
|
$data[$i] = array_search((rtrim($_data[$i], $_map[CACHE_EXT])), $_map[CACHE_MAP]);
|
|
}
|
|
}
|
|
} else {
|
|
// this is payload data -- it puts the extension in the basket...er, after the column name
|
|
foreach ($_data as $record) {
|
|
$rec = null;
|
|
foreach ($record as $column => $value) {
|
|
if (in_array($column, $_map[CACHE_MAP])) {
|
|
// this is inbound data because $column matches the cacheMap key, not the field
|
|
$schemaKey = array_search($column, $_map[CACHE_MAP]); // grab the schema key
|
|
if (array_key_exists(CACHE_SUBC, $_map) and is_array($_map[CACHE_SUBC]) and array_key_exists($schemaKey, $_map[CACHE_SUBC])) {
|
|
foreach ($value as $subCRecord) {
|
|
$subCRec = null;
|
|
if (is_array($subCRecord)) {
|
|
foreach ($subCRecord as $subCField => $subCValue) {
|
|
if (in_array($subCField, $_map[CACHE_MAP])) {
|
|
$subCRec[array_search($subCField, $_map[CACHE_MAP]) . $_map[CACHE_EXT]] = $subCValue;
|
|
} else {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = sprintf(ERROR_CACHE_MAP_KEY_404, $subCField);
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
}
|
|
}
|
|
} else {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$msg = ERROR_SUBC_SCALAR;
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
}
|
|
if (!is_null($subCRec) and count($subCRec)) {
|
|
$rec[($schemaKey . $_map[CACHE_EXT])][] = $subCRec;
|
|
}
|
|
}
|
|
} elseif (is_array($value)) {
|
|
// we have an array field that is not a subCollection
|
|
static::cacheMapGrind($value, $_map, $_errs);
|
|
$rec[($schemaKey . $_map[CACHE_EXT])] = $value;
|
|
} elseif (is_object($value)) {
|
|
// we have an object field who's elements should be mapped
|
|
$aryData = null;
|
|
foreach ($value as $oKey => $oValue) {
|
|
if (is_array($oValue)) {
|
|
static::cacheMapGrind($oValue, $_map, $_errs);
|
|
$aryData[((array_search($oKey, $_map[CACHE_MAP])) . $_map[CACHE_EXT])] = $oValue;
|
|
} elseif (in_array($oKey, $_map[CACHE_MAP])) {
|
|
$aryData[((array_search($oKey, $_map[CACHE_MAP])) . $_map[CACHE_EXT])] = $oValue;
|
|
} else {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = sprintf(ERROR_CACHE_MAP_KEY_404, $oValue);
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
}
|
|
}
|
|
if (!is_null($aryData)) {
|
|
if (in_array($column, $_map[CACHE_MAP])) {
|
|
$rec[array_search($column, $_map[CACHE_MAP]) . $_map[CACHE_EXT]] = (object)$aryData;
|
|
} else {
|
|
$rec[$column] = (object)$aryData;
|
|
}
|
|
}
|
|
} else {
|
|
$rec[($schemaKey . $_map[CACHE_EXT])] = $value;
|
|
}
|
|
} else {
|
|
// we're assuming this is an *OUTBOUND* payload since the key was not found in the cacheMap
|
|
$newKey = str_replace($_map[CACHE_EXT], '', $column); // remove class extension
|
|
|
|
// next, we need to process the outbound key against the cache map while checking for
|
|
// sub-collections and arrays -- if these are found, process after the container key
|
|
// has been mapped before mapping the container contents.
|
|
// Note that the framework does not return objects.
|
|
if (array_key_exists($newKey, $_map[CACHE_MAP])) {
|
|
$subCKey = $_map[CACHE_MAP][$newKey];
|
|
// check for sub-collection data
|
|
if (isset($_map[CACHE_SUBC]) and is_array($_map[CACHE_SUBC]) and array_key_exists($newKey, $_map[CACHE_SUBC]) and is_array($value)) {
|
|
for ($i = 0, $max = count($value); $i < $max; $i++) {
|
|
foreach ($value[$i] as $subColumn => $subValue) {
|
|
// strip the extension for the subCollection column
|
|
@$newSubKey = $_map[CACHE_MAP][str_replace($_map[CACHE_EXT], '', $subColumn)];
|
|
if (!empty($newSubKey))
|
|
$rec[$subCKey][$i][$newSubKey] = $subValue;
|
|
elseif (in_array($subColumn, $_map[CACHE_MAP]))
|
|
// border/edge case -- if data was inserted incorrectly,
|
|
// check for already mapped column name:
|
|
$rec[$subCKey][$i][$subColumn] = $subValue;
|
|
// else
|
|
// silently dropped the non-mapped field (no logging, no error message)
|
|
}
|
|
}
|
|
} elseif (is_array($value) or is_object($value)) {
|
|
// processing an array field that is not a sub-array (meaning: multiple records)
|
|
foreach ($value as $subKey => $subValue) {
|
|
$newSubKey = '';
|
|
if (array_key_exists(str_replace($_map[CACHE_EXT], '', $subKey), $_map[CACHE_MAP]))
|
|
$newSubKey = $_map[CACHE_MAP][str_replace($_map[CACHE_EXT], '', $subKey)];
|
|
if (!empty($newSubKey))
|
|
$rec[$subCKey][$newSubKey] = $subValue;
|
|
elseif (in_array($subKey, $_map[CACHE_MAP]))
|
|
$rec[$subCKey][$subKey] = $subValue;
|
|
}
|
|
} else {
|
|
// we have an outbound payload if the column data is schema (matches the key)
|
|
$rec[$_map[CACHE_MAP][$newKey]] = $value;
|
|
}
|
|
} /** @noinspection PhpStatementHasEmptyBodyInspection */ else {
|
|
// data is not in the schemaKey or the cacheKey array so it's invalid and will be
|
|
// dropped from the return payload -- we're not exposing this information to the user
|
|
// because it could reveal schema (e.g.: _id or id fields...) so this branch does
|
|
// no processing at the present time.
|
|
}
|
|
}
|
|
}
|
|
$data[] = $rec;
|
|
}
|
|
}
|
|
return $data;
|
|
} catch (TypeError $t) {
|
|
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
|
|
$msg = ERROR_TYPE_EXCEPTION;
|
|
$_errs[] = $msg;
|
|
static::$logger->error($hdr . $msg);
|
|
static::$logger->error($hdr . $t->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* cacheMapGrind() -- private static method
|
|
*
|
|
* This method was written to fix an error (PD-18) in the processing of structures embedded in a collection. For
|
|
* objects, and arrays, this recursive processing routine was added allowing Namaste to support structures with
|
|
* nested structures. This expands the ability of Namaste to store more complex structures as previously structures
|
|
* were limited to a depth of 1.
|
|
*
|
|
* The method requires three input parameters:
|
|
*
|
|
* $_data -- call by reference array that is the array of data to be parsed
|
|
* $_map -- an array containing the cacheMap for the current data class
|
|
* $_errs -- call by reference array with the current error catalog
|
|
*
|
|
* The function loops through the $_data array and, if it finds a new value, within a key->value pair, that's an
|
|
* array, we'll make a recursive call to that the sub-array is validated (fields exist in the cache map).
|
|
*
|
|
*
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
* @param array $_data
|
|
* @param array $_map
|
|
* @param array $_errs
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 03-26-20 mks PD-18: original coding
|
|
*
|
|
*/
|
|
private static function cacheMapGrind(array &$_data, array $_map, array &$_errs):void
|
|
{
|
|
foreach ($_data as $k => &$v) {
|
|
if (is_int($k)) {
|
|
// numeric keys will not be in cache map b/c this is an indexed array
|
|
if (is_array($v)) {
|
|
// if we're dealing with a nested array, then make a recursive call
|
|
static::cacheMapGrind($v, $_map, $_errs);
|
|
}
|
|
} elseif (in_array($k, $_map)) {
|
|
$aryData[(array_search($k, $_map[CACHE_MAP]) . $_map[CACHE_EXT])] = $v;
|
|
} else {
|
|
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
|
|
$msg = sprintf(ERROR_CACHE_MAP_KEY_404, $k);
|
|
$_errs[] = $msg;
|
|
static::$logger->data($hdr . $msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* __clone()
|
|
*
|
|
* silently disallow the cloning function
|
|
*
|
|
* @return null
|
|
*/
|
|
private function __clone()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* __destruct()
|
|
*
|
|
* closes the connection to memcache
|
|
*
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
// As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.
|
|
//
|
|
// destructor is registered shut-down function in constructor -- so any recovery
|
|
// efforts should go in this method.
|
|
}
|
|
} |