]> git.mxchange.org Git - core.git/blob - framework/main/classes/database/result/class_CachedDatabaseResult.php
Continued:
[core.git] / framework / main / classes / database / result / class_CachedDatabaseResult.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Result\Database;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Criteria\Local\LocalSearchCriteria;
7 use Org\Mxchange\CoreFramework\Criteria\Local\LocalUpdateCriteria;
8 use Org\Mxchange\CoreFramework\Criteria\Storing\StoreableCriteria;
9 use Org\Mxchange\CoreFramework\Database\Frontend\DatabaseFrontend;
10 use Org\Mxchange\CoreFramework\Database\Backend\BaseDatabaseBackend;
11 use Org\Mxchange\CoreFramework\Result\Database\BaseDatabaseResult;
12 use Org\Mxchange\CoreFramework\Result\Search\SearchableResult;
13 use Org\Mxchange\CoreFramework\Result\Update\UpdateableResult;
14
15 // Import SPL stuff
16 use \InvalidArgumentException;
17 use \SeekableIterator;
18
19 /**
20  * A database result class
21  *
22  * @author              Roland Haeder <webmaster@shipsimu.org>
23  * @version             0.0.0
24  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
25  * @license             GNU GPL 3.0 or any newer version
26  * @link                http://www.shipsimu.org
27  *
28  * This program is free software: you can redistribute it and/or modify
29  * it under the terms of the GNU General Public License as published by
30  * the Free Software Foundation, either version 3 of the License, or
31  * (at your option) any later version.
32  *
33  * This program is distributed in the hope that it will be useful,
34  * but WITHOUT ANY WARRANTY; without even the implied warranty of
35  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
36  * GNU General Public License for more details.
37  *
38  * You should have received a copy of the GNU General Public License
39  * along with this program. If not, see <http://www.gnu.org/licenses/>.
40  */
41 class CachedDatabaseResult extends BaseDatabaseResult implements SearchableResult, UpdateableResult, SeekableIterator {
42         // Exception constants
43         const EXCEPTION_INVALID_DATABASE_RESULT = 0x1c0;
44         const EXCEPTION_RESULT_UPDATE_FAILED    = 0x1c1;
45
46         /**
47          * Current position in array
48          */
49         private $currentPos = -1;
50
51         /**
52          * Current row
53          */
54         private $currentRow = NULL;
55
56         /**
57          * Result array
58          */
59         private $resultArray = [];
60
61         /**
62          * Array of out-dated entries
63          */
64         private $outDated = [];
65
66         /**
67          * Affected rows
68          */
69         private $affectedRows = 0;
70
71         /**
72          * Found value
73          */
74         private $foundValue = '';
75
76         /**
77          * Protected constructor
78          *
79          * @return      void
80          */
81         protected function __construct () {
82                 // Call parent constructor
83                 parent::__construct(__CLASS__);
84         }
85
86         /**
87          * Creates an instance of this result by a provided result array
88          *
89          * @param       $resultArray            The array holding the result from query
90          * @return      $resultInstance         An instance of this class
91          * @throws      InvalidArgumentException        If a parameter is invalid
92          */
93         public static final function createCachedDatabaseResult (array $resultArray) {
94                 // Misses an element?
95                 if (count($resultArray) == 0) {
96                         // Cannot be empty
97                         throw new InvalidArgumentException('Array "resultArray" cannot be empty.');
98                 } elseif (!array_key_exists(BaseDatabaseResult::RESULT_NAME_ROWS, $resultArray)) {
99                         // Yes, then abort here
100                         throw new InvalidArgumentException(sprintf('resultArray(%d)=%s has no element "%s".', count($resultArray), print_r($resultArray, TRUE), BaseDatabaseResult::RESULT_NAME_ROWS));
101                 }
102
103                 // Get a new instance
104                 $resultInstance = new CachedDatabaseResult();
105
106                 // Set the result array and reset current position
107                 $resultInstance->setResultArray($resultArray);
108                 $resultInstance->resetCurrentPosition();
109
110                 // Set affected rows
111                 $resultInstance->setAffectedRows(count($resultArray[BaseDatabaseResult::RESULT_NAME_ROWS]));
112
113                 // Return the instance
114                 return $resultInstance;
115         }
116
117         /**
118          * Setter for result array
119          *
120          * @param       $resultArray    The array holding the result from query
121          * @return      void
122          */
123         protected final function setResultArray (array $resultArray) {
124                 $this->resultArray = $resultArray;
125         }
126
127         /**
128          * Updates the current entry by given update criteria
129          *
130          * @param       $updateInstance         An instance of an Updateable criteria
131          * @return      void
132          */
133         private function updateCurrentEntryByCriteria (LocalUpdateCriteria $updateInstance) {
134                 // Get the current entry key
135                 $entryKey = $this->key();
136
137                 // Now get the update criteria array and update all entries
138                 foreach ($updateInstance->getUpdateCriteria() as $criteriaKey => $criteriaValue) {
139                         // Update data
140                         $this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS][$entryKey][$criteriaKey] = $criteriaValue;
141
142                         // Mark it as out-dated
143                         $this->outDated[$criteriaKey] = 1;
144                 }
145         }
146
147         /**
148          * "Iterator" method next() to advance to the next valid entry. This method
149          * does also check if result is invalid
150          *
151          * @return      $nextValid      Whether the next entry is valid
152          */
153         public function next () {
154                 // Default is not valid
155                 $nextValid = false;
156
157                 // Increase position
158                 $this->currentPos++;
159
160                 // Is the result valid?
161                 if ($this->valid()) {
162                         // Next entry found, so cache it
163                         $this->currentRow = $this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS][$this->currentPos];
164                         $nextValid = true;
165                 }
166
167                 // Return the result
168                 return $nextValid;
169         }
170
171         /**
172          * Seeks for to a specified position
173          *
174          * @param       $index  Index to seek for
175          * @return      void
176          */
177         public function seek ($index) {
178                 // Rewind to beginning
179                 $this->rewind();
180
181                 // Search for the entry
182                 while (($this->currentPos < $index) && ($this->valid())) {
183                         // Continue on
184                         $this->next();
185                 }
186         }
187
188         /**
189          * Gives back the current position or null if not found
190          *
191          * @return      $current        Current element to give back
192          */
193         public function current () {
194                 // Default is not found
195                 $current = NULL;
196
197                 // Does the current enty exist?
198                 if (isset($this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS][$this->currentPos])) {
199                         // Then get it
200                         $current = $this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS][$this->currentPos];
201                 }
202
203                 // Return the result
204                 return $current;
205         }
206
207         /**
208          * Checks if next() and rewind will give a valid result
209          *
210          * @return      $isValid Whether the next/rewind entry is valid
211          */
212         public function valid () {
213                 // Check if all is fine ...
214                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
215                 $isValid = ($this->ifStatusIsOkay() && isset($this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS][$this->currentPos]) && isset($this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS][0]));
216
217                 // Return the result
218                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: isValid=%d - EXIT!', intval($isValid)));
219                 return $isValid;
220         }
221
222         /**
223          * Returns count of entries
224          *
225          * @return      $isValid Whether the next/rewind entry is valid
226          */
227         public function count () {
228                 // Count rows
229                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: CALLED!');
230                 $count = count($this->resultArray[BaseDatabaseResult::RESULT_NAME_ROWS]);
231
232                 // Return it
233                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: count=%d - EXIT!', $count));
234                 return $count;
235         }
236
237         /**
238          * Determines whether the status of the query was fine (BaseDatabaseBackend::RESULT_OKAY)
239          *
240          * @return      $ifStatusOkay   Whether the status of the query was okay
241          */
242         public function ifStatusIsOkay () {
243                 // Check all conditions
244                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
245                 $ifStatusOkay = (isset($this->resultArray[BaseDatabaseResult::RESULT_NAME_STATUS]) && $this->resultArray[BaseDatabaseResult::RESULT_NAME_STATUS] === BaseDatabaseBackend::RESULT_OKAY);
246
247                 // Return status
248                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: ifStatusOkay=%s - EXIT!', intval($ifStatusOkay)));
249                 return $ifStatusOkay;
250         }
251
252         /**
253          * Gets the current key of iteration
254          *
255          * @return      $currentPos     Key from iterator
256          */
257         public function key () {
258                 // Return current array position
259                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
260                 return $this->currentPos;
261         }
262
263         /**
264          * Rewind to the beginning and clear array $currentRow
265          *
266          * @return      void
267          */
268         public function rewind () {
269                 // Reset both current array position and current row
270                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('CACHED-DATABASE-RESULT: this->currentPos=%d - CALLED!', $this->currentPos));
271                 $this->resetCurrentPosition();
272                 $this->currentRow = [];
273
274                 // Trace message
275                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: EXIT!');
276         }
277
278         /**
279          * Resets current array position to 0 if at least one record is there or -1
280          * if no record is there.
281          *
282          * @return      void
283          */
284         private function resetCurrentPosition () {
285                 // Reset position
286                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: CALLED!');
287                 $this->currentPos = ($this->count() > 0 ? 0 : -1);
288
289                 // Trace message
290                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('CACHED-DATABASE-RESULT: EXIT!');
291         }
292
293         /**
294          * Searches for an entry in data result and returns it
295          *
296          * @param       $criteriaInstance       The criteria to look inside the data set
297          * @return      $result                         Found result entry
298          * @todo        0% done
299          */
300         public function searchEntry (LocalSearchCriteria $criteriaInstance) {
301                 // Unfinished code
302                 $this->debugBackTrace(sprintf('[%s:%d]: criteriaInstance=%s', __METHOD__, __LINE__, print_r($criteriaInstance, TRUE)));
303         }
304
305         /**
306          * Adds an update request to the database result for writing it to the
307          * database layer
308          *
309          * @param       $updateInstance An instance of a updateable criteria
310          * @return      void
311          * @throws      ResultUpdateException   If no result was updated
312          */
313         public function add2UpdateQueue (LocalUpdateCriteria $updateInstance) {
314                 // Rewind the pointer
315                 $this->rewind();
316
317                 // Get search criteria
318                 $searchInstance = $updateInstance->getSearchInstance();
319
320                 // And start looking for the result
321                 $foundEntries = $this->getAffectedRows();
322                 while (($this->valid()) && ($foundEntries < $searchInstance->getLimit())) {
323                         // Get next entry
324                         $this->next();
325                         $currentEntry = $this->current();
326
327                         // Is this entry found?
328                         if ($searchInstance->ifEntryMatches($currentEntry)) {
329                                 // Update this entry
330                                 $this->updateCurrentEntryByCriteria($updateInstance);
331
332                                 // Count one up
333                                 $foundEntries++;
334                         }
335                 }
336
337                 // If no entry is found/updated throw an exception
338                 if ($foundEntries == 0) {
339                         // Throw an exception here
340                         throw new ResultUpdateException($this, self::EXCEPTION_RESULT_UPDATE_FAILED);
341                 }
342
343                 // Set affected rows
344                 $this->setAffectedRows($foundEntries);
345
346                 // Set update instance
347                 $this->setUpdateInstance($updateInstance);
348         }
349
350         /**
351          * Setter for affected rows
352          *
353          * @param       $rows   Number of affected rows
354          * @return      void
355          */
356         public final function setAffectedRows (int $rows) {
357                 $this->affectedRows = $rows;
358         }
359
360         /**
361          * Getter for affected rows
362          *
363          * @return      $rows   Number of affected rows
364          */
365         public final function getAffectedRows () {
366                 return $this->affectedRows;
367         }
368
369         /**
370          * Getter for found value of previous found() call
371          *
372          * @return      $foundValue             Found value of previous found() call
373          */
374         public final function getFoundValue () {
375                 return $this->foundValue;
376         }
377
378         /**
379          * Checks whether we have out-dated entries or not
380          *
381          * @return      $needsUpdate    Whether we have out-dated entries
382          */
383         public function ifDataNeedsFlush () {
384                 $needsUpdate = (count($this->outDated) > 0);
385                 return $needsUpdate;
386         }
387
388         /**
389          * Adds registration elements to a given dataset instance
390          *
391          * @param       $criteriaInstance       An instance of a StoreableCriteria class
392          * @return      void
393          */
394         public function addElementsToDataSet (StoreableCriteria $criteriaInstance) {
395                 // Walk only through out-dated columns
396                 foreach ($this->outDated as $key => $dummy) {
397                         // Does this key exist?
398                         if ($this->find($key)) {
399                                 // Then update it
400                                 $criteriaInstance->addCriteria($key, $this->getFoundValue());
401                         }
402                 }
403         }
404
405         /**
406          * Find a key inside the result array
407          *
408          * @param       $key    The key we shall find
409          * @return      $found  Whether the key was found or not
410          */
411         public function find (string $key) {
412                 // By default nothing is found
413                 $found = false;
414
415                 // Rewind the pointer
416                 $this->rewind();
417
418                 // Walk through all entries
419                 while ($this->valid()) {
420                         // Advance to next entry
421                         $this->next();
422
423                         // Get the whole array
424                         $currentEntry = $this->current();
425
426                         // Is the element there?
427                         if (isset($currentEntry[$key])) {
428                                 // Okay, found!
429                                 $found = true;
430
431                                 // So "cache" it
432                                 $this->foundValue = $currentEntry[$key];
433
434                                 // And stop searching
435                                 break;
436                         }
437                 }
438
439                 // Return the result
440                 return $found;
441         }
442
443         /**
444          * Solver for result index value with call-back method
445          *
446          * @param       $databaseColumn         Database column where the index might be found
447          * @param       $frontendInstance       The frontend instance to ask for array element
448          * @para        $callBack                       Call-back object for setting the index;
449          *                                                              0=object instance,1=method name
450          * @return      void
451          * @todo        Find a caching way without modifying the result array
452          */
453         public function solveResultIndex (string $databaseColumn, DatabaseFrontend $frontendInstance, array $callBack) {
454                 // By default nothing is found
455                 $indexValue = 0;
456
457                 // Is the element in result itself found?
458                 if ($this->find($databaseColumn)) {
459                         // Use this value
460                         $indexValue = $this->getFoundValue();
461                 } elseif ($this->find($frontendInstance->getIndexKey())) {
462                         // Use this value
463                         $indexValue = $this->getFoundValue();
464                 }
465
466                 // Set the index
467                 call_user_func_array($callBack, array($indexValue));
468         }
469
470 }