DATA_TYPE_INTEGER, // sorting by the id is just like sorting by createdDate TEST_FIELD_TEST_STRING => DATA_TYPE_STRING, TEST_FIELD_TEST_DOUBLE => DATA_TYPE_DOUBLE, TEST_FIELD_TEST_INT => DATA_TYPE_INTEGER, TEST_FIELD_TEST_BOOL => DATA_TYPE_INTEGER, // BOOLs in PDO are really tinyInt(1) TEST_FIELD_TEST_NIF => DATA_TYPE_INTEGER, // used in unit-testing -- never index this field DB_TOKEN => DATA_TYPE_STRING, // unique key (string) exposed externally and is REQUIRED, DB_EVENT_GUID => DATA_TYPE_STRING, // track-back identifier for broker/events DB_CREATED => DATA_TYPE_STRING, // dateTime type DB_STATUS => DATA_TYPE_STRING, // record status DB_ACCESSED => DATA_TYPE_STRING // dateTime type ]; // protected fields are fields that a client is unable to modify or delete. If a client submits a query that // updates these fields, the query will be rejected (worst case) or the directive to update/delete the field // will be silently dropped (best case). In either way, updating or removing this fields cannot be accomplished. // // Minimally, this array should contain the following fields: // -- DB_TOKEN, DB_EVENT_GUID, DB_CREATED, DB_ACCESSED // -- the ID field (either PDO_ID or MONGO_ID) // -- DB_WH_CREATED, DB_WH_EVENT_GUID, DB_WH_TOKEN // public ?array $protectedFields = [ DB_TOKEN, DB_EVENT_GUID, DB_CREATED, DB_ACCESSED, PDO_ID ]; // all fields that appear in any of the index declarations must appear in this list as this is the property // that's used in the framework as an authoritative check to qualify discriminant fields as indexes. // // indexes are always declared with the template column name and not the cache-map column name // // warehouse indexes are limited to the original record's created date and the three WH fields only // // NOTE: if you're going to declare a single column as a property, then do NOT also declare it as a single index! // public array $indexFields = [ PDO_ID, // implicitly indexed as pkey when table is created DB_CREATED, DB_STATUS, // compound index DB_TOKEN, // unique index TEST_FIELD_TEST_STRING, // for unit-testing TEST_FIELD_TEST_INT, DB_EVENT_GUID, // single field indexes... DB_ACCESSED, TEST_FIELD_TEST_DOUBLE ]; // all index names that are explicitly declared in the indexes below must also appear in this array. If there are // no pre-defined index names, then this field should be set to null. // // Note that if you're allowing mysql to generate the index names for you, and if you use a partial index (below) // that references that randomly-generated index name, and that name does not appear in this list, then you will // fail to load that template at run time, every time. // // You have been warned. // public ?array $indexNameList = [ 'cIdx1ITest' ]; // the primary key index is declared in the class properties section as $setPKey // unique indexes are to be used a values stored in these columns have to be unique to the table. Note that // null values are permissible in unique-index columns. public ?array $uniqueIndexes = [ DB_TOKEN ]; // single field index declarations -- since you can have a field in more than one index (index, multi) // the format for the single-field index declaration is a simple indexed array. public ?array $singleFields = [ TEST_FIELD_TEST_INT, DB_ACCESSED, DB_EVENT_GUID, TEST_FIELD_TEST_DOUBLE, TEST_FIELD_TEST_STRING ]; // multi-column (or compound) indexes have format of: // [ INDEX-NAME => [ FIELD_NAME1, FIELD_NAME2, ..., FIELD_NAMEn ]] // where INDEX-NAME is a unique string // // PDO compound-indexes are left-most indexes - if it cannot use the entire index, the db must be able to use // one, or more, of the left-most fields in the index. // unless it's for mongoDB -- mongoDB does not use index labels public ?array $compoundIndexes = [ 'cIdx1Test' => [ DB_CREATED, DB_STATUS ] ]; // NOTE: foreign-key indexes are not explicitly enumerated in a template -- that relationship is defined in the // schema for the table. Foreign-key indexes appear implicitly in the indexing declarations above. // cache maps are requires for namaste service classes. Even if caching is disabled for a class, a cache map is // still required for the class. For PDO classes, the PDO_ID is never included in the mapping, nor is MONGO_ID. public ?array $cacheMap = [ TEST_FIELD_TEST_STRING => CM_TST_FIELD_TEST_STRING, TEST_FIELD_TEST_DOUBLE => CM_TST_FIELD_TEST_DOUBLE, TEST_FIELD_TEST_INT => CM_TST_FIELD_TEST_INT, TEST_FIELD_TEST_BOOL => CM_TST_FIELD_TEST_BOOL, TEST_FIELD_TEST_NIF => CM_TST_FIELD_TEST_NIF, DB_TOKEN => CM_TST_TOKEN, DB_STATUS => CM_TST_FIELD_TEST_STATUS, DB_EVENT_GUID => CM_TST_EVENT_GUID, DB_CREATED => CM_TST_FIELD_TEST_CDATE, DB_ACCESSED => CM_TST_FIELD_TEST_ADATE ]; /* * if there is no cache-mapping supported for the class, and you want to limit the fields returned, * then those fields are listed here as an associative array. * * NOTE: You can have caching disabled for the class and still have a cache-map -- this controls the labels * assigned to the returned data column names exposed to the client. Schema should never be exposed. * * NOTE: if you do not support caching for the class and this class is one that is returned to a client, * (some classes are limited to internal use only, like logging), then you should (at a minimum) * exclude the primary key field (integer). * * * This array is an associative array -- the key is the native column name and the value doesn't matter. The * important thing is that the keys are the column names that you want to return back to the client. * * If $exposedFields is to be undefined for the class, then assign it to null. * */ public ?array $exposedFields = null; // in PDO-land, binary fields are your basic data blobs. All binary fields require special handling and so // need to be enumerated here as an indexed array. public ?array $binFields = null; // DB SQL: // ------- // PDO SQL is stored in the template and is keyed by the current namaste version (defined in the XML file) during // execution of the deployment script. Each version denotes a container of SQL commands that will be executed // for the targeted version. // // SQL is versioned in parallel with the Namaste (XML->application->id->version) version. Each PDO_SQL // sub-container has several fields - one of which has the version identifier. When the deployment script // executes, the release versions are compared and, if they're an exact match, the SQL is submitted for execution. // // The PDO_SQL container consists of these sub-containers: // // PDO_SQL_VERSION --> this is a float value in the form of x.y as namaste only supports versions as a major // and minor release number. (Patch releases are minor release increments.) // PDO_TABLE --> string value containing the full table name. // PDO_SQL_FC --> the FC means "first commit" -- when the table is first created, it will execute the // SQL in this block, if it exists, and if the version number for the sub-container // exactly matched the version number in the configuration XML. // PDO_SQL_UPDATE --> When the sub-container PDO_SQL_VERSION value exactly matches the XML release value, // then the ALTER-TABLE sql in this update block will be executed. // STRING_DROP_CODE_IDX --> The boilerplate code for dropping the indexes of the table. // STRING_DROP_CODE_DEV --> For version 1.0 only, this points to code to drop the entire table. // // Again, containers themselves are indexed arrays under the PDO_SQL tag. Within the container, data is stored // as an associative array with the keys enumerated above. // // // DB OBJECTS: // ----------- // DB objects are: views, procedures, functions and events. // All such objects assigned to a class are declared in this array under the appropriate header. // This is a safety-feature that prevents a one class (table) from invoking another class object. // The name of the object is stored as an indexed-array under the appropriate header. // // The format for these structures is basically the same. Each DBO is stored in an associative array with the // key defining the name of the object. Within each object, there are embedded associative arrays that have the // name of the object as the key and the object definition (text) and the value: // // objectType => [ objectName => [ objectContent ], ... ] // // Each created object should also have the directive to remove it's predecessor using a DROP statement. // // todo -- unset these objects post-instantiation so that schema is not revealed // // VIEWS: // ------ // Every namaste table will have at least one view which limits the data fetched from the table. At a minimum, // the id_{ext} field is filtered from the resulting data set via the view. Other fields can be withheld as well // but that is something that is individually set-up for each table. // // The basic view has the following syntax for declaring it's name: // view_basic_{tableName_ext} // All views start with the word "view" so as to self-identify the object, followed by the view type which, // optimally, you should try to limit to a single, descriptive word. // // Following this label, which points to a sub-array containing three elements: // STRING_VIEW ----------> this is the SQL code that defines the view as a single string value // STRING_TYPE_LIST -----> null or an array of types that corresponds to variable markers ('?') in the sql // STRING_DESCRIPTION' --> a string that describes the purpose of the view. // // At a minimum, every class definition should contain at-least a basic view as all queries that don't specify // a named view or other DBO, will default to the the basic view in the FROM clause of the generated SQL. // // PROCEDURES: // ----------- // For stored procedures, which are entirely optional, the array definition contains the following elements: // STRING_PROCEDURE -------> the SQL code that defined the stored procedure as a single string value // STRING_DROP_CODE -------> the sql code that drops the current database object // STRING_TYPE_LIST -------> an associative array of associative arrays -- in the top level, the key is the name // of the parameter that points to a sub-array that contains the parameter direction // as the key, and the parameter type as the value. There should be an entry for each // parameter to be passed to the stored procedure/function. // // ------------------------------------------------------ // | NOTE: IN params must precede INOUT and OUT params! | // ------------------------------------------------------ // // STRING_SP_EVENT_TYPE ---> Assign one of the DB_EVENT constants to this field to indicate the type of // query the stored-procedure will execute. // NOTE: there is not a defined PDO::PARAM constant for type float: use string. // STRING_DESCRIPTION -----> clear-text definition of the procedure's purpose // // Note that all of these containers are required; empty containers should contain a null placeholder. // // When a stored procedure contains a join of two or more tables/views, the first table listed is considered // to be the "owning" table and the procedure will be declared in the class template for that table, but it will // not be duplicated in other template classes referenced in the join. // public ?array $dbObjects = [ PDO_SQL => [ [ PDO_VERSION => 1.0, PDO_TABLE => 'gaTest_tst', PDO_SQL_FC => " -- -- Table structure for table `gaTest_tst` -- CREATE TABLE `gaTest_tst` ( `id_tst` int(10) UNSIGNED NOT NULL, `testString_tst` varchar(255) DEFAULT NULL, `testDouble_tst` double DEFAULT NULL, `testInteger_tst` int(11) DEFAULT NULL, `testBoolean_tst` tinyint(1) UNSIGNED DEFAULT NULL, `notIndexedField_tst` int(11) DEFAULT NULL COMMENT 'do not index this field', `createdDate_tst` datetime DEFAULT NULL, `lastAccessedDate_tst` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `status_tst` varchar(32) DEFAULT NULL, `eventGUID_tst` char(36) DEFAULT NULL, `token_tst` char(36) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ", PDO_SQL_UPDATE => " -- -- Indexes for table `gaTest_tst` -- ALTER TABLE `gaTest_tst` ADD PRIMARY KEY (`id_tst`), ADD UNIQUE KEY `gaTest_tst_token_tst_uindex` (`token_tst`), ADD KEY `gaTest_tst_createdDate_tst_status_tst_index` (`createdDate_tst`,`status_tst`), ADD KEY `gaTest_tst_eventGuid_tst_index` (`eventGUID_tst`), ADD KEY `gaTest_tst_lastAccessedDate_tst_index` (`lastAccessedDate_tst`), ADD KEY `testInteger_tst` (`testInteger_tst`), ADD KEY `testDouble_tst` (`testDouble_tst`), ADD KEY `testString_tst` (`testString_tst`(191)); -- -- AUTO_INCREMENT for dumped tables -- -- -- AUTO_INCREMENT for table `gaTest_tst` -- ALTER TABLE `gaTest_tst` MODIFY `id_tst` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; ", /* * example query return: * --------------------- * ALTER TABLE gaTest_tst DROP INDEX gaTest_tst_createdDate_tst_status_tst_index, DROP INDEX * gaTest_tst_lastAccessedDate_tst_index, DROP INDEX testInteger_tst, DROP INDEX * gaTest_tst_eventGuid_tst_index, DROP INDEX testDouble_tst, DROP INDEX testString_tst; * * NOTE: * ----- * The sql comment code tag (--) will be removed during mysqlConfig's run time processing */ STRING_DROP_CODE_IDX => "-- SELECT CONCAT('ALTER TABLE ', `Table`, ' DROP INDEX ', GROUP_CONCAT(`Index` SEPARATOR ', DROP INDEX '),';' ) FROM ( SELECT table_name AS `Table`, index_name AS `Index` FROM information_schema.statistics WHERE INDEX_NAME != 'PRIMARY' AND table_schema = 'XXXDROP_DB_NAMEXXX' AND table_name = 'XXXDROP_TABLE_NAMEXXX' GROUP BY `Table`, `Index`) AS tmp GROUP BY `Table`; ", STRING_DROP_CODE_DEV => "DROP TABLE IF EXISTS gaTest_tst;" // only executed if declared ] ], PDO_VIEWS => [ 'view_basic_gaTest_tst' => [ STRING_VIEW => "DROP VIEW IF EXISTS view_basic_gaTest_tst; CREATE VIEW view_basic_gaTest_tst AS SELECT token_tst, testString_tst, testDouble_tst, testInteger_tst, testBoolean_tst, notIndexedField_tst, status_tst, createdDate_tst, lastAccessedDate_tst, eventGUID_tst FROM gaTest_tst WHERE status_tst <> 'DELETED';", STRING_TYPE_LIST => null, STRING_DESCRIPTION => 'basic query' ], 'view_audit_gaTest_tst' => [ STRING_VIEW => "DROP VIEW IF EXISTS view_audit_gaTest_tst; CREATE VIEW view_audit_gaTest_tst AS SELECT token_tst, testString_tst, testDouble_tst, testInteger_tst, testBoolean_tst, notIndexedField_tst, status_tst, createdDate_tst, lastAccessedDate_tst, eventGUID_tst FROM gaTest_tst;", STRING_TYPE_LIST => null, STRING_DESCRIPTION => 'query for cross-broker queries by audit micro-service' ] ], PDO_PROCEDURES => [ 'testProc0' => [ STRING_DROP_CODE_DEV => "DROP PROCEDURE IF EXISTS testProc0;", STRING_PROCEDURE => "CREATE PROCEDURE testProc0() READS SQL DATA BEGIN SET @sqlString = 'SELECT COUNT(*) AS recordCount FROM gaTest_tst'; PREPARE sqlString from @sqlString; EXECUTE sqlString; DEALLOCATE PREPARE sqlString; END", STRING_TYPE_LIST => null, STRING_SP_EVENT_TYPE => DB_EVENT_SELECT, STRING_DESCRIPTION => 'stored procedure to return row-count of the table, demos a zero-param sp' ], 'testProc1' => [ STRING_DROP_CODE_DEV => 'DROP PROCEDURE IF EXISTS testProc1;', STRING_PROCEDURE => "CREATE PROCEDURE testProc1( IN targetValue INT ) READS SQL DATA BEGIN SET @targetVal = targetValue; SET @sqlString = CONCAT(' SELECT testInteger_tst, count(*) as rowCount FROM gaTest_tst WHERE testInteger_tst is not null GROUP BY testInteger_tst HAVING rowCount > ', @targetVal, ' ORDER BY rowCount DESC LIMIT 10'); PREPARE sqlStatement FROM @sqlString; EXECUTE sqlStatement; DEALLOCATE PREPARE sqlStatement; END", STRING_TYPE_LIST => [ 'targetValue' => [ STRING_IN => PDO::PARAM_INT ] ], STRING_SP_EVENT_TYPE => DB_EVENT_SELECT, STRING_DESCRIPTION => 'stored procedure that return top-10 list of integer-field values by count for all values greater than the supplied input parameter' ], 'testProc2' => [ STRING_DROP_CODE_DEV => 'DROP PROCEDURE IF EXISTS testProc2;', STRING_PROCEDURE => "CREATE PROCEDURE testProc2( IN intVal INT, OUT avgDouble FLOAT, OUT stdDevDouble FLOAT ) READS SQL DATA BEGIN SELECT AVG(testDouble_tst), STDDEV(testDouble_tst) INTO avgDouble, stdDevDouble FROM gaTest_tst WHERE testInteger_tst = intVal; END", STRING_TYPE_LIST => [ 'intVal' => [ STRING_IN => PDO::PARAM_INT ], 'avgDouble' => [ STRING_OUT => PDO::PARAM_STR ], 'stdDevDouble' => [ STRING_OUT => PDO::PARAM_STR] ], STRING_SP_EVENT_TYPE => DB_EVENT_SELECT, STRING_DESCRIPTION => 'stored procedure that calculates the avg() and stddev() for the floats with a specified integer value' ] ], PDO_FUNCTIONS => [], PDO_EVENTS => [], PDO_TRIGGERS => [] ]; //================================================================================================================= // MIGRATION DECLARATIONS // ---------------------- // Data in this section is used to handle migrations -- when we're pulling from legacy tables into the Namaste // framework. See online doc for more info. //================================================================================================================= /** * The migration map is an associative array that maps the Namaste fields (keys) to the corresponding * (remote) legacy fields in the source table to be migrated to Namaste. * * For example, if we were migrating a mysql table in the legacy production database to Namaste::mongo, then * the keys of the migration map would be the Namaste::mongo->fieldNames and the values would be the mysql * column names in the legacy table. * * If there is a value which cannot be mapped to a key, then set it to null. * * Fields that will be dropped in the migration are not listed as values or as keys. * * This map will only exist in the template object and will never be imported into the class widget. * * This is a required field. * */ public ?array $migrationMap = null; /* * the migrationSortKey defines the SOURCE field by which the fetch query will be sorted. ALL sort fields are * in ASC order so all we need to list here is the name of the field -- which MUST BE IN THE SOURCE TABLE. * * Populating this field may require preliminary examination of the data - what we want is a field that has * zero NULL values. * * This is a required field. * */ public ?string $migrationSortKey = ''; /* * The migrationStatusKey defines the status field/column in the source table -- if the user requires that * soft-deleted records not be migrated, then this field must be set. Otherwise, set the value to null. * * The format is in the form of a key-value paired array. The key specifies the name of the column and the value * specifies the "deleted" value that, if found, will cause that row from the SOURCE data to be omitted from the * DESTINATION table. * * e.g.: $migrationStatusKV = [ 'some_field' => 'deleted' ] * * Note that both the key and the value are case-sensitive! * * This is an optional field. * */ public ?string $migrationStatusKV = null; // The $migrationSourceSchema defines the remote schema for the source table, and is set in the constructor public ?string $migrationSourceSchema; // The source table in the remote repos (default defined in the XML) must be declared here, set in the constructor public ?string $migrationSourceTable; //================================================================================================================= // WAREHOUSE DECLARATIONS // ---------------------- // This section handles the warehousing configuration for the class. If a data table is eligible to be ware- // housed, then this section contains all the configuration information, including permissions, for the destination // repository. Note that we need to support bi-directional flow for data. // // Terms/Definitions: // ------------------ // HOT -- data is in production // COOL -- data has been warehoused, maintains schema, but with indexing changes. // COLD -- data has been warehoused but formatted to the destination schema, usually CSV. // WARM -- indicates any data moving from COLD -> HOT // // Design Features: // ---------------- // Supported // This is a boolean value that indicates if the class supports warehousing. If this is set to false, then // warehousing requests for the class will be rejected. // // Remote Support // -------------- // This is a boolean value that indicates if the class will support a warehouse source outside of the Namaste // framework. If this is set to false, and a user submits a request defining the data source as a remote // repository, the request will be rejected. // // Automated // This is a boolean value that indicates if the class allows automated warehousing, meaning that data will be // warehoused once the qualifying condition has been met. // // Dynamic // Boolean value that, if set to true, indicates that the class will accept dynamic requests. Otherwise, the // warehousing operations will follow the interval schedule. Defaults to false. // // Interval // This is a string value that tells the AT_micro-service how often to run automated warehousing on the data. // D = Daily, M = 1st of every month, Q = 1st of every quarter, Y = 1st of every year // The default setting for this value should be monthly (M). // // Qualifier // This is a query string, similar to what you would provide to Namaste for a fetch operation, that establishes // the filter/criteria for moving data to the warehouse. If Supported is set to true, this cannot be blank. // // Override // Boolean value indicating if, and only for dynamic event requests, if the Qualifier can be overridden. If // set to true, the the event request must contain a valid query filter. // // Delete // This is a string value that tells Namaste what to do with the source data once successfully warehoused. // H = hard delete, S = soft delete // Note that this value overrides the $setDeletes setting. // //================================================================================================================= public ?array $wareHouse = [ WH_SUPPORTED => false, // must be set to true for data class to support any warehousing WH_REMOTE_SUPPORT => false, // must be set to true to import data into this class from remote source WH_AUTOMATED => false, // must be set to true for warehousing to be automatically processed WH_DYNAMIC => false, // must be set to true to allow non-scheduled event requests WH_INTERVAL => 'M', // must be either D, M, Q or A, defaults to M WH_OVERRIDE => false, // must be set to true to allow an ad-hoc query filter WH_DELETE => 'H', // must be either H, or S. Can be reset to T via meta. Default: H // default warehouse query to grab records where the date is LT a value and the status is active: // the null value will be replaced with the value provided by the client in the wh request payload. WH_QUALIFIER => [ DB_CREATED => [ OPERAND_NULL => [ OPERATOR_LT => [ null ] ] ], DB_STATUS => [ OPERAND_NULL => [ OPERATOR_EQ => [ STATUS_ACTIVE ]]], OPERAND_AND => null ] ]; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CLASS METHODS... //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * __construct() -- public method * * we have a constructor to register the destructor. * * @author mike@givingassistant.org * @version 1.0 * * HISTORY: * ======== * 09-13-17 mks CORE-562: original coding * 09-09-19 mks DB-111: initialization of migration members moved to constructor b/c IDE warnings. * */ public function __construct() { $this->authToken = NULL_TOKEN; // these next two lines are so that the IDE doesn't flag the variable declarations as unused $this->migrationSourceSchema = ''; $this->migrationSourceTable = ''; register_shutdown_function([$this, STRING_DESTRUCTOR]); } /** @noinspection PhpUnused */ /** * buildTestData() -- public static method * * this method is used to build an array structure of random data. There are two parameters to the method: * * $_records specifies the number of records to return to the calling client * $_incomplete indicates if we want to generate a partial (not all the fields are provided) record * * The $_incomplete parameter allows us to test the PDO class ability to successfully process partial payloads * on new record creation. * * The input parameter specifies how many records (indexes in the array) should be returned to the calling client * and should be a reasonable integer between one and one-hundred (1 - 100). If the passed-value for the number * of records is outside of this range, on either side, then the passed-value will be replaced with the range * limit for the appropriate "side". * * We then spin through a loop which populates an indexed array with the elements, from the test class, * with the appropriate extension as the sub-array key value. * * @author mike@givingassistant.org * @version 1.0 * * @param int $_records * @param bool $_incomplete * @return array * * HISTORY: * ======== * 09-13-17 mks CORE-562: original coding * 10-23-17 mks CORE_585: incomplete option added to skip some of the fields * 11-06-20 mks DB-171: ensuring that the test string length cannot be > 255 (max width of table column) * */ public static function buildTestData(int $_records = 1, bool $_incomplete = false): array { if ($_records < 1) $_records = 1; if ($_records > 1000) $_records = 1000; $retData = null; mt_srand(); for ($index = 0; $index < $_records; $index++) { $sentenceCount = mt_rand(1, 20); $coinToss = ($_incomplete) ? mt_rand(0, 1) : 1; if ($coinToss) $retData[$index][CM_TST_FIELD_TEST_INT] = $sentenceCount; $coinToss = ($_incomplete) ? mt_rand(0, 1) : 1; if ($coinToss) $retData[$index][CM_TST_FIELD_TEST_DOUBLE] = floatval((1 / mt_rand(1, 10000)) * 100); $coinToss = ($_incomplete) ? mt_rand(0, 1) : 1; if ($coinToss) { $retData[$index][CM_TST_FIELD_TEST_STRING] = lorumIpsum($sentenceCount, 0); if (strlen($retData[$index][CM_TST_FIELD_TEST_STRING]) > 255) $retData[$index][CM_TST_FIELD_TEST_STRING] = substr($retData[$index][CM_TST_FIELD_TEST_STRING], 0, strpos(wordwrap($retData[$index][CM_TST_FIELD_TEST_STRING], 255), "\n")); } $retData[$index][CM_TST_FIELD_TEST_BOOL] = intval(mt_rand(0,1)); } return ($retData); } /** * __clone() -- private function * * Silently disallows cloning of the object * * @author mike@givingassistant.org * @version 1.0 * * @return null * * HISTORY: * ======== * 09-13-17 mks CORE-562: original coding * */ private function __clone() { return(null); } /** * __destruct() -- public function * * As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors. * * The destructor is registered as a shut-down function in the constructor -- so any recovery * efforts should go in this method. * * @author mike@givingassistant.org * @version 1.0 * * HISTORY: * ======== * 09-13-17 mks CORE-562: original coding * */ public function __destruct() { // empty by design } }