ArangoDB-PHP API Documentation
  • Namespace
  • Class
  • Deprecated

Namespaces

  • triagens
    • ArangoDb

Classes

  • triagens\ArangoDb\AdminHandler
  • triagens\ArangoDb\AqlUserFunction
  • triagens\ArangoDb\Autoloader
  • triagens\ArangoDb\Batch
  • triagens\ArangoDb\BatchPart
  • triagens\ArangoDb\BindVars
  • triagens\ArangoDb\Collection
  • triagens\ArangoDb\CollectionHandler
  • triagens\ArangoDb\Connection
  • triagens\ArangoDb\ConnectionOptions
  • triagens\ArangoDb\Cursor
  • triagens\ArangoDb\Database
  • triagens\ArangoDb\DefaultValues
  • triagens\ArangoDb\Document
  • triagens\ArangoDb\DocumentHandler
  • triagens\ArangoDb\Edge
  • triagens\ArangoDb\EdgeDefinition
  • triagens\ArangoDb\EdgeHandler
  • triagens\ArangoDb\Endpoint
  • triagens\ArangoDb\Export
  • triagens\ArangoDb\ExportCursor
  • triagens\ArangoDb\Graph
  • triagens\ArangoDb\GraphHandler
  • triagens\ArangoDb\Handler
  • triagens\ArangoDb\HttpHelper
  • triagens\ArangoDb\HttpResponse
  • triagens\ArangoDb\QueryCacheHandler
  • triagens\ArangoDb\QueryHandler
  • triagens\ArangoDb\Statement
  • triagens\ArangoDb\TraceRequest
  • triagens\ArangoDb\TraceResponse
  • triagens\ArangoDb\Transaction
  • triagens\ArangoDb\Traversal
  • triagens\ArangoDb\UpdatePolicy
  • triagens\ArangoDb\UrlHelper
  • triagens\ArangoDb\Urls
  • triagens\ArangoDb\User
  • triagens\ArangoDb\UserHandler
  • triagens\ArangoDb\ValueValidator
  • triagens\ArangoDb\Vertex
  • triagens\ArangoDb\VertexHandler

