* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2022 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ abstract class BaseDatabaseFrontend extends BaseFrameworkSystem { // Load traits use CacheableTrait; /** * Current table name to use */ private $tableName = 'unknown'; /** * "Cached" value 'database_cache_enabled' from configuration */ private $databaseCacheEnabled = false; /** * Protected constructor * * @param $className Name of the class * @return void */ protected function __construct (string $className) { // Call parent constructor //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CONSTRUCTED!'); parent::__construct($className); // Initialize the cache instance $this->initCacheInstance(); // Trace message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!'); } /** * Initializes the cache instance with a new object * * @return void */ private final function initCacheInstance () { // Set "cache" attributes //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!'); $this->databaseCacheEnabled = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('database_cache_enabled'); // Is the cache enabled? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled))); if ($this->databaseCacheEnabled === true) { // Set the new instance $this->setCacheInstance(ObjectFactory::createObjectByConfiguredName('cache_class')); } // Trace message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!'); } /** * Setter for table name * * @param $tableName Name of table name to set * @return void */ protected final function setTableName (string $tableName) { $this->tableName = $tableName; } /** * Getter for table name * * @return $tableName Name of table name to set */ protected final function getTableName () { return $this->tableName; } /** * Gets a cache key from Criteria instance * * @param $criteriaInstance An instance of a Criteria class * @param $onlyKeys Only use these keys for a cache key * @return $cacheKey A cache key suitable for lookup/storage purposes */ protected function getCacheKeyByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) { // Generate it //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys))); $cacheKey = sprintf('%s@%s', $this->__toString(), $criteriaInstance->getCacheKey($onlyKeys) ); // And return it //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FRAMEWORK-SYSTEM: cacheKey=' . $cacheKey . ' - EXIT!'); return $cacheKey; } /** * 'Inserts' a data set instance into a local file database folder * * @param $dataSetInstance A storeable data set * @param $onlyKeys Only use these keys for a cache key * @return void */ protected function queryInsertDataSet (StoreableCriteria $dataSetInstance, array $onlyKeys = []) { // Default cache key is NULL //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: dataSetInstance=%s,onlyKeys()=%d - CALLED!', $dataSetInstance->__toString(), count($onlyKeys))); $cacheKey = NULL; // Is cache enabled? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled))); if ($this->databaseCacheEnabled === true) { // First get a key suitable for our cache and extend it with this class name $cacheKey = $this->getCacheKeyByCriteria($dataSetInstance, $onlyKeys); //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Using cache key %s for purging ...', $cacheKey)); } // Does this key exists in cache? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey[%s]=%s', intval($this->databaseCacheEnabled), gettype($cacheKey), $cacheKey)); if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey))) { // Purge the cache //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Invoking this->cacheInstance->purgeOffset(%s) ...', $cacheKey)); $this->getCacheInstance()->purgeOffset($cacheKey); } // Handle it over to the middleware FrameworkBootstrap::getDatabaseInstance()->queryInsertDataSet($dataSetInstance); // Trace message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!'); } /** * 'Updates' a data set instance with a database layer * * @param $dataSetInstance A storeable data set * @param $onlyKeys Only use these keys for a cache key * @return void */ protected function queryUpdateDataSet (StoreableCriteria $dataSetInstance, array $onlyKeys = []) { // Init cache key //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: dataSetInstance=%s,onlyKeys()=%d - CALLED!', $dataSetInstance->__toString(), count($onlyKeys))); $cacheKey = NULL; // Is cache enabled? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled))); if ($this->databaseCacheEnabled === true) { // First get a key suitable for our cache and extend it with this class name $cacheKey = $this->getCacheKeyByCriteria($dataSetInstance, $onlyKeys); //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: Using cache key ' . $cacheKey . ' for purging ...'); } // Does this key exists in cache? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey=%s', intval($this->databaseCacheEnabled), $cacheKey)); if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey))) { // Purge the cache $this->getCacheInstance()->purgeOffset($cacheKey); } // Handle it over to the middleware FrameworkBootstrap::getDatabaseInstance()->queryUpdateDataSet($dataSetInstance); // Trace message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!'); } /** * Getter for index key * * @return $indexKey Index key */ public final function getIndexKey () { //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!'); return FrameworkBootstrap::getDatabaseInstance()->getIndexKey(); } /** * Getter for last exception * * @return $lastException Last exception or NULL if none occured */ public final function getLastException () { //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!'); return FrameworkBootstrap::getDatabaseInstance()->getLastException(); } /** * Do a "select" query on the current table with the given search criteria and * store it in cache for later usage * * @param $criteriaInstance An instance of a Criteria class * @param $onlyKeys Only use these keys for a cache key * @return $resultInstance An instance of a database result class */ public function doSelectByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) { // Default cache key if cache is not enabled //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys))); $cacheKey = NULL; $result = []; // Is the cache enabled? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled))); if ($this->databaseCacheEnabled === true) { // First get a key suitable for our cache and extend it with this class name $cacheKey = $this->getCacheKeyByCriteria($criteriaInstance, $onlyKeys); } // Does this key exists in cache? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey[%s]=%s', intval($this->databaseCacheEnabled), gettype($cacheKey), $cacheKey)); if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey, BaseDatabaseResult::RESULT_NAME_ROWS, 1))) { // Then use this result //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Cache used for cacheKey=%s', $cacheKey)); $result = $this->getCacheInstance()->offsetGet($cacheKey); } else { // Now it's time to ask the database layer for this select statement //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Quering database, cacheKey=%s ...', $cacheKey)); $result = FrameworkBootstrap::getDatabaseInstance()->doSelectByTableCriteria($this->getTableName(), $criteriaInstance); // Cache the result if not null //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: result[]=%s', gettype($result))); if (!is_null($result)) { // Is cache enabled? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled))); if ($this->databaseCacheEnabled === true) { // A valid result has returned from the database layer //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Setting cacheKey=%s with result()=%d entries', $cacheKey, count($result))); $this->getCacheInstance()->offsetSet($cacheKey, $result); } } else { // This invalid result must be wrapped $result = array( BaseDatabaseResult::RESULT_NAME_STATUS => 'invalid', BaseDatabaseResult::RESULT_NAME_EXCEPTION => FrameworkBootstrap::getDatabaseInstance()->getLastException() ); } } // Create an instance of a CachedDatabaseResult class with the given result // @TODO Minor: Update above comment to e.g. BaseDatabaseResult //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: result[%s]=%s,result[%s]?=%d,result[%s]?=%d', BaseDatabaseResult::RESULT_NAME_STATUS, $result[BaseDatabaseResult::RESULT_NAME_STATUS], BaseDatabaseResult::RESULT_NAME_ROWS, isset($result[BaseDatabaseResult::RESULT_NAME_ROWS]), BaseDatabaseResult::RESULT_NAME_EXCEPTION, isset($result[BaseDatabaseResult::RESULT_NAME_EXCEPTION]))); $resultInstance = ObjectFactory::createObjectByConfiguredName('database_result_class', array($result)); // And return the instance //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: resultInstance=%s - EXIT!', $resultInstance->__toString())); return $resultInstance; } /** * Count the numbers of rows we shall receive * * @param $criteriaInstance An instance of a Criteria class * @param $onlyKeys Only use these keys for a cache key * @return $numRows Numbers of rows of database entries */ public function doSelectCountByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) { // Total numbers is -1 so we can distinglish between failed and valid queries /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys))); $numRows = 0; // Get the result from above method $resultInstance = $this->doSelectByCriteria($criteriaInstance, $onlyKeys); // Was that query fine? /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: resultInstance->ifStatusIsOkay()=%d', $resultInstance->ifStatusIsOkay())); if ($resultInstance->ifStatusIsOkay()) { // Then get the number of rows $numRows = $resultInstance->getAffectedRows(); } // Return the result /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: numRows=%d - EXIT!', $numRows)); return $numRows; } /** * Generates a primary key for this database frontend * * @return $primaryKey Primary key used in wrapped table */ public final function generatePrimaryKey () { // Get the table name and a database instance and ask for it /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!'); $primaryKey = FrameworkBootstrap::getDatabaseInstance()->getPrimaryKeyOfTable($this->getTableName()); // Return value /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: primaryKey=%s - EXIT!', $primaryKey)); return $primaryKey; } /** * Count rows of this table * * @return $count Count of total rows in this table */ public final function countTotalRows () { // Get the table name and a database instance and ask for it /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!'); $count = FrameworkBootstrap::getDatabaseInstance()->countTotalRows($this->getTableName()); // Return value /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: count=%d - EXIT!', $count)); return $count; } /** * Removes non-public data from given array. * * @param $data An array with possible non-public data that needs to be removed. * @return $data A cleaned up array with only public data. * @throws InvalidArgumentException If a parameter has an invalid value */ public function removeNonPublicDataFromArray (array $data) { // Check parameter /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: data()=%d - CALLED!', count($data))); if (count($data) == 0) { // Throw IAE throw new InvalidArgumentException('Parameter "data" is an empty array', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT); } // Remove non-public data (aka. sensitive) $data = FrameworkBootstrap::getDatabaseInstance()->removeNonPublicDataFromArray($data); // Return cleaned array /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: data()=%d - EXIT!', count($data))); return $data; } }