    /**
     * convertCacheMap() -- private static method
     *
     * This private method is the gateway/entry-point for cacheMapping on data payloads.  The function has the following
     * required input parameters:
     *
     * $_data -- this is the payload to be cacheMapped.  This should be an indexed array of one, or more, assoc arrays
     * $_dir  -- string value indicating if the data is incoming (IN) or outbound (OUT)
     * $_map  -- this is the class-specific vector of cacheMapped settings pulled from the global cacheMap
     * $_type -- this defines the data payload as either record-data or query-data
     * $_errs -- this is a call-by-reference array that allows us to propagate error messages back up the stack
     *
     * The function looks at the contents of most of the input parameters and validates the content returning a null
     * if any of the params fail validation while also adding messages to the error stack and by publishing a message
     * to the error logger.
     *
     * Once validation is complete, we pass all of the input params to a second private function, allowing for
     * recursion in that function, and hopefully get back an array (which is passed though back up to the calling
     * client) that is successfully cacheMapped.
     *
     *
     * @author  mike@givingassistant.org
     * @version 1.0
     *
     * @param array $_data
     * @param string $_dir
     * @param array $_map
     * @param string $_type
     * @param array $_errs
     * @return array|null
     *
     *
     * HISTORY:
     * ========
     * 02-25-19     mks     DB-116: original coding
     *
     */
    private static function convertCacheMap(array $_data, string $_dir, array $_map, string $_type, array &$_errs): ?array
    {
        // validate input param content -- input param type is implicitly validated by strong type decls
        if ($_dir != IN and $_dir != OUT) {
            $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
            $msg = sprintf(ERROR_CACHE_DIRECTION, (string) $_dir);
            $_errs[] = $msg;
            static::$logger->data($hdr . $msg);
            return null;
        }
        if ($_type != STRING_QUERY and $_type != STRING_DATA) {
            $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
            $msg = ERROR_CACHE_MAP_TYPE . $_type;
            $_errs[] = $msg;
            static::$logger->error($hdr . $msg);
            return null;
        }
        if (!is_array($_data)) {
            $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
            $msg = $hdr . ERROR_DATA_ARRAY_NOT_ARRAY . STRING_DATA;
            $_errs[] = $msg;
            static::$logger->data($msg);
            return null;
        }
        if (!is_array($_map)) {
            $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
            $msg = $hdr . ERROR_DATA_ARRAY_NOT_ARRAY . CACHE_MAP;
            $_errs[] = $msg;
            static::$logger->data($msg);
            return null;
        }
        try {
            return static::processCacheMap($_data, $_map, $_dir, $_type, $_errs);
        } catch (TypeError $t) {
            $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
            $msg = $hdr . ERROR_TYPE_EXCEPTION;
            $_errs[] = $msg;
            static::$logger->warn($msg);
            $msg = $hdr . $t->getMessage();
            $_errs[] = $msg;
            static::$logger->warn($msg);
            return null;
        }
    }


    /**
     * processCacheMap() -- private static method with recursion
     *
     * This function is responsible for the cacheMapping for both incoming and outbound data payloads.  It is a
     * stand-alone function because of it's recursive nature -- when we encounter a sub-array within the payload,
     * we must recursively call this method in order to process the sub-array (etc.).
     *
     * There are the following input parameters to this method, all of which are required:
     *
     * $_data -- this is the incoming/outgoing data payload.  The invoking method has parsed, for example, the incoming
     * data payload and, as an example, let's say there are three sub-arrays stored in the payload:
     * STRING_QUERY_DATA, STRING_SORT_DATA and STRING_RETURN_DATA -- the invoking method will make a total of three
     * calls to this method, one for each of the sub-arrays under BROKER_DATA.  Note that $_data should be passed as
     * an indexed array s.t. each tuple in the array is processed as a separate record.
     *
     * $_map -- this is the cacheMap for the targeted class.  In other words, it is not the entire cacheMap but the
     * named tuple for the current data class.
     *
     * $_dir -- this is a string value that may only be either IN or OUT (both are Namaste system constants).  IN
     * designates the payload as incoming while OUT designates the payload as outbound.
     *
     * $_type -- this is a string value, defined as either STRING_QUERY or STRING_DATA, and is verified in the
     * the calling client.  This value designates the type of payload to be mapped.
     *
     * $_es -- this is an array for the error-stack -- it's a call-by-reference parameter s.t. we can propagate any
     * error messages back to the invoking client.
     *
     * The method returns an array which, under optimal conditions, returns a mirror of the incoming data payload
     * save that the keys have been successfully cacheMapped.
     *
     * Any filed which fails cacheMapping (e.g.: not found) will be stored in the class static $badCacheFields and
     * will be implicitly returned to the calling client for processing.
     *
     *
     * @author  mike@givingassistant.org
     * @version 1.0
     *
     *
     * @param array $_data -- indexed array of data to be cacheMapped; may contain more than one record
     * @param array $_map  -- cacheMap for the current data class
     * @param string $_dir -- indicates the direction (flow) of the data: either INcoming or OUTbound
     * @param string $_type - indicates payload type:  either DATA or QUERY (validated in calling client)
     * @param array $_es   -- call-by-reference array for returning error messages to the calling client
     * @return array|null  -- returns the cacheMapped array or a null on error
     *
     *
     * HISTORY:
     * ========
     * 02-25-19     mks     DB-116: original coding
     *
     */
    private static function processCacheMap(array $_data, array $_map, string $_dir, string $_type, array &$_es): ?array
    {
        $data = null;                                                        // container to hold the cacheMapped record
        $records = null;                                                // container to hold all the cacheMapped records
        // todo -- test for subCollection array existing in the subC setting... which means you have to add it to the cacheMap data
        // this is where the cache-mapping magic happens...
        foreach ($_data as $record => $recordData) {
            foreach ($record as $column => &$value) {
                if ($_dir == IN) {  // todo -- map off the type...
                    // we're cache-mapping an incoming payload
                    if (in_array($column, $_map[CACHE_MAP])) {
                        if (is_array($value) and $_map[CACHE_SUBC] != STRING_NOT_DEFINED and array_key_exists(array_search($column, $_map[CACHE_MAP]), $_map[CACHE_SUBC])) {
                            // note: not checking for the case where $value is an array but $newKey is not defined in the
                            //       cached SUBC definition for the class. This loose definition for sub-collections
                            //       permits the user to store sub-arrays without inspection/validation/mapping.
                            try {
                                // we have to process $value recursively as a sub-array
                                $data[array_search($column, $_map[CACHE_MAP])] = static::processCacheMap($value, $_map, $_dir, $_type, $_es);
                            } catch (TypeError $t) {
                                $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
                                $msg = $hdr . ERROR_TYPE_EXCEPTION;
                                $_errs[] = $msg;
                                static::$logger->warn($msg);
                                $msg = $hdr . $t->getMessage();
                                $_errs[] = $msg;
                                static::$logger->warn($msg);
                                return null;
                            }
                        } else {
                            $data[array_search($column, $_map[CACHE_MAP])] = $value;
                        }
                    } else {
                        static::$badCacheFields[$column] = $value;
                    }
                } else {
                    // we're cache-mapping an outbound payload so remove the class extension from the column name
                    $newKey = str_replace($_map[CACHE_EXT], '', $column);
                    if (array_key_exists($newKey, $_map[CACHE_MAP])) {
                        // note: not checking for the case where $value is an array but $newKey is not defined in the
                        //       cached SUBC definition for the class. This loose definition for sub-collections
                        //       permits the user to store sub-arrays without inspection/validation/mapping.
                        if (is_array($value) and $_map[CACHE_SUBC] != STRING_NOT_DEFINED and array_key_exists($newKey, $_map[CACHE_SUBC])) {
                            try {
                                // we have to process $value recursively as a sub-array
                                $data[$_map[CACHE_MAP][$newKey]] = static::processCacheMap($value, $_map, $_dir, $_type,$_es);
                            } catch (TypeError $t) {
                                $hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
                                $msg = $hdr . ERROR_TYPE_EXCEPTION;
                                $_errs[] = $msg;
                                static::$logger->warn($msg);
                                $msg = $hdr . $t->getMessage();
                                $_errs[] = $msg;
                                static::$logger->warn($msg);
                                return null;
                            }
                        } else {
                            $data[$_map[CACHE_MAP][$newKey]] = $value;
                        }
                    } else {
                        static::$badCacheFields[$newKey] = $value;
                    }
                }
            }
        }
        if (!empty($data)) {
            $records[] = $data;
            unset($data);
        }  // todo: else?
        return $records;
    }