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;
55 * Protected constructor
57 * @param $className Name of the class
60 protected function __construct (string $className) {
61 // Call parent constructor
62 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: className=%s - CONSTRUCTED!', $className));
63 parent::__construct($className);
65 // Initialize the cache instance
66 $this->initCacheInstance();
69 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: EXIT!');
73 * Initializes the cache instance with a new object
77 private final function initCacheInstance () {
78 // Set "cache" attributes
79 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: CALLED!');
80 $this->databaseCacheEnabled = FrameworkBootstrap::getConfigurationInstance()->isEnabled('database_cache');
82 // Is the cache enabled?
83 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
84 if ($this->databaseCacheEnabled === true) {
85 // Set the new instance
86 $this->setCacheInstance(ObjectFactory::createObjectByConfiguredName('cache_class'));
90 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: EXIT!');
94 * Setter for table name
96 * @param $tableName Name of table name to set
99 protected final function setTableName (string $tableName) {
100 $this->tableName = $tableName;
104 * Getter for table name
106 * @return $tableName Name of table name to set
108 protected final function getTableName () {
109 return $this->tableName;
113 * Gets a cache key from Criteria instance
115 * @param $criteriaInstance An instance of a Criteria class
116 * @param $onlyKeys Only use these keys for a cache key
117 * @return $cacheKey A cache key suitable for lookup/storage purposes
119 protected function getCacheKeyByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) {
121 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys)));
122 $cacheKey = sprintf('%s@%s',
124 $criteriaInstance->getCacheKey($onlyKeys)
128 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-FRAMEWORK-SYSTEM: cacheKey=' . $cacheKey . ' - EXIT!');
133 * 'Inserts' a data set instance into a local file database folder
135 * @param $dataSetInstance A storeable data set
136 * @param $onlyKeys Only use these keys for a cache key
139 protected function queryInsertDataSet (StoreableCriteria $dataSetInstance, array $onlyKeys = []) {
140 // Default cache key is NULL
141 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: dataSetInstance=%s,onlyKeys()=%d - CALLED!', $dataSetInstance->__toString(), count($onlyKeys)));
145 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
146 if ($this->databaseCacheEnabled === true) {
147 // First get a key suitable for our cache and extend it with this class name
148 $cacheKey = $this->getCacheKeyByCriteria($dataSetInstance, $onlyKeys);
149 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: Using cache key %s for purging ...', $cacheKey));
152 // Does this key exists in cache?
153 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey[%s]=%s', intval($this->databaseCacheEnabled), gettype($cacheKey), $cacheKey));
154 if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey))) {
156 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: Invoking this->cacheInstance->purgeOffset(%s) ...', $cacheKey));
157 $this->getCacheInstance()->purgeOffset($cacheKey);
160 // Handle it over to the middleware
161 FrameworkBootstrap::getDatabaseInstance()->queryInsertDataSet($dataSetInstance);
164 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: EXIT!');
168 * 'Updates' a data set instance with a database layer
170 * @param $dataSetInstance A storeable data set
171 * @param $onlyKeys Only use these keys for a cache key
174 protected function queryUpdateDataSet (StoreableCriteria $dataSetInstance, array $onlyKeys = []) {
176 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: dataSetInstance=%s,onlyKeys()=%d - CALLED!', $dataSetInstance->__toString(), count($onlyKeys)));
180 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
181 if ($this->databaseCacheEnabled === true) {
182 // First get a key suitable for our cache and extend it with this class name
183 $cacheKey = $this->getCacheKeyByCriteria($dataSetInstance, $onlyKeys);
184 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('BASE-DATABASE-FRONTEND: Using cache key ' . $cacheKey . ' for purging ...');
187 // Does this key exists in cache?
188 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey=%s', intval($this->databaseCacheEnabled), $cacheKey));
189 if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey))) {
191 $this->getCacheInstance()->purgeOffset($cacheKey);
194 // Handle it over to the middleware
195 FrameworkBootstrap::getDatabaseInstance()->queryUpdateDataSet($dataSetInstance);
198 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: EXIT!');
202 * Getter for index key
204 * @return $indexKey Index key
206 public final function getIndexKey () {
207 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: CALLED!');
208 return FrameworkBootstrap::getDatabaseInstance()->getIndexKey();
212 * Getter for last exception
214 * @return $lastException Last exception or NULL if none occured
216 public final function getLastException () {
217 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: CALLED!');
218 return FrameworkBootstrap::getDatabaseInstance()->getLastException();
222 * Do a "select" query on the current table with the given search criteria and
223 * store it in cache for later usage
225 * @param $criteriaInstance An instance of a Criteria class
226 * @param $onlyKeys Only use these keys for a cache key
227 * @return $resultInstance An instance of a database result class
229 public function doSelectByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) {
230 // Default cache key if cache is not enabled
231 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys)));
235 // Is the cache enabled?
236 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
237 if ($this->databaseCacheEnabled === true) {
238 // First get a key suitable for our cache and extend it with this class name
239 $cacheKey = $this->getCacheKeyByCriteria($criteriaInstance, $onlyKeys);
242 // Does this key exists in cache?
243 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d,cacheKey[%s]=%s', intval($this->databaseCacheEnabled), gettype($cacheKey), $cacheKey));
244 if (($this->databaseCacheEnabled === true) && ($this->getCacheInstance()->offsetExists($cacheKey, BaseDatabaseResult::RESULT_NAME_ROWS, 1))) {
245 // Then use this result
246 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: Cache used for cacheKey=%s', $cacheKey));
247 $result = $this->getCacheInstance()->offsetGet($cacheKey);
249 // Now it's time to ask the database layer for this select statement
250 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: Quering database, cacheKey=%s ...', $cacheKey));
251 $result = FrameworkBootstrap::getDatabaseInstance()->doSelectByTableCriteria($this->getTableName(), $criteriaInstance);
253 // Cache the result if not null
254 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: result[]=%s', gettype($result)));
255 if (!is_null($result)) {
257 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: this->databaseCacheEnabled=%d', intval($this->databaseCacheEnabled)));
258 if ($this->databaseCacheEnabled === true) {
259 // A valid result has returned from the database layer
260 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: Setting cacheKey=%s with result()=%d entries', $cacheKey, count($result)));
261 $this->getCacheInstance()->offsetSet($cacheKey, $result);
264 // This invalid result must be wrapped
266 BaseDatabaseResult::RESULT_NAME_STATUS => 'invalid',
267 BaseDatabaseResult::RESULT_NAME_EXCEPTION => FrameworkBootstrap::getDatabaseInstance()->getLastException(),
272 // Create an instance of a CachedDatabaseResult class with the given result
273 // @TODO Minor: Update above comment to e.g. BaseDatabaseResult
274 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(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])));
275 $resultInstance = ObjectFactory::createObjectByConfiguredName('database_result_class', array($result));
277 // And return the instance
278 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: resultInstance=%s - EXIT!', $resultInstance->__toString()));
279 return $resultInstance;
283 * Count the numbers of rows we shall receive
285 * @param $criteriaInstance An instance of a Criteria class
286 * @param $onlyKeys Only use these keys for a cache key
287 * @return $numRows Numbers of rows of database entries
289 public function doSelectCountByCriteria (Criteria $criteriaInstance, array $onlyKeys = []) {
290 // Total numbers is -1 so we can distinglish between failed and valid queries
291 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: criteriaInstance=%s,onlyKeys()=%d - CALLED!', $criteriaInstance->__toString(), count($onlyKeys)));
294 // Get the result from above method
295 $resultInstance = $this->doSelectByCriteria($criteriaInstance, $onlyKeys);
297 // Was that query fine?
298 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-DATABASE-FRONTEND: resultInstance->ifStatusIsOkay()=%d', $resultInstance->ifStatusIsOkay()));
299 if ($resultInstance->ifStatusIsOkay()) {
300 // Then get the number of rows
301 $numRows = $resultInstance->getAffectedRows();
305 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: numRows=%d - EXIT!', $numRows));
310 * Generates a primary key for this database frontend
312 * @return $primaryKey Primary key used in wrapped table
314 public final function generatePrimaryKey () {
315 // Get the table name and a database instance and ask for it
316 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: CALLED!');
317 $primaryKey = FrameworkBootstrap::getDatabaseInstance()->getPrimaryKeyOfTable($this->getTableName());
320 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: primaryKey=%s - EXIT!', $primaryKey));
325 * Count rows of this table
327 * @return $count Count of total rows in this table
329 public final function countTotalRows () {
330 // Get the table name and a database instance and ask for it
331 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-DATABASE-FRONTEND: CALLED!');
332 $count = FrameworkBootstrap::getDatabaseInstance()->countTotalRows($this->getTableName());
335 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: count=%d - EXIT!', $count));
340 * Removes non-public data from given array.
342 * @param $data An array with possible non-public data that needs to be removed.
343 * @return $data A cleaned up array with only public data.
344 * @throws InvalidArgumentException If a parameter has an invalid value
346 public function removeNonPublicDataFromArray (array $data) {
348 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: data()=%d - CALLED!', count($data)));
349 if (count($data) == 0) {
351 throw new InvalidArgumentException('Parameter "data" is an empty array', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
354 // Remove non-public data (aka. sensitive)
355 $data = FrameworkBootstrap::getDatabaseInstance()->removeNonPublicDataFromArray($data);
357 // Return cleaned array
358 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-DATABASE-FRONTEND: data()=%d - EXIT!', count($data)));