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.
This commit is contained in:
2026-04-05 09:49:30 -07:00
commit 373ebc8c93
1284 changed files with 409372 additions and 0 deletions

View File

@@ -0,0 +1,709 @@
<?php
/**
* BindParams -- helper class
*
* this is a helper class for the gacMySQL class, specifically for generating dynamic prepared statements.
*
* this class, when instantiated, creates storage for a prepared statement's type and values. When we want to
* create the prepared statement, we use call_user_func_array() and use the output from this method to generate
* the arguments that are normally passed in a prepared statement.
*
* using this class allows a data payload to be dynamically parsed and validated - allows a client to update
* a sub-set of a table without having to explicitly enumerate every column in the table.
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* --------
* 06-29-17 mks original coding
*
*/
class BindParams {
private $values = array();
private $types = '';
/**
* add() -- public method
*
* this method accepts two parameters as input - the type of the variable and the value of the variable. In
* this instance, when I say variable, I am referring to a mysql table column.
*
* if, for some unknown reason, type is a value not allowed, reset it to type 's' which should cover-up most
* mistakes.
*
* $value as a call-by-reference to suppress a PHP warning message.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param $type
* @param $value
*
* HISTORY:
* --------
* 06-29-17 mks original coding
*
*/
public function add(string $type, &$value)
{
switch ($type) {
case 'd' :
case 'i' :
case 'b' :
case 's' :
break;
default :
$type = 's';
}
$this->values[] = $value;
$this->types .= $type;
}
public function isEmpty()
{
return((empty($this->values)) ? true : false);
}
public function checkOrd()
{
return((count($this->values) == strlen($this->types)));
}
/**
* get() -- public method
*
* get() simply returns the two class variables as a string of output that's tailored to the input
* requirement of mysqli::bind_param().
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return array
*
* HISTORY:
* --------
* 06-29-17 mks Original coding
*
*/
public function get()
{
return array_merge(array($this->types), $this->values);
}
public function refValues($arr) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
}
class gacMySQL extends gaaNamasteCore
{
private $slaveConnection = null; // resource link to mysqli service
protected $useSlaveServer = false; // should be overridden in the class instantiation
protected $batchSize = PDO_RECORDS_PER_PAGE;
protected $mySqlTypes = array();
protected $tip = false; // indicates if a transaction is already in progress
protected $uniqueIndexes = null;
protected $compoundIndexes = null;
protected $exposedFields = null;
protected $dbEvent; // used to track the different sql events
protected $rowsAffected; // how many rows were affected by the sql query
protected $queryResult; // container to hold return payload from mysqli
protected $recordLimit;
protected $serviceReady; // boolean indicating if the mysql service is ready
// exceptions to the query-builder
public $queryOrderBy;
public $queryOrderByDirection;
public $queryGroupBy;
public $queryGroupByDirection;
public $queryLimit;
public $queryHaving;
public $mysqlMasterAvailable;
public $mysqlSlaveAvailable;
// allowable operands for mysql
public $operands = [
OPERATOR_EQ,
OPERATOR_LTE,
OPERATOR_GTE,
OPERATOR_DNE,
OPERATOR_LT,
OPERATOR_GT,
OPERATOR_NE
];
/**
* __construct -- public method
*
* constructor for the mysql data instantiation class
*
* Three input parameters are supported for the constructor:
*
* $_template: the name of the template that establishes which data class will be instantiated
* $_meta: the meta data payload as received from the broker - critical because it contains the name
* of the class template we're going to be instantiating.
* $_id: an optional parameter - if provided, we'll instantiate the class and then attempt to load
* the record referenced by the primary key value (after evaluating the id type).
*
* Next, we going to assign mysql resources - based on the configuration, if we're supporting slave reads, make
* the appropriate assignments so the correct resource is engaged for any particular query.
*
* Load the template properties into the class and set the class properties accordingly.
*
* Every mySQL table has two "primary keys" -- the traditional auto-incrementing integer, and a guid string.
* The best-practices effort of "id's internally, guids externally" applies to mysql structures.
*
* When we instantiate the class and we receive an id, we have to evaluate if we're passed a string (guid) or an
* integer (id) and adjust the current pkey pointer appropriately so that correct query is build deeper down.
*
* Therefore, mysql is the first and, of this writing, the only db instantiation class that has a floating pkey
* value/type which is established on a data fetch at run-time.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_template
* @param array $_meta
* @param mixed $_id
*
* HISTORY:
* --------
* 06-29-17 mks initial coding
*
*/
public function __construct(string $_template, array $_meta, $_id = null)
{
register_shutdown_function(array($this, '__destruct'));
parent::__construct();
if ($this->trace and $this->logger->available) {
$this->logger->trace(STRING_ENT_METH . __METHOD__);
if (!empty($_guid) and $this->debug) {
$this->logger->debug('received guid: ' . $_guid);
}
}
// validate the meta data payload
if (empty($_meta)) {
$this->state = STATE_META_ERROR;
$this->logger->data(ERROR_DATA_META_REQUIRED);
$this->eventMessages[] = ERROR_DATA_META_REQUIRED;
return;
} elseif (!array_key_exists(META_TEMPLATE, $_meta)) {
$this->state = STATE_META_ERROR;
$msg = ERROR_DATA_META_KEY_404 . META_TEMPLATE;
$this->logger->data($msg);
$this->eventMessages[] = $msg;
return;
}
// invoke the parent constructor, load the mysql configuration
parent::__construct();
$this->status = false;
$this->config = gasConfig::$settings[CONFIG_DATABASE_MYSQL];
if (empty($this->config)) {
$msg = ERROR_CONFIG_RESOURCE_404 . RESOURCE_MYSQL;
$this->logger->warn($msg);
$this->eventMessages[] = $msg;
$this->state = STATE_RESOURCE_ERROR_MYSQL;
return;
}
// load the template
$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
// if we're passed an optional $_id, then evaluate which type of id we're working with and make
// the appropriate assignments.
if (!empty($_id)) {
$_id = trim($_id);
$_id = (is_numeric($_id)) ? abs(intval($_id)) : $_id;
switch (gettype($_id)) {
case DATA_TYPE_STRING :
if (validateGUID($_id)) {
if ($this->pKey != PKEY_GUID) {
$msg = sprintf(ERROR_PKEY_TYPE, DATA_TYPE_STRING);
$this->logger->error($msg);
$this->state = STATE_DATA_TYPE_ERROR;
$this->eventMessages[] = $msg;
return;
}
} else {
$msg = ERROR_INVALID_GUID . $_id;
$this->eventMessages[] = $msg;
$this->logger->error($msg);
$this->state = STATE_DATA_ERROR;
return;
}
break;
case DATA_TYPE_INTEGER :
if ($this->pKey != PKEY_ID) {
$msg = sprintf(ERROR_PKEY_TYPE, DATA_TYPE_INTEGER);
$this->logger->error($msg);
$this->eventMessages[] = $msg;
$this->state = STATE_DATA_TYPE_ERROR;
return;
}
$this->pKey = PKEY_ID;
break;
default :
$msg = sprintf(ERROR_PKEY_TYPE, gettype($_id));
$this->logger->error($msg);
$this->eventMessages[] = $msg;
$this->state = STATE_DATA_TYPE_ERROR;
return;
break;
}
}
// establish and assign mysql connections
if (gasResourceManager::$mySqlMasterAvailable) {
$this->connection = gasResourceManager::fetchResource(RESOURCE_MYSQL_MASTER);
$this->mysqlMasterAvailable = true;
if (gasResourceManager::$mySqlSlaveAvailable) {
$this->slaveConnection = gasResourceManager::fetchResource(RESOURCE_MYSQL_SLAVE);
$this->mysqlSlaveAvailable = true;
} else {
$this->mysqlSlaveAvailable = false;
}
} else {
$this->mysqlMasterAvailable = false;
$this->state = STATE_RESOURCE_ERROR_MYSQL;
return;
}
$this->queryOrderBy = null;
$this->queryGroupBy = null;
$this->queryLimit = null;
$this->queryHaving = null;
$this->queryGroupByDirection = null;
$this->queryGroupByDirection = null;
$this->serviceReady = true;
// if we have an $_id, load the record
if ($this->collectionName != NONE) {
$this->buildIndexReference();
$this->setRowsReturnedLimit();
}
}
/**
* buildIndexReference() -- private method
*
* this method looks at the table defined in the current class instantiation and fetches the schema
* information about the table from mysql.
*
* Each returned array structure from the query looks like this:
*
* Array
* (
* [Field] => email_usr
* [Type] => varchar(50)
* [Null] => NO
* [Key] => UNI
* [Default] =>
* [Extra] =>
* )
*
* We're looking for the column 'Key' to be not-empty as this indicates that the table column is indexed
* in some way.
*
* We want to save the indexed column information in a K->V paired array so that, when we're parsing
* queries submitted to the mysql service, we can screen the query and prevent the execution of any
* query that does not use the indexed columns of the table.
*
* The K->V associative array will be stored locally in the the $fieldTypes variable (declared in the core).
*
* The Key will contain the name of the indexed column, and the Value will have the mysql type definition
* for that column.
*
* If the query execution generates a mysql error, set a WARN message and return.
* If the query executes, but no indexed columns are returned, raise a WARN message.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* --------
* 06-30-17 mks original coding
*
*/
private function buildIndexReference()
{
if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__);
$data = null;
// generate the cache key appropriate to the class and see if we've already cached this info
// based off a previous instantiation...
$cKey = CACHE_NAMASTE_KEY . '_' . CACHE_MYSQL_TABLE_SCHEMA . '_' . $this->collectionName;
if ($data = gasCache::get($cKey)) {
$data = json_decode(gzuncompress($data), true);
} else {
$this->dbEvent = DB_EVENT_NAMASTE;
$this->strQuery = 'SHOW COLUMNS FROM ' . $this->collectionName;
$this->executeNonPreparedQuery();
if (!$this->rowsAffected) {
$this->logger->warn(ERROR_SQL_FTL_INDEXES);
$this->eventMessages[] = ERROR_SQL_FTL_INDEXES;
} else {
foreach($this->queryResults as $row) {
$data[] = $row;
}
}
gasCache::add($cKey, gzcompress(json_encode($data, true)));
}
if (!empty($data) and is_array($data)) {
foreach ($data as $row) {
@$this->mySqlTypes[$row[MYSQL_COLUMN_FIELD]] = $row[MYSQL_COLUMN_TYPE];
if (!empty($row[MYSQL_COLUMN_KEY])) {
$this->indexes[] = $row[MYSQL_COLUMN_FIELD];
}
if (@$row[MYSQL_COLUMN_KEY] == MYSQL_INDEX_PRIMARY or @$row[MYSQL_COLUMN_KEY] == MYSQL_INDEX_UNIQUE) {
$this->uniqueIndexes[] = $row[MYSQL_COLUMN_FIELD];
}
}
}
}
/**
* setRowsReturnedLimit() -- private method
*
* this function is called in the constructor for the current table instantiation.
*
* it looks at the information_schema table to get the average_row_length (arl) value for the table.
* this is a gross calculation - the more data in the table, the more accurate the value.
*
* if we can get the arl value from the information_schema, then divide this number into the system
* constant (max_data_returned) to see if the result is less-than or equal-to the system constant for the
* number of rows returned per query...
*
* if the calculated value is smaller, then allow the system constants to remain -- if not, adjust the system
* constant for the max_rows_returned so that the total amount of data remains under the system constant
* max_data_returned.
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* 07-10-17 mks original coding
*
*/
private function setRowsReturnedLimit()
{
if (gasConfig::$settings[ERROR_TRACE] and $this->logger->available) {
$this->logger->trace(STRING_ENT_METH . __METHOD__);
}
$key = PDO_DATA_DEFINITION . '_' . PDO_AVG_ROW_LEN . '_' . $this->collectionName;
$cacheData = null;
$this->dbEvent = MYSQL_EVENT_META;
if ($cacheData = gasCache::get($key)) {
$cacheData = json_decode(gzuncompress($cacheData), true);
$this->recordLimit = $cacheData[PDO_RECORDS_PER_PAGE];
} else {
$schema = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_MYSQL][CONFIG_DATABASE_MYSQL_APPSERVER][CONFIG_DATABASE_MYSQL_MASTER][CONFIG_DATABASE_MYSQL_DB];
$this->strQuery = '-- noinspection SqlDialectInspection
SELECT AVG_ROW_LENGTH
FROM information_schema.tables
WHERE table_schema = "' . $schema . '"
AND table_name = "' . $this->collectionName . '"';
$this->recordLimit = PDO_RECORDS_PER_PAGE;
$this->executeNonPreparedQuery();
if (($this->rowsAffected === 1) and (isset($this->queryResult[0][MYSQL_AVG_ROW_LENGTH]))) {
$arl = $this->queryResult[0][MYSQL_AVG_ROW_LENGTH];
if (($arl * PDO_RECORDS_PER_PAGE) > MYSQL_MAX_DATA_RETURNED) {
$this->recordLimit = intval(MYSQL_MAX_DATA_RETURNED / $arl);
}
}
$cacheData[PDO_RECORDS_PER_PAGE] = $this->recordLimit;
if (!gasCache::add(PDO_DATA_DEFINITION . '_' . PDO_AVG_ROW_LEN . '_' . $this->collectionName, gzcompress(json_encode($cacheData, true)), gasCache::$cacheTTL)) {
$this->logger->warn('memcache:set failed - check log files');
}
}
}
/**
* executeNonPreparedQuery() -- private method
*
* this is the main method to execute all META and SELECT queries - any query that is not a prepared statement
* will execute here. Basically, namaste queries.
*
* upon invocation, the string passed (implicitly through the member variable: $query) will be cleaned through
* the common function, and then we'll evaluate the query based on the setting of the member variable $dbEvent.
* If $dbEvent is not META and not SELECT, then we're going to return with a WARN message requiring the client
* to use the prepared-query method.
*
* Next, parse the query and look for the "?" character - which is used as a place holder in prepared queries,
* and, if found, reject the request and return with a WARN message.
*
* Call a private method to see if the slave server is enabled and, if so, use it if the current query contains
* the SELECT keyword (meta queries will not use SELECT) and return the connection resource to a local variable.
*
* if query timers are enabled, then mark the start time and execute the query. record the end-time and log
* the query through the parent::method().
*
* Make a call to fetch the data as an associative array and post the results, along with the row count, to
* class variables.
*
* if the query generated an mysql error, generate an WARN message and return.
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* --------
* 06-30-17 mks original coding
*
*/
private function executeNonPreparedQuery()
{
if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__);
$startTime = floatval(0);
if ($this->debug) {
$this->logger->debug($this->strQuery);
}
// todo: can I exec this schema command using the read-slave? Do I want to?
/** @var $dbLink mysqli() */
$dbLink = $this->connection;
if ($this->useTimers) $startTime = floatval(0);
$this->queryResults = null;
if ($this->dbEvent != DB_EVENT_NAMASTE) {
$this->strQuery = cleanQueryString($this->strQuery);
}
switch($this->dbEvent) {
case DB_EVENT_NAMASTE :
case DB_EVENT_SELECT :
break;
default :
$this->logger->error(ERROR_SQL_NOT_PREP_STMNT);
return;
}
if (stripos($this->strQuery, '?')) {
$this->logger->warn(ERROR_SQL_LOST_PREP_QUERY);
$this->logger->warn($this->strQuery);
return;
}
if ($this->useTimers) {
$startTime = gasStatic::doingTime();
}
if ($result = $dbLink->query($this->strQuery)) {
$this->rowsAffected = $result->num_rows;
if ($this->useTimers) {
$this->logger->metrics($this->strQuery, gasStatic::doingTime($startTime));
$this->logger->debug(MYSQL_ROWS_AFFECTED . $this->rowsAffected);
}
while ($row = $result->fetch_assoc()) {
$this->queryResult[] = $row;
}
} else {
$this->logger->warn('error expecting query: ' . $this->strQuery);
}
}
/**
* 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-30-17 mks original coding
*
*/
private function loadTemplate():bool
{
if ($this->trace) $this->logger->trace(STRING_ENT_METH . __METHOD__);
try {
/** @var gatTestMySQL template */
$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_PDO) {
$this->logger->warn(ERROR_SCHEMA_MISMATCH . $this->template->schema . ERROR_STUB_EXPECTING . TEMPLATE_DB_PDO);
$this->setState(ERROR_SCHEMA_MISMATCH . $this->templateName);
return (false);
}
// transfer meta data info to current instantiation
$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);
}
}
if (!is_null($this->template->cacheMap) and $this->useCache) {
foreach ($this->template->cacheMap as $key => $value) {
$this->cacheMap[($key . $this->ext)] = $value;
}
} elseif (!$this->useCache) {
$this->cacheMap = null;
if (!is_null($this->template->exposedFields)) {
$this->exposedFields = $this->template->exposedFields;
}
}
if (!is_null($this->template->uniqueIndexes)) $this->uniqueIndexes = $this->template->uniqueIndexes;
if (!is_null($this->template->compoundIndexes)) $this->compoundIndexes = $this->template->compoundIndexes;
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);
}
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()
{
}
/**
* __destruct() -- public function
*
* class destructor
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-29-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();
}
}