3 namespace Org\Mxchange\CoreFramework\Database\Frontend;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Criteria\Criteria;
8 use Org\Mxchange\CoreFramework\Criteria\Storing\StoreableCriteria;
9 use Org\Mxchange\CoreFramework\Database\Backend\BaseDatabaseBackend;
10 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
11 use Org\Mxchange\CoreFramework\Object\BaseFrameworkSystem;
12 use Org\Mxchange\CoreFramework\Result\Database\BaseDatabaseResult;
13 use Org\Mxchange\CoreFramework\Traits\Cache\CacheableTrait;
16 use \InvalidArgumentException;
19 * A generic database frontend
21 * @author Roland Haeder <webmaster@shipsimu.org>
23 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
24 * @license GNU GPL 3.0 or any newer version
25 * @link http://www.shipsimu.org
27 * This program is free software: you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation, either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program. If not, see <http://www.gnu.org/licenses/>.
40 abstract class BaseDatabaseFrontend extends BaseFrameworkSystem {
45 * Current table name to use
47 private $tableName = 'unknown';
50 * "Cached" value 'database_cache_enabled' from configuration
52 private $databaseCacheEnabled = false;
54 * Protected constructor
56 * @param $className Name of the class
59 protected function __construct (string $className) {
60 // Call parent constructor
61 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CONSTRUCTED!');
62 parent::__construct($className);
64 // Initialize the cache instance
65 $this->initCacheInstance();
68 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!');
72 * Initializes the cache instance with a new object
76 private final function initCacheInstance () {
77 // Set "cache" attributes
78 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!');
79 $this->databaseCacheEnabled = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('database_cache_enabled');
81 // Is the cache enabled?
82 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
83 if ($this->databaseCacheEnabled === true) {
84 // Set the new instance
85 $this->setCacheInstance(ObjectFactory::createObjectByConfiguredName('cache_class'));
89 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!');
93 * Setter for table name
95 * @param $tableName Name of table name to set
98 protected final function setTableName (string $tableName) {
99 $this->tableName = $tableName;
103 * Getter for table name
105 * @return $tableName Name of table name to set
107 protected final function getTableName () {
108 return $this->tableName;
112 * Gets a cache key from Criteria instance
114 * @param $criteriaInstance An instance of a Criteria class
115 * @param $onlyKeys Only use these keys for a cache key
116 * @return $cacheKey A cache key suitable for lookup/storage purposes
118 protected function getCacheKeyByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) {
120 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys)));
121 $cacheKey = sprintf('%s@%s',
123 $criteriaInstance->getCacheKey($onlyKeys)
127 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FRAMEWORK-SYSTEM: cacheKey=' . $cacheKey . ' - EXIT!');
132 * 'Inserts' a data set instance into a local file database folder
134 * @param $dataSetInstance A storeable data set
135 * @param $onlyKeys Only use these keys for a cache key
138 protected function queryInsertDataSet (StoreableCriteria $dataSetInstance, array $onlyKeys = []) {
139 // Default cache key is NULL
140 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: dataSetInstance=%s,onlyKeys()=%d - CALLED!', $dataSetInstance->__toString(), count($onlyKeys)));
144 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
145 if ($this->databaseCacheEnabled === true) {
146 // First get a key suitable for our cache and extend it with this class name
147 $cacheKey = $this->getCacheKeyByCriteria($dataSetInstance, $onlyKeys);
148 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Using cache key %s for purging ...', $cacheKey));
151 // Does this key exists in cache?
152 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey[%s]=%s', intval($this->databaseCacheEnabled), gettype($cacheKey), $cacheKey));
153 if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey))) {
155 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Invoking this->cacheInstance->purgeOffset(%s) ...', $cacheKey));
156 $this->getCacheInstance()->purgeOffset($cacheKey);
159 // Handle it over to the middleware
160 FrameworkBootstrap::getDatabaseInstance()->queryInsertDataSet($dataSetInstance);
163 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!');
167 * 'Updates' a data set instance with a database layer
169 * @param $dataSetInstance A storeable data set
170 * @param $onlyKeys Only use these keys for a cache key
173 protected function queryUpdateDataSet (StoreableCriteria $dataSetInstance, array $onlyKeys = []) {
175 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: dataSetInstance=%s,onlyKeys()=%d - CALLED!', $dataSetInstance->__toString(), count($onlyKeys)));
179 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
180 if ($this->databaseCacheEnabled === true) {
181 // First get a key suitable for our cache and extend it with this class name
182 $cacheKey = $this->getCacheKeyByCriteria($dataSetInstance, $onlyKeys);
183 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: Using cache key ' . $cacheKey . ' for purging ...');
186 // Does this key exists in cache?
187 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey=%s', intval($this->databaseCacheEnabled), $cacheKey));
188 if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey))) {
190 $this->getCacheInstance()->purgeOffset($cacheKey);
193 // Handle it over to the middleware
194 FrameworkBootstrap::getDatabaseInstance()->queryUpdateDataSet($dataSetInstance);
197 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: EXIT!');
201 * Getter for index key
203 * @return $indexKey Index key
205 public final function getIndexKey () {
206 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!');
207 return FrameworkBootstrap::getDatabaseInstance()->getIndexKey();
211 * Getter for last exception
213 * @return $lastException Last exception or NULL if none occured
215 public final function getLastException () {
216 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!');
217 return FrameworkBootstrap::getDatabaseInstance()->getLastException();
221 * Do a "select" query on the current table with the given search criteria and
222 * store it in cache for later usage
224 * @param $criteriaInstance An instance of a Criteria class
225 * @param $onlyKeys Only use these keys for a cache key
226 * @return $resultInstance An instance of a database result class
228 public function doSelectByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) {
229 // Default cache key if cache is not enabled
230 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys)));
234 // Is the cache enabled?
235 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
236 if ($this->databaseCacheEnabled === true) {
237 // First get a key suitable for our cache and extend it with this class name
238 $cacheKey = $this->getCacheKeyByCriteria($criteriaInstance, $onlyKeys);
241 // Does this key exists in cache?
242 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey[%s]=%s', intval($this->databaseCacheEnabled), gettype($cacheKey), $cacheKey));
243 if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey, BaseDatabaseResult::RESULT_NAME_ROWS, 1))) {
244 // Then use this result
245 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Cache used for cacheKey=%s', $cacheKey));
246 $result = $this->getCacheInstance()->offsetGet($cacheKey);
248 // Now it's time to ask the database layer for this select statement
249 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Quering database, cacheKey=%s ...', $cacheKey));
250 $result = FrameworkBootstrap::getDatabaseInstance()->doSelectByTableCriteria($this->getTableName(), $criteriaInstance);
252 // Cache the result if not null
253 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: result[]=%s', gettype($result)));
254 if (!is_null($result)) {
256 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
257 if ($this->databaseCacheEnabled === true) {
258 // A valid result has returned from the database layer
259 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: Setting cacheKey=%s with result()=%d entries', $cacheKey, count($result)));
260 $this->getCacheInstance()->offsetSet($cacheKey, $result);
263 // This invalid result must be wrapped
265 BaseDatabaseResult::RESULT_NAME_STATUS => 'invalid',
266 BaseDatabaseResult::RESULT_NAME_EXCEPTION => FrameworkBootstrap::getDatabaseInstance()->getLastException()
271 // Create an instance of a CachedDatabaseResult class with the given result
272 // @TODO Minor: Update above comment to e.g. BaseDatabaseResult
273 //* 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])));
274 $resultInstance = ObjectFactory::createObjectByConfiguredName('database_result_class', array($result));
276 // And return the instance
277 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: resultInstance=%s - EXIT!', $resultInstance->__toString()));
278 return $resultInstance;
282 * Count the numbers of rows we shall receive
284 * @param $criteriaInstance An instance of a Criteria class
285 * @param $onlyKeys Only use these keys for a cache key
286 * @return $numRows Numbers of rows of database entries
288 public function doSelectCountByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) {
289 // Total numbers is -1 so we can distinglish between failed and valid queries
290 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys)));
293 // Get the result from above method
294 $resultInstance = $this->doSelectByCriteria($criteriaInstance, $onlyKeys);
296 // Was that query fine?
297 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: resultInstance->ifStatusIsOkay()=%d', $resultInstance->ifStatusIsOkay()));
298 if ($resultInstance->ifStatusIsOkay()) {
299 // Then get the number of rows
300 $numRows = $resultInstance->getAffectedRows();
304 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: numRows=%d - EXIT!', $numRows));
309 * Generates a primary key for this database frontend
311 * @return $primaryKey Primary key used in wrapped table
313 public final function generatePrimaryKey () {
314 // Get the table name and a database instance and ask for it
315 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!');
316 $primaryKey = FrameworkBootstrap::getDatabaseInstance()->getPrimaryKeyOfTable($this->getTableName());
319 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: primaryKey=%s - EXIT!', $primaryKey));
324 * Count rows of this table
326 * @return $count Count of total rows in this table
328 public final function countTotalRows () {
329 // Get the table name and a database instance and ask for it
330 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-DATABASE-FRONTEND: CALLED!');
331 $count = FrameworkBootstrap::getDatabaseInstance()->countTotalRows($this->getTableName());
334 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: count=%d - EXIT!', $count));
339 * Removes non-public data from given array.
341 * @param $data An array with possible non-public data that needs to be removed.
342 * @return $data A cleaned up array with only public data.
343 * @throws InvalidArgumentException If a parameter has an invalid value
345 public function removeNonPublicDataFromArray (array $data) {
347 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: data()=%d - CALLED!', count($data)));
348 if (count($data) == 0) {
350 throw new InvalidArgumentException('Parameter "data" is an empty array', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
353 // Remove non-public data (aka. sensitive)
354 $data = FrameworkBootstrap::getDatabaseInstance()->removeNonPublicDataFromArray($data);
356 // Return cleaned array
357 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-DATABASE-FRONTEND: data()=%d - EXIT!', count($data)));