status = false; parent::__construct(); // load config $this->config = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_DDB]; if (empty($this->config)) { $this->logger->fatal(ERROR_CONFIG_RESOURCE_404 . CONFIG_DATABASE . COLON . CONFIG_DATABASE_DDB); $this->state = STATE_FRAMEWORK_FAIL; return; } // set the class properties based off the template settings for the data $this->templateName = STRING_CLASS_GAT . $_meta[META_TEMPLATE]; if (!$this->loadTemplate()) { $this->logger->warn(ERROR_TEMPLATE_INSTANTIATE . $_meta[META_TEMPLATE]); $this->state = STATE_TEMPLATE_ERROR; return; } $this->class = $_meta[META_TEMPLATE]; // set the class to the name of the requested data class // get the http resource for connecting to DynamoDB instance if (gasResourceManager::$ddbAvailable and is_null($this->connection)) { $this->connection = gasResourceManager::fetchResource(RESOURCE_DDB); if (!is_object($this->connection)) { $this->logger->fatal(ERROR_RESOURCE_DDB_404); $this->setState(STATE_DB_ERROR); return; } } // store meta data and client identifier $this->client = STRING_UNDEFINED; // todo: why? if (!empty($_meta)) { $this->metaPayload = $_meta; if (isset($this->metaPayload[META_CLIENT])) { $this->client = $this->metaPayload[META_CLIENT]; } } // store the event GUID if (isset($this->metaPayload[META_EVENT_GUID])) { $this->eventGUID = $this->metaPayload[META_EVENT_GUID]; } else { $this->logger->warn(ERROR_EVENT_GUID_404); } // if a GUID/key was passed to the constructor, we need to fetch that record from the db // NOTE: mBEDS code has connecting-to-remote-service (vault) code in this block $this->data = null; // reset the data container if (!empty($_guid) and validateGUID($_guid)) { // if we have a valid guid... if ($this->useCache and gasResourceManager::$cacheAvailable) { // if cache is on and available $this->data = gasCache::get($_guid); // search cache for the key if (!is_null($this->data)) { $this->data = json_decode(gzuncompress($this->data), true); } } if (is_null($this->data)) { $this->guidFetch($_guid); } } else { $this->state = STATE_SUCCESS; $this->status = true; } } protected function _createRecord($_data) { } protected function _fetchRecords($_dd, $_rd = null, $_co = true, $_skip = 0, $_limit = 0, $_sort = null) { } protected function _updateRecord($_data){ } protected function _deleteRecord($_data) { } protected function _lockRecord() { } protected function _releaseLock() { } protected function _isLocked() { } /** * guidFetch() -- private method * * this method is (should only be) called from the class constructor and only then if the calling client passes * a guid into the constructor - which the framework interprets as a request to instantiate and populate with the * record designated by the guid - which is the primary key for the ddb table. * * The required input parameter is the guid for the record. Validation (that this is a guid) happens in the * constructor or the calling client. * * First, we build the table name from the current environment and then build the query. The query is just a * key-value-pair fetch but converts into something overly-complicated once we phrase it in ddb syntax. Just * for logging and general readability, we sql-ize the ddb-query and store the string in the designated property. * * If the class is using timers, start the timer * Execute the query - which returns an iterator and not a data set * Log the timer results to the metrics table * parse the return data set and assign it to the $data property * * We exception-wrap the query so we'll return the error as to why the query failed and will log accordingly. * * Next, if cache is enabled for the current class, cache the item after converting the data set to a json * string. * * At all levels, set the state-status values accordingly - these are what should be evaluated by the calling * client on return from the method. * * @author mike@givingassistant.org * @version 1.0 * * @param string $_guid * * HISTORY: * ======== * 06-21-17 mks original coding * */ private function guidFetch(string $_guid) { if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__); $this->status = false; $startTime = floatval(0); $tableName = gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $this->collectionName . $this->ext; $query = [ DDB_STRING_CONSISTENT_READ => true, DDB_TABLE_NAME => $tableName, DDB_STRING_EXPR_ATTR_VALS => [ ':q1' => [ $this->fieldTypes[$this->pKey . $this->ext] => $_guid ] ], DDB_STRING_KEY_COND_EXPR => "$this->pKey$this->ext = :q1" ]; /** @noinspection SqlNoDataSourceInspection */ $this->strQuery = 'DDB:SELECT * FROM ' . $tableName . ' WHERE id' . $this->ext . ' = "' . $_guid . '"'; // todo - this was coded to see if it worked - next, build a query request array and pass to queryBuilder() and _fetchRecords() // exec the DDB query try { if ($this->useTimers) $startTime = gasStatic::doingTime(); /** @var AWS\Result $result */ /** @noinspection PhpUndefinedMethodInspection */ $result = $this->connection->query($query); if ($this->useTimers) $this->logger->metrics($this->strQuery, gasStatic::doingTime($startTime)); if ($result[DDB_STRING_COUNT] != 1) { $msg = sprintf(ERROR_DDB_RECORD_COUNT, count($result), 1); $this->eventMessages[] = $msg; $this->logger->error($msg); return; } // parse the return data set /** @var array $resultData */ $resultData = $result[DDB_STRING_ITEMS]; foreach ($resultData as $item) { $record = null; foreach ($item as $key => $value) foreach ($value as $type => $column) $record[$key] = $column; $this->data[] = $record; } } catch (\Aws\Exception\AwsException $e) { $this->eventMessages[] = $e->getMessage(); $this->logger->error($e->getMessage()); $this->state = STATE_DB_ERROR; return; } // cache the item if cache is enabled for the current class if ($this->useCache and !empty($this->data)) { $cacheData = json_encode($this->data); if (is_null(gasCache::add($this->getColumn($this->pKey), $cacheData))) { $msg = ERROR_CACHE_ADD_FAIL . $this->getColumn($this->pKey); $this->eventMessages[] = $msg; $this->logger->error($msg); $this->state = STATE_CACHE_ERROR; return; } } $this->event = DB_EVENT_FETCH; $this->status = true; $this->state = STATE_SUCCESS; } private function queryBuilder(array $_query) { if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__); $this->status = false; $foundIndex = false; $attribute = ''; if (!is_array($_query) or empty($_query)) { // '[]' tests true for is_array $msg = ERROR_PARAM_404 . DDB_STRING_QUERY; $this->eventMessages[] = $msg; $this->logger->error($msg); $this->state = STATE_DATA_ERROR; return; } // deal with a single-key query if (count($_query) == 1) { // extract the key value pair foreach ($_query as $k1 => $v1) { // attribute level $attribute = $k1; foreach ($v1 as $k2 => $v2) { // operand level foreach ($v2 as $k3 => $v3) { // operator level // check that the operand is '=' if ($k3 != OPERATOR_DDB_EQ) { $this->eventMessages[] = ERROR_DDB_EXP_EQ_Q1; $this->logger->data(ERROR_DDB_EXP_EQ_Q1); $this->state = STATE_DATA_ERROR; return; } if (count($v3) != 1) { $this->eventMessages[] = ERROR_DDB_EXP_VAL_Q1; $this->logger->data(ERROR_DDB_EXP_VAL_Q1); $this->state = STATE_DATA_ERROR; return; } $value = $v3[0]; // grab the search value } } } // we have extracted the search key attribute and the search value...need to find it in one of // the three possible index declarations for the current table (VALIDATE THE INDEX ATTRIBUTE) if (array_key_exists($attribute, $this->indexes) and $this->indexes[$attribute] == DDB_INDEX_HASH) { $foundIndex = 'base'; } else { $ca = ['globalIndexes', 'localIndexes']; foreach ($ca as $index) { if (is_array($this->$index)) { foreach ($this->$index as $subIndex) { if (array_key_exists($attribute, $subIndex) and $subIndex[$attribute] == DDB_INDEX_HASH) { $foundIndex = true; break; } } } if ($foundIndex) break; } } // if (is_array($this->globalIndexes)) { // foreach ($this->globalIndexes as $gIndex) { // if (array_key_exists($attribute, $gIndex) and $gIndex[$attribute] == DDB_INDEX_HASH) { // $foundIndex = true; // } // } // } // if (!$foundIndex and is_array($this->localIndexes)) { // foreach ($this->localIndexes as $lIndex) { // if (array_key_exists($attribute, $lIndex) and $lIndex[$attribute] == DDB_INDEX_HASH) { // $foundIndex = true; // } // } // } // } if (!$foundIndex) { $msg = sprintf(ERROR_DDB_NO_HASH_IDX, $attribute); $this->eventMessages[] = $msg; $this->logger->data($msg); $this->state = STATE_DATA_ERROR; return; } } elseif (count($_query) == 1) { // todo: query uses the hash and the range } else { // todo -- malformed request } } /** * loadTemplate() -- private method * * this method is invoked by the constructor and serves to load the class template file, assimilating it into * the current instantiation. * * template loads are done on the schema-instantiation level, instead of the core, because of the changes in * the template file(s) across various schemas. * * the method will load the class template and set the class member variables controlled/referenced by the * template. * * successful loading of the template is determined by the return (boolean) value -- on error, a log message * will be generated so it's up to the developer to check logs on fail-returns to see why their template * file was not correctly assimilated. * * The template to be loaded is first derived in the constructor (post validation that the template file * exists) and is pulled from the member variable (also set in the constructor) within this method. * * * @author mike@givingassistant.org * @version 1.0 * * @return bool * * HISTORY: * ======== * 06-20-17 mks original coding * */ private function loadTemplate():bool { if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__); try { $this->template = new $this->templateName(); } catch (Exception $e) { $this->logger->warn($e->getMessage()); $this->state = STATE_FRAMEWORK_FAIL; return (false); } if (!is_object($this->template)) { $this->logger->warn(ERROR_FILE_404 . $this->templateName); $this->setState(ERROR_FILE_404 . $this->templateName); return (false); } if ($this->template->schema != TEMPLATE_DB_DDB) { $this->logger->warn(ERROR_SCHEMA_MISMATCH . $this->template->schema . ERROR_STUB_EXPECTING . TEMPLATE_DB_DDB); $this->setState(ERROR_SCHEMA_MISMATCH . $this->templateName); return (false); } // transfer meta data info to current instantiation $this->service = $this->template->service; $this->schema = $this->template->schema; $this->collectionName = $this->template->collection; $this->ext = $this->template->extension; $this->useCache = $this->template->setCache; $this->useDeletes = $this->template->setDeletes; $this->useAuditing = $this->template->setAuditing; $this->useJournaling = $this->template->setJournaling; $this->allowUpdates = $this->template->setUpdates; $this->useDetailedHistory = $this->template->setHistory; $this->defaultStatus = $this->template->setDefaultStatus; $this->searchStatus = $this->template->setSearchStatus; $this->useLocking = $this->template->setLocking; $this->useTimers = ($this->template->setTimers and gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMERS]); $this->pKey = $this->template->setPKey; $this->useToken = $this->template->setTokens; $this->cacheExpiry = $this->template->cacheTimer; if (isset($this->template->fields) and is_array($this->template->fields)) { foreach ($this->template->fields as $key => $value) { if ($key == DB_HISTORY) { $this->fieldList[] = $key; $this->fieldTypes[$key] = $value; } else { $this->fieldList[] = ($key . $this->ext); $this->fieldTypes[($key . $this->ext)] = $value; } } } if (isset($this->template->indexes) and is_array($this->template->indexes)) { foreach ($this->template->indexes as $key => $value) { $this->indexes[] = ($key . $this->ext); } } // todo: validate the global index data if (isset($this->template->globalIndexes) and is_array($this->template->globalIndexes)) { foreach ($this->template->globalIndexes as $key ) { $this->globalIndexes[] = $key; } } // todo: validate the local index data if (isset($this->template->localIndexes) and is_array($this->template->localIndexes)) { foreach ($this->template->localIndexes as $key) { $this->localIndexes[] = $key; } } if (!is_null($this->template->cacheMap)) { foreach ($this->template->cacheMap as $key => $value) { $this->cacheMap[($key . $this->ext)] = $value; } } else { $this->cacheMap = null; } if (!is_null($this->template->binFields)) { foreach ($this->template->binFields as $key) { $this->binaryFields[] = ($key . $this->ext); } } if ($this->template->selfDestruct) { unset($this->template); } return (true); } /** * __destruct() -- public function * * class destructor * * @author mike@givingassistant.org * @version 1.0 * * HISTORY: * ======== * 06-20-17 mks original coding * */ 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. // there is no destructor method defined in the core abstraction class, hence // there is no call to that parent destructor in this class. parent::__destruct(); } }