Exceptions

  • triagens\ArangoDb\ClientException
  • triagens\ArangoDb\ConnectException
  • triagens\ArangoDb\Exception
  • triagens\ArangoDb\ServerException
  1 <?php
  2 
  3 /**
  4  * ArangoDB PHP client: result set cursor
  5  *
  6  * @package   triagens\ArangoDb
  7  * @author    Jan Steemann
  8  * @copyright Copyright 2012, triagens GmbH, Cologne, Germany
  9  */
 10 
 11 namespace triagens\ArangoDb;
 12 
 13 /**
 14  * Provides access to the results of an AQL query or another statement
 15  *
 16  * The cursor might not contain all results in the beginning.<br>
 17  *
 18  * If the result set is too big to be transferred in one go, the
 19  * cursor might issue additional HTTP requests to fetch the
 20  * remaining results from the server.
 21  *
 22  * @package   triagens\ArangoDb
 23  * @since     0.2
 24  */
 25 class Cursor implements
 26     \Iterator
 27 {
 28     /**
 29      * The connection object
 30      *
 31      * @var Connection
 32      */
 33     private $_connection;
 34     /**
 35      * Cursor options
 36      *
 37      * @var array
 38      */
 39     private $_options;
 40 
 41     /**
 42      * Result Data
 43      *
 44      * @var array
 45      */
 46     private $data;
 47 
 48     /**
 49      * The result set
 50      *
 51      * @var array
 52      */
 53     private $_result;
 54 
 55     /**
 56      * "has more" indicator - if true, the server has more results
 57      *
 58      * @var bool
 59      */
 60     private $_hasMore;
 61 
 62     /**
 63      * cursor id - might be NULL if cursor does not have an id
 64      *
 65      * @var mixed
 66      */
 67     private $_id;
 68 
 69     /**
 70      * current position in result set iteration (zero-based)
 71      *
 72      * @var int
 73      */
 74     private $_position;
 75 
 76     /**
 77      * total length of result set (in number of documents)
 78      *
 79      * @var int
 80      */
 81     private $_length;
 82 
 83     /**
 84      * full count of the result set (ignoring the outermost LIMIT)
 85      *
 86      * @var int
 87      */
 88     private $_fullCount;
 89 
 90     /**
 91      * extra data (statistics) returned from the statement
 92      *
 93      * @var array
 94      */
 95     private $_extra;
 96 
 97     /**
 98      * number of HTTP calls that were made to build the cursor result
 99      */
100     private $_fetches = 1;
101 
102     /**
103      * whether or not the query result was served from the AQL query result cache
104      */
105     private $_cached;
106 
107     /**
108      * result entry for cursor id
109      */
110     const ENTRY_ID = 'id';
111 
112     /**
113      * result entry for "hasMore" flag
114      */
115     const ENTRY_HASMORE = 'hasMore';
116 
117     /**
118      * result entry for result documents
119      */
120     const ENTRY_RESULT = 'result';
121 
122     /**
123      * result entry for extra data
124      */
125     const ENTRY_EXTRA = 'extra';
126 
127     /**
128      * result entry for stats
129      */
130     const ENTRY_STATS = 'stats';
131 
132     /**
133      * result entry for the full count (ignoring the outermost LIMIT)
134      */
135     const FULL_COUNT = 'fullCount';
136 
137     /**
138      * cache option entry
139      */
140     const ENTRY_CACHE = 'cache';
141 
142     /**
143      * cached result attribute - whether or not the result was served from the AQL query cache
144      */
145     const ENTRY_CACHED = 'cached';
146 
147     /**
148      * sanitize option entry
149      */
150     const ENTRY_SANITIZE = '_sanitize';
151 
152     /**
153      * "flat" option entry (will treat the results as a simple array, not documents)
154      */
155     const ENTRY_FLAT = '_flat';
156 
157     /**
158      * "objectType" option entry.
159      */
160     const ENTRY_TYPE = 'objectType';
161 
162     /**
163      * "baseurl" option entry.
164      */
165     const ENTRY_BASEURL = 'baseurl';
166 
167     /**
168      * Initialise the cursor with the first results and some metadata
169      *
170      * @param Connection $connection - connection to be used
171      * @param array      $data       - initial result data as returned by the server
172      * @param array      $options    - cursor options
173      *
174      * @throws \triagens\ArangoDb\ClientException
175      */
176     public function __construct(Connection $connection, array $data, array $options)
177     {
178         $this->_connection = $connection;
179         $this->data        = $data;
180         $this->_id         = null;
181         $this->_extra      = [];
182         $this->_cached     = false;
183 
184         if (isset($data[self::ENTRY_ID])) {
185             $this->_id = $data[self::ENTRY_ID];
186         }
187 
188         if (isset($data[self::ENTRY_EXTRA])) {
189             // ArangoDB 2.3+ return value struct
190             $this->_extra = $data[self::ENTRY_EXTRA];
191 
192             if (isset($this->_extra[self::ENTRY_STATS][self::FULL_COUNT])) {
193                 $this->_fullCount = $this->_extra[self::ENTRY_STATS][self::FULL_COUNT];
194             }
195         } else if (isset($data[self::ENTRY_EXTRA][self::FULL_COUNT])) {
196             // pre-ArangoDB 2.3 return value struct
197             $this->_fullCount = $data[self::ENTRY_EXTRA][self::FULL_COUNT];
198         }
199 
200         if (isset($data[self::ENTRY_CACHED])) {
201             $this->_cached = $data[self::ENTRY_CACHED];
202         }
203 
204         // attribute must be there
205         assert(isset($data[self::ENTRY_HASMORE]));
206         $this->_hasMore = (bool) $data[self::ENTRY_HASMORE];
207 
208         $options['isNew'] = false;
209         $this->_options   = $options;
210         $this->_result    = [];
211         $this->add((array) $data[self::ENTRY_RESULT]);
212         $this->updateLength();
213 
214         $this->rewind();
215     }
216 
217 
218     /**
219      * Explicitly delete the cursor
220      *
221      * This might issue an HTTP DELETE request to inform the server about
222      * the deletion.
223      *
224      * @throws Exception
225      * @return bool - true if the server acknowledged the deletion request, false otherwise
226      */
227     public function delete()
228     {
229         if ($this->_id) {
230             try {
231                 $this->_connection->delete($this->url() . '/' . $this->_id, []);
232 
233                 return true;
234             } catch (Exception $e) {
235             }
236         }
237 
238         return false;
239     }
240 
241 
242     /**
243      * Get the total number of results in the cursor
244      *
245      * This might issue additional HTTP requests to fetch any outstanding
246      * results from the server
247      *
248      * @throws Exception
249      * @return int - total number of results
250      */
251     public function getCount()
252     {
253         while ($this->_hasMore) {
254             $this->fetchOutstanding();
255         }
256 
257         return $this->_length;
258     }
259 
260     /**
261      * Get the full count of the cursor (ignoring the outermost LIMIT)
262      *
263      * @return int - total number of results
264      */
265     public function getFullCount()
266     {
267         return $this->_fullCount;
268     }
269 
270 
271     /**
272      * Get the cached attribute for the result set
273      *
274      * @return bool - whether or not the query result was served from the AQL query cache
275      */
276     public function getCached()
277     {
278         return $this->_cached;
279     }
280 
281 
282     /**
283      * Get all results as an array
284      *
285      * This might issue additional HTTP requests to fetch any outstanding
286      * results from the server
287      *
288      * @throws Exception
289      * @return array - an array of all results
290      */
291     public function getAll()
292     {
293         while ($this->_hasMore) {
294             $this->fetchOutstanding();
295         }
296 
297         return $this->_result;
298     }
299 
300 
301     /**
302      * Rewind the cursor, necessary for Iterator
303      *
304      * @return void
305      */
306     public function rewind()
307     {
308         $this->_position = 0;
309     }
310 
311 
312     /**
313      * Return the current result row, necessary for Iterator
314      *
315      * @return array - the current result row as an assoc array
316      */
317     public function current()
318     {
319         return $this->_result[$this->_position];
320     }
321 
322 
323     /**
324      * Return the index of the current result row, necessary for Iterator
325      *
326      * @return int - the current result row index
327      */
328     public function key()
329     {
330         return $this->_position;
331     }
332 
333 
334     /**
335      * Advance the cursor, necessary for Iterator
336      *
337      * @return void
338      */
339     public function next()
340     {
341         ++$this->_position;
342     }
343 
344 
345     /**
346      * Check if cursor can be advanced further, necessary for Iterator
347      *
348      * This might issue additional HTTP requests to fetch any outstanding
349      * results from the server
350      *
351      * @throws Exception
352      * @return bool - true if the cursor can be advanced further, false if cursor is at end
353      */
354     public function valid()
355     {
356         if ($this->_position <= $this->_length - 1) {
357             // we have more results than the current position is
358             return true;
359         }
360 
361         if (!$this->_hasMore || !$this->_id) {
362             // we don't have more results, but the cursor is exhausted
363             return false;
364         }
365 
366         // need to fetch additional results from the server
367         $this->fetchOutstanding();
368 
369         return ($this->_position <= $this->_length - 1);
370     }
371 
372 
373     /**
374      * Create an array of results from the input array
375      *
376      * @param array $data - incoming result
377      *
378      * @return void
379      * @throws \triagens\ArangoDb\ClientException
380      */
381     private function add(array $data)
382     {
383         foreach ($this->sanitize($data) as $row) {
384             if (!is_array($row) || (isset($this->_options[self::ENTRY_FLAT]) && $this->_options[self::ENTRY_FLAT])) {
385                 $this->addFlatFromArray($row);
386             } else {
387                 if (!isset($this->_options['objectType'])) {
388                     $this->addDocumentsFromArray($row);
389                 } else {
390                     switch ($this->_options['objectType']) {
391                         case 'edge' :
392                             $this->addEdgesFromArray($row);
393                             break;
394                         case 'vertex' :
395                             $this->addVerticesFromArray($row);
396                             break;
397                         case 'path' :
398                             $this->addPathsFromArray($row);
399                             break;
400                         case 'shortestPath' :
401                             $this->addShortestPathFromArray($row);
402                             break;
403                         case 'distanceTo' :
404                             $this->addDistanceToFromArray($row);
405                             break;
406                         case 'commonNeighbors' :
407                             $this->addCommonNeighborsFromArray($row);
408                             break;
409                         case 'commonProperties' :
410                             $this->addCommonPropertiesFromArray($row);
411                             break;
412                         case 'figure' :
413                             $this->addFigureFromArray($row);
414                             break;
415                         default :
416                             $this->addDocumentsFromArray($row);
417                             break;
418                     }
419                 }
420             }
421         }
422     }
423 
424 
425     /**
426      * Create an array of results from the input array
427      *
428      * @param array $data - array of incoming results
429      *
430      * @return void
431      */
432     private function addFlatFromArray($data)
433     {
434         $this->_result[] = $data;
435     }
436 
437 
438     /**
439      * Create an array of documents from the input array
440      *
441      * @param array $data - array of incoming "document" arrays
442      *
443      * @return void
444      * @throws \triagens\ArangoDb\ClientException
445      */
446     private function addDocumentsFromArray(array $data)
447     {
448         $this->_result[] = Document::createFromArray($data, $this->_options);
449     }
450 
451     /**
452      * Create an array of paths from the input array
453      *
454      * @param array $data - array of incoming "paths" arrays
455      *
456      * @return void
457      * @throws \triagens\ArangoDb\ClientException
458      */
459     private function addPathsFromArray(array $data)
460     {
461         $entry = [
462             'vertices'    => [],
463             'edges'       => [],
464             'source'      => Document::createFromArray($data['source'], $this->_options),
465             'destination' => Document::createFromArray($data['destination'], $this->_options),
466         ];
467         foreach ($data['vertices'] as $v) {
468             $entry['vertices'][] = Document::createFromArray($v, $this->_options);
469         }
470         foreach ($data['edges'] as $v) {
471             $entry['edges'][] = Edge::createFromArray($v, $this->_options);
472         }
473         $this->_result[] = $entry;
474     }
475 
476     /**
477      * Create an array of shortest paths from the input array
478      *
479      * @param array $data - array of incoming "paths" arrays
480      *
481      * @return void
482      * @throws \triagens\ArangoDb\ClientException
483      */
484     private function addShortestPathFromArray(array $data)
485     {
486         if (!isset($data['vertices'])) {
487             return;
488         }
489 
490         $vertices    = $data['vertices'];
491         $startVertex = $vertices[0];
492         $destination = $vertices[count($vertices) - 1];
493 
494         $entry = [
495             'paths'       => [],
496             'source'      => Document::createFromArray($startVertex, $this->_options),
497             'distance'    => $data['distance'],
498             'destination' => Document::createFromArray($destination, $this->_options),
499         ];
500 
501         $path = [
502             'vertices' => [],
503             'edges'    => []
504         ];
505 
506         foreach ($data['vertices'] as $v) {
507             $path['vertices'][] = $v;
508         }
509         foreach ($data['edges'] as $v) {
510             $path['edges'][] = Edge::createFromArray($v, $this->_options);
511         }
512         $entry['paths'][] = $path;
513 
514         $this->_result[] = $entry;
515     }
516 
517 
518     /**
519      * Create an array of distances from the input array
520      *
521      * @param array $data - array of incoming "paths" arrays
522      *
523      * @return void
524      */
525     private function addDistanceToFromArray(array $data)
526     {
527         $entry           = [
528             'source'      => $data['startVertex'],
529             'distance'    => $data['distance'],
530             'destination' => $data['vertex']
531         ];
532         $this->_result[] = $entry;
533     }
534 
535     /**
536      * Create an array of common neighbors from the input array
537      *
538      * @param array $data - array of incoming "paths" arrays
539      *
540      * @return void
541      * @throws \triagens\ArangoDb\ClientException
542      */
543     private function addCommonNeighborsFromArray(array $data)
544     {
545         $left  = $data['left'];
546         $right = $data['right'];
547 
548         if (!isset($this->_result[$left])) {
549             $this->_result[$left] = [];
550         }
551         if (!isset($this->_result[$left][$right])) {
552             $this->_result[$left][$right] = [];
553         }
554 
555         foreach ($data['neighbors'] as $neighbor) {
556             $this->_result[$left][$right][] = Document::createFromArray($neighbor);
557         }
558     }
559 
560     /**
561      * Create an array of common properties from the input array
562      *
563      * @param array $data - array of incoming "paths" arrays
564      *
565      * @return void
566      */
567     private function addCommonPropertiesFromArray(array $data)
568     {
569         $k                 = array_keys($data);
570         $k                 = $k[0];
571         $this->_result[$k] = [];
572         foreach ($data[$k] as $c) {
573             $id = $c['_id'];
574             unset($c['_id']);
575             $this->_result[$k][$id] = $c;
576         }
577     }
578 
579     /**
580      * Create an array of figuresfrom the input array
581      *
582      * @param array $data - array of incoming "paths" arrays
583      *
584      * @return void
585      */
586     private function addFigureFromArray(array $data)
587     {
588         $this->_result = $data;
589     }
590 
591     /**
592      * Create an array of Edges from the input array
593      *
594      * @param array $data - array of incoming "edge" arrays
595      *
596      * @return void
597      * @throws \triagens\ArangoDb\ClientException
598      */
599     private function addEdgesFromArray(array $data)
600     {
601         $this->_result[] = Edge::createFromArray($data, $this->_options);
602     }
603 
604 
605     /**
606      * Create an array of Vertex from the input array
607      *
608      * @param array $data - array of incoming "vertex" arrays
609      *
610      * @return void
611      * @throws \triagens\ArangoDb\ClientException
612      */
613     private function addVerticesFromArray(array $data)
614     {
615         $this->_result[] = Vertex::createFromArray($data, $this->_options);
616     }
617 
618 
619     /**
620      * Sanitize the result set rows
621      *
622      * This will remove the _id and _rev attributes from the results if the
623      * "sanitize" option is set
624      *
625      * @param array $rows - array of rows to be sanitized
626      *
627      * @return array - sanitized rows
628      */
629     private function sanitize(array $rows)
630     {
631         if (isset($this->_options[self::ENTRY_SANITIZE]) && $this->_options[self::ENTRY_SANITIZE]) {
632             foreach ($rows as $key => $value) {
633 
634                 if (is_array($value) && isset($value[Document::ENTRY_ID])) {
635                     unset($rows[$key][Document::ENTRY_ID]);
636                 }
637 
638                 if (is_array($value) && isset($value[Document::ENTRY_REV])) {
639                     unset($rows[$key][Document::ENTRY_REV]);
640                 }
641             }
642         }
643 
644         return $rows;
645     }
646 
647 
648     /**
649      * Fetch outstanding results from the server
650      *
651      * @throws Exception
652      * @return void
653      */
654     private function fetchOutstanding()
655     {
656         // continuation
657         $response = $this->_connection->put($this->url() . '/' . $this->_id, '', []);
658         ++$this->_fetches;
659 
660         $data = $response->getJson();
661 
662         $this->_hasMore = (bool) $data[self::ENTRY_HASMORE];
663         $this->add($data[self::ENTRY_RESULT]);
664 
665         if (!$this->_hasMore) {
666             // we have fetched the complete result set and can unset the id now
667             $this->_id = null;
668         }
669 
670         $this->updateLength();
671     }
672 
673 
674     /**
675      * Set the length of the (fetched) result set
676      *
677      * @return void
678      */
679     private function updateLength()
680     {
681         $this->_length = count($this->_result);
682     }
683 
684 
685     /**
686      * Return the base URL for the cursor
687      *
688      * @return string
689      */
690     private function url()
691     {
692         if (isset($this->_options[self::ENTRY_BASEURL])) {
693             return $this->_options[self::ENTRY_BASEURL];
694         }
695 
696         // this is the fallback
697         return Urls::URL_CURSOR;
698     }
699 
700     /**
701      * Get a statistical figure value from the query result
702      *
703      * @param string $name - name of figure to return
704      *
705      * @return int
706      */
707     private function getStatValue($name)
708     {
709         if (isset($this->_extra[self::ENTRY_STATS][$name])) {
710             return $this->_extra[self::ENTRY_STATS][$name];
711         }
712 
713         return 0;
714     }
715 
716     /**
717      * Get MetaData of the current cursor
718      *
719      * @return array
720      */
721     public function getMetadata()
722     {
723         return $this->data;
724     }
725 
726     /**
727      * Return the extra data of the query (statistics etc.). Contents of the result array
728      * depend on the type of query executed
729      *
730      * @return array
731      */
732     public function getExtra()
733     {
734         return $this->_extra;
735     }
736 
737     /**
738      * Return the warnings issued during query execution
739      *
740      * @return array
741      */
742     public function getWarnings()
743     {
744         if (isset($this->_extra['warnings'])) {
745             return $this->_extra['warnings'];
746         }
747 
748         return [];
749     }
750 
751     /**
752      * Return the number of writes executed by the query
753      *
754      * @return int
755      */
756     public function getWritesExecuted()
757     {
758         return $this->getStatValue('writesExecuted');
759     }
760 
761     /**
762      * Return the number of ignored write operations from the query
763      *
764      * @return int
765      */
766     public function getWritesIgnored()
767     {
768         return $this->getStatValue('writesIgnored');
769     }
770 
771     /**
772      * Return the number of documents iterated over in full scans
773      *
774      * @return int
775      */
776     public function getScannedFull()
777     {
778         return $this->getStatValue('scannedFull');
779     }
780 
781     /**
782      * Return the number of documents iterated over in index scans
783      *
784      * @return int
785      */
786     public function getScannedIndex()
787     {
788         return $this->getStatValue('scannedIndex');
789     }
790 
791     /**
792      * Return the number of documents filtered by the query
793      *
794      * @return int
795      */
796     public function getFiltered()
797     {
798         return $this->getStatValue('filtered');
799     }
800 
801     /**
802      * Return the number of HTTP calls that were made to build the cursor result
803      *
804      * @return int
805      */
806     public function getFetches()
807     {
808         return $this->_fetches;
809     }
810 
811     /**
812      * Return the cursor id, if any
813      *
814      * @return string
815      */
816     public function getId()
817     {
818         return $this->_id;
819     }
820 
821 }
822 
ArangoDB-PHP API Documentation API documentation generated by ApiGen