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

498 lines
19 KiB
PHP

<?php
/**
* some assumptions to make:
*
* -- that this class will be invoked via the gacFactory class
* -- that the gacFactory class will pre-validate the data template as being for the Ddb schema
*
*/
class gacDdb extends gaaNamasteCore
{
private $globalIndexes = null;
private $localIndexes = null;
protected $service; // defines the end-point service
/**
* gacDdb constructor.
*
* the constructor for this class requires two input parameters:
*
* $_meta -- the meta data payload (has been vetted by the factory class)
* $_guid -- an optional string containing a guid value -- if this is specified, then we're telling the class
* to instantiate the class with the designated record pre-loaded.
*
* We're going to initialize with some housekeeping chores - like loading up the configuration, initializing
* a connection to the DDB resource via the resource manager, and then loading the template.
*
* if we passed a guid into the method, validate the guid
* if cache is enabled, check to see if the record is cached and, if so, load it
* if the $data property is still null, call the method to load the record
* note that the state/status of the class will be set here
*
* return control to the calling client
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_meta
* @param string $_guid
*
* HISTORY:
* ========
* 06-21-17 mks original coding
*
*/
public function __construct(array $_meta, string $_guid = '')
{
// set-up
register_shutdown_function([$this, STRING_DESTRUCTOR]);
$this->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();
}
}