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