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: connection
  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 ArangoDB server
 15  *
 16  * As all access is done using HTTP, we do not need to establish a
 17  * persistent connection and keep its state.<br>
 18  * Instead, connections are established on the fly for each request
 19  * and are destroyed afterwards.<br>
 20  *
 21  * @package   triagens\ArangoDb
 22  * @since     0.2
 23  */
 24 class Connection
 25 {
 26     /**
 27      * Connection options
 28      *
 29      * @var array
 30      */
 31     private $_options;
 32 
 33     /**
 34      * Pre-assembled HTTP headers string for connection
 35      * This is pre-calculated when connection options are set/changed, to avoid
 36      * calculation of the same HTTP header values in each request done via the
 37      * connection
 38      *
 39      * @var string
 40      */
 41     private $_httpHeader = '';
 42 
 43     /**
 44      * Pre-assembled base URL for the current database
 45      * This is pre-calculated when connection options are set/changed, to avoid
 46      * calculation of the same base URL in each request done via the
 47      * connection
 48      *
 49      * @var string
 50      */
 51     private $_baseUrl = '';
 52 
 53     /**
 54      * Connection handle, used in case of keep-alive
 55      *
 56      * @var resource
 57      */
 58     private $_handle;
 59 
 60     /**
 61      * Flag if keep-alive connections are used
 62      *
 63      * @var bool
 64      */
 65     private $_useKeepAlive;
 66 
 67     /**
 68      * Batches Array
 69      *
 70      * @var array
 71      */
 72     private $_batches = [];
 73 
 74     /**
 75      * $_activeBatch object
 76      *
 77      * @var Batch
 78      */
 79     private $_activeBatch;
 80 
 81     /**
 82      * $_captureBatch boolean
 83      *
 84      * @var boolean
 85      */
 86     private $_captureBatch = false;
 87 
 88     /**
 89      * $_batchRequest boolean
 90      *
 91      * @var boolean
 92      */
 93     private $_batchRequest = false;
 94 
 95     /**
 96      * $_database string
 97      *
 98      * @var string
 99      */
100     private $_database = '';
101 
102     /**
103      * Set up the connection object, validate the options provided
104      *
105      * @throws Exception
106      *
107      * @param array $options - initial connection options
108      *
109      */
110     public function __construct(array $options)
111     {
112         $this->_options      = new ConnectionOptions($options);
113         $this->_useKeepAlive = ($this->_options[ConnectionOptions::OPTION_CONNECTION] === 'Keep-Alive');
114         $this->setDatabase($this->_options[ConnectionOptions::OPTION_DATABASE]);
115 
116         $this->updateHttpHeader();
117     }
118 
119     /**
120      * Close existing connection handle if a keep-alive connection was used
121      *
122      * @return void
123      */
124     public function __destruct()
125     {
126         if ($this->_useKeepAlive && is_resource($this->_handle)) {
127             @fclose($this->_handle);
128         }
129     }
130 
131     /**
132      * Set an option set for the connection
133      *
134      * @throws ClientException
135      *
136      * @param string $name  - name of option
137      * @param string $value - value of option
138      */
139     public function setOption($name, $value)
140     {
141         if ($name === ConnectionOptions::OPTION_ENDPOINT ||
142             $name === ConnectionOptions::OPTION_HOST ||
143             $name === ConnectionOptions::OPTION_PORT ||
144             $name === ConnectionOptions::OPTION_VERIFY_CERT ||
145             $name === ConnectionOptions::OPTION_CIPHERS ||
146             $name === ConnectionOptions::OPTION_ALLOW_SELF_SIGNED
147         ) {
148             throw new ClientException('Must not set option ' . $value . ' after connection is created.');
149         }
150 
151         $this->_options[$name] = $value;
152 
153         // special handling for several options
154         if ($name === ConnectionOptions::OPTION_TIMEOUT) {
155             // set the timeout option: patch the stream of an existing connection
156             if (is_resource($this->_handle)) {
157                 stream_set_timeout($this->_handle, $value);
158             }
159         } else if ($name === ConnectionOptions::OPTION_CONNECTION) {
160             // set keep-alive flag
161             $this->_useKeepAlive = (strtolower($value) === 'keep-alive');
162         } else if ($name === ConnectionOptions::OPTION_DATABASE) {
163             // set database
164             $this->setDatabase($value);
165         }
166 
167         $this->updateHttpHeader();
168     }
169 
170     /**
171      * Get the options set for the connection
172      *
173      * @return ConnectionOptions
174      */
175     public function getOptions()
176     {
177         return $this->_options;
178     }
179 
180     /**
181      * Get an option set for the connection
182      *
183      * @throws ClientException
184      *
185      * @param string $name - name of option
186      *
187      * @return mixed
188      */
189     public function getOption($name)
190     {
191         assert(is_string($name));
192 
193         return $this->_options[$name];
194     }
195 
196 
197     /**
198      * Issue an HTTP GET request
199      *
200      * @throws Exception
201      *
202      * @param string $url - GET URL
203      * @param array  $customHeaders
204      *
205      * @return HttpResponse
206      */
207     public function get($url, array $customHeaders = [])
208     {
209         $response = $this->executeRequest(HttpHelper::METHOD_GET, $url, '', $customHeaders);
210 
211         return $this->parseResponse($response);
212     }
213 
214     /**
215      * Issue an HTTP POST request with the data provided
216      *
217      * @throws Exception
218      *
219      * @param string $url  - POST URL
220      * @param string $data - body to post
221      * @param array  $customHeaders
222      *
223      * @return HttpResponse
224      */
225     public function post($url, $data, array $customHeaders = [])
226     {
227         $response = $this->executeRequest(HttpHelper::METHOD_POST, $url, $data, $customHeaders);
228 
229         return $this->parseResponse($response);
230     }
231 
232     /**
233      * Issue an HTTP PUT request with the data provided
234      *
235      * @throws Exception
236      *
237      * @param string $url  - PUT URL
238      * @param string $data - body to post
239      * @param array  $customHeaders
240      *
241      * @return HttpResponse
242      */
243     public function put($url, $data, array $customHeaders = [])
244     {
245         $response = $this->executeRequest(HttpHelper::METHOD_PUT, $url, $data, $customHeaders);
246 
247         return $this->parseResponse($response);
248     }
249 
250     /**
251      * Issue an HTTP Head request with the data provided
252      *
253      * @throws Exception
254      *
255      * @param string $url - PUT URL
256      * @param array  $customHeaders
257      *
258      * @return HttpResponse
259      */
260     public function head($url, array $customHeaders = [])
261     {
262         $response = $this->executeRequest(HttpHelper::METHOD_HEAD, $url, '', $customHeaders);
263 
264         return $this->parseResponse($response);
265     }
266 
267     /**
268      * Issue an HTTP PATCH request with the data provided
269      *
270      * @throws Exception
271      *
272      * @param string $url  - PATCH URL
273      * @param string $data - patch body
274      * @param array  $customHeaders
275      *
276      * @return HttpResponse
277      */
278     public function patch($url, $data, array $customHeaders = [])
279     {
280         $response = $this->executeRequest(HttpHelper::METHOD_PATCH, $url, $data, $customHeaders);
281 
282         return $this->parseResponse($response);
283     }
284 
285     /**
286      * Issue an HTTP DELETE request with the data provided
287      *
288      * @throws Exception
289      *
290      * @param string $url - DELETE URL
291      * @param array  $customHeaders
292      *
293      * @return HttpResponse
294      */
295     public function delete($url, array $customHeaders = [])
296     {
297         $response = $this->executeRequest(HttpHelper::METHOD_DELETE, $url, '', $customHeaders);
298 
299         return $this->parseResponse($response);
300     }
301 
302 
303     /**
304      * Recalculate the static HTTP header string used for all HTTP requests in this connection
305      */
306     private function updateHttpHeader()
307     {
308         $this->_httpHeader = HttpHelper::EOL;
309 
310         $endpoint = $this->_options[ConnectionOptions::OPTION_ENDPOINT];
311         if (Endpoint::getType($endpoint) !== Endpoint::TYPE_UNIX) {
312             $this->_httpHeader .= sprintf('Host: %s%s', Endpoint::getHost($endpoint), HttpHelper::EOL);
313         }
314 
315         if (isset($this->_options[ConnectionOptions::OPTION_AUTH_TYPE], $this->_options[ConnectionOptions::OPTION_AUTH_USER])) {
316             // add authorization header
317             $authorizationValue = base64_encode(
318                 $this->_options[ConnectionOptions::OPTION_AUTH_USER] . ':' .
319                 $this->_options[ConnectionOptions::OPTION_AUTH_PASSWD]
320             );
321 
322             $this->_httpHeader .= sprintf(
323                 'Authorization: %s %s%s',
324                 $this->_options[ConnectionOptions::OPTION_AUTH_TYPE],
325                 $authorizationValue,
326                 HttpHelper::EOL
327             );
328         }
329 
330         if (isset($this->_options[ConnectionOptions::OPTION_CONNECTION])) {
331             // add connection header
332             $this->_httpHeader .= sprintf('Connection: %s%s', $this->_options[ConnectionOptions::OPTION_CONNECTION], HttpHelper::EOL);
333         }
334 
335         if ($this->_database === '') {
336             $this->_baseUrl = '/_db/_system';
337         } else {
338             $this->_baseUrl = '/_db/' . urlencode($this->_database);
339         }
340     }
341 
342     /**
343      * Get a connection handle
344      *
345      * If keep-alive connections are used, the handle will be stored and re-used
346      *
347      * @throws ClientException
348      * @return resource - connection handle
349      * @throws \triagens\ArangoDb\ConnectException
350      */
351     private function getHandle()
352     {
353         if ($this->_useKeepAlive && $this->_handle && is_resource($this->_handle)) {
354             // keep-alive and handle was created already
355             $handle = $this->_handle;
356 
357             // check if connection is still valid
358             if (!feof($handle)) {
359                 // connection still valid
360                 return $handle;
361             }
362 
363             // close handle
364             @fclose($this->_handle);
365             $this->_handle = 0;
366 
367             if (!$this->_options[ConnectionOptions::OPTION_RECONNECT]) {
368                 // if reconnect option not set, this is the end
369                 throw new ClientException('Server has closed the connection already.');
370             }
371         }
372 
373         // no keep-alive or no handle available yet or a reconnect
374         $handle = HttpHelper::createConnection($this->_options);
375 
376         if ($this->_useKeepAlive && is_resource($handle)) {
377             $this->_handle = $handle;
378         }
379 
380         return $handle;
381     }
382 
383     /**
384      * Execute an HTTP request and return the results
385      *
386      * This function will throw if no connection to the server can be established or if
387      * there is a problem during data exchange with the server.
388      *
389      * will restore it.
390      *
391      * @throws Exception
392      *
393      * @param string $method        - HTTP request method
394      * @param string $url           - HTTP URL
395      * @param string $data          - data to post in body
396      * @param array  $customHeaders - any array containing header elements
397      *
398      * @return HttpResponse
399      */
400     private function executeRequest($method, $url, $data, array $customHeaders = [])
401     {
402         assert($this->_httpHeader !== '');
403         $wasAsync = false;
404         if (is_array($customHeaders) && isset($customHeaders[HttpHelper::ASYNC_HEADER])) {
405             $wasAsync = true;
406         }
407 
408         HttpHelper::validateMethod($method);
409         $url = $this->_baseUrl . $url;
410 
411         // create request data
412         if ($this->_batchRequest === false) {
413 
414             if ($this->_captureBatch === true) {
415                 $this->_options->offsetSet(ConnectionOptions::OPTION_BATCHPART, true);
416                 $request = HttpHelper::buildRequest($this->_options, $this->_httpHeader, $method, $url, $data, $customHeaders);
417                 $this->_options->offsetSet(ConnectionOptions::OPTION_BATCHPART, false);
418             } else {
419                 $request = HttpHelper::buildRequest($this->_options, $this->_httpHeader, $method, $url, $data, $customHeaders);
420             }
421 
422             if ($this->_captureBatch === true) {
423                 $batchPart = $this->doBatch($method, $request);
424                 if (null !== $batchPart) {
425                     return $batchPart;
426                 }
427             }
428         } else {
429             $this->_batchRequest = false;
430 
431             $this->_options->offsetSet(ConnectionOptions::OPTION_BATCH, true);
432             $request = HttpHelper::buildRequest($this->_options, $this->_httpHeader, $method, $url, $data, $customHeaders);
433             $this->_options->offsetSet(ConnectionOptions::OPTION_BATCH, false);
434         }
435 
436 
437         $traceFunc = $this->_options[ConnectionOptions::OPTION_TRACE];
438         if ($traceFunc) {
439             // call tracer func
440             if ($this->_options[ConnectionOptions::OPTION_ENHANCED_TRACE]) {
441                 list($header) = HttpHelper::parseHttpMessage($request, $url, $method);
442                 $headers = HttpHelper::parseHeaders($header);
443                 $traceFunc(new TraceRequest($headers[2], $method, $url, $data));
444             } else {
445                 $traceFunc('send', $request);
446             }
447         }
448 
449 
450         // open the socket. note: this might throw if the connection cannot be established
451         $handle = $this->getHandle();
452 
453         if ($handle) {
454             // send data and get response back
455 
456             if ($traceFunc) {
457                 // only issue syscall if we need it
458                 $startTime = microtime(true);
459             }
460 
461             $result = HttpHelper::transfer($handle, $request);
462 
463             if ($traceFunc) {
464                 // only issue syscall if we need it
465                 $timeTaken = microtime(true) - $startTime;
466             }
467 
468             $status = socket_get_status($handle);
469             if ($status['timed_out']) {
470                 throw new ClientException('Got a timeout while waiting for the server\'s response', 408);
471             }
472 
473             if (!$this->_useKeepAlive) {
474                 // must close the connection
475                 fclose($handle);
476             }
477 
478             $response = new HttpResponse($result, $url, $method, $wasAsync);
479 
480             if ($traceFunc) {
481                 // call tracer func
482                 if ($this->_options[ConnectionOptions::OPTION_ENHANCED_TRACE]) {
483                     $traceFunc(
484                         new TraceResponse(
485                             $response->getHeaders(), $response->getHttpCode(), $response->getBody(),
486                             $timeTaken
487                         )
488                     );
489                 } else {
490                     $traceFunc('receive', $result);
491                 }
492             }
493 
494             return $response;
495         }
496 
497         throw new ClientException('Whoops, this should never happen');
498     }
499 
500     /**
501      * Parse the response return the body values as an assoc array
502      *
503      * @throws Exception
504      *
505      * @param HttpResponse $response - the response as supplied by the server
506      *
507      * @return HttpResponse
508      */
509     public function parseResponse(HttpResponse $response)
510     {
511         $httpCode = $response->getHttpCode();
512 
513         if ($httpCode < 200 || $httpCode >= 400) {
514             // failure on server
515 
516             $body = $response->getBody();
517             if ($body !== '') {
518                 // check if we can find details in the response body
519                 $details = json_decode($body, true);
520                 if (is_array($details) && isset($details['errorMessage'])) {
521                     // yes, we got details
522                     $exception = new ServerException($details['errorMessage'], $details['code']);
523                     $exception->setDetails($details);
524                     throw $exception;
525                 }
526             }
527 
528             // no details found, throw normal exception
529             throw new ServerException($response->getResult(), $httpCode);
530         }
531 
532         return $response;
533     }
534 
535     /**
536      * Stop capturing commands
537      *
538      * @return Batch - Returns the active batch object
539      */
540     public function stopCaptureBatch()
541     {
542         $this->_captureBatch = false;
543 
544         return $this->_activeBatch;
545     }
546 
547 
548     /**
549      * Sets the active Batch for this connection
550      *
551      * @param Batch $batch - Sets the given batch as active
552      *
553      * @return Batch active batch
554      */
555     public function setActiveBatch($batch)
556     {
557         $this->_activeBatch = $batch;
558 
559         return $this->_activeBatch;
560     }
561 
562     /**
563      * returns the active batch
564      *
565      * @return Batch active batch
566      */
567     public function getActiveBatch()
568     {
569         return $this->_activeBatch;
570     }
571 
572 
573     /**
574      * Sets the batch capture state (true, if capturing)
575      *
576      * @param boolean $state true to turn on capture batch mode, false to turn it off
577      */
578     public function setCaptureBatch($state)
579     {
580         $this->_captureBatch = $state;
581     }
582 
583 
584     /**
585      * Sets connection into Batch-request mode. This is needed for some operations to act differently when in this mode.
586      *
587      * @param boolean $state sets the connection state to batch request, meaning it is currently doing a batch request.
588      */
589     public function setBatchRequest($state)
590     {
591         $this->_batchRequest = $state;
592     }
593 
594 
595     /**
596      * Returns true if this connection is in Batch-Capture mode
597      *
598      * @return bool
599      */
600     public function isInBatchCaptureMode()
601     {
602         return $this->_captureBatch;
603     }
604 
605 
606     /**
607      * returns the active batch
608      */
609     public function getBatches()
610     {
611         return $this->_batches;
612     }
613 
614 
615     /**
616      * This is a helper function to executeRequest that captures requests if we're in batch mode
617      *
618      * @param mixed  $method  - The method of the request (GET, POST...)
619      *
620      * @param string $request - The request to process
621      *
622      * This checks if we're in batch mode and returns a placeholder object,
623      * since we need to return some object that is expected by the caller.
624      * if we're not in batch mode it does not return anything, and
625      *
626      * @return mixed Batchpart or null if not in batch capturing mode
627      * @throws \triagens\ArangoDb\ClientException
628      */
629     private function doBatch($method, $request)
630     {
631         $batchPart = null;
632         if ($this->_captureBatch === true) {
633 
634             /** @var $batch Batch */
635             $batch = $this->getActiveBatch();
636 
637             $batchPart = $batch->append($method, $request);
638         }
639 
640         # do batch processing
641         return $batchPart;
642     }
643 
644     /**
645      * This function checks that the encoding of a string is utf.
646      * It only checks for printable characters.
647      *
648      *
649      * @param array $string the data to check
650      *
651      * @return boolean true if string is UTF-8, false if not
652      */
653     public static function detect_utf($string)
654     {
655         if (preg_match('//u', $string)) {
656             return true;
657         } else {
658             return false;
659         }
660     }
661 
662 
663     /**
664      * This function checks that the encoding of the keys and
665      * values of the array are utf-8, recursively.
666      * It will raise an exception if it encounters wrong encoded strings.
667      *
668      * @param array $data the data to check
669      *
670      * @throws ClientException
671      */
672     public static function check_encoding($data)
673     {
674         if (!is_array($data)) {
675             return;
676         }
677 
678         foreach ($data as $key => $value) {
679             if (!is_array($value)) {
680                 // check if the multibyte library function is installed and use it.
681                 if (function_exists('mb_detect_encoding')) {
682                     // check with mb library
683                     if (is_string($key) && mb_detect_encoding($key, 'UTF-8', true) === false) {
684                         throw new ClientException('Only UTF-8 encoded keys allowed. Wrong encoding in key string: ' . $key);
685                     }
686                     if (is_string($value) && mb_detect_encoding($value, 'UTF-8', true) === false) {
687                         throw new ClientException('Only UTF-8 encoded values allowed. Wrong encoding in value string: ' . $value);
688                     }
689                 } else {
690                     // fallback to preg_match checking
691                     if (is_string($key) && self::detect_utf($key) === false) {
692                         throw new ClientException('Only UTF-8 encoded keys allowed. Wrong encoding in key string: ' . $key);
693                     }
694                     if (is_string($value) && self::detect_utf($value) === false) {
695                         throw new ClientException('Only UTF-8 encoded values allowed. Wrong encoding in value string: ' . $value);
696                     }
697                 }
698             } else {
699                 self::check_encoding($value);
700             }
701         }
702     }
703 
704 
705     /**
706      * This is a json_encode() wrapper that also checks if the data is utf-8 conform.
707      * internally it calls the check_encoding() method. If that method does not throw
708      * an Exception, this method will happily return the json_encoded data.
709      *
710      * @param mixed $data    the data to encode
711      * @param mixed $options the options for the json_encode() call
712      *
713      * @return string the result of the json_encode
714      * @throws \triagens\ArangoDb\ClientException
715      */
716     public function json_encode_wrapper($data, $options = null)
717     {
718         if ($this->_options[ConnectionOptions::OPTION_CHECK_UTF8_CONFORM] === true) {
719             self::check_encoding($data);
720         }
721         if (empty($data)) {
722             $response = json_encode($data, $options | JSON_FORCE_OBJECT);
723         } else {
724             $response = json_encode($data, $options);
725         }
726 
727         return $response;
728     }
729 
730 
731     /**
732      * Set the database to use with this connection
733      *
734      * Sets the database to use with this connection, for example: 'my_database'<br>
735      * Further calls to the database will be addressed to the given database.
736      *
737      * @param string $database the database to use
738      */
739     public function setDatabase($database)
740     {
741         $this->_options[ConnectionOptions::OPTION_DATABASE] = $database;
742         $this->_database                                    = $database;
743 
744         $this->updateHttpHeader();
745     }
746 
747     /**
748      * Get the database that is currently used with this connection
749      *
750      * Get the database to use with this connection, for example: 'my_database'
751      *
752      * @return string
753      */
754     public function getDatabase()
755     {
756         return $this->_database;
757     }
758 }
759 
ArangoDB-PHP API Documentation API documentation generated by ApiGen