composer package updates
[openemr.git] / vendor / doctrine / couchdb / lib / Doctrine / CouchDB / CouchDBClient.php
blobe6c45bbb79e4310a6c67420d6f2af27a8b98a83b
1 <?php
2 /*
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\CouchDB;
22 use Doctrine\CouchDB\HTTP\Client;
23 use Doctrine\CouchDB\HTTP\Response;
24 use Doctrine\CouchDB\HTTP\HTTPException;
25 use Doctrine\CouchDB\HTTP\MultipartParserAndSender;
26 use Doctrine\CouchDB\HTTP\StreamClient;
27 use Doctrine\CouchDB\Utils\BulkUpdater;
28 use Doctrine\CouchDB\View\DesignDocument;
30 /**
31 * CouchDB client class
33 * @license http://www.opensource.org/licenses/mit-license.php MIT
34 * @link www.doctrine-project.com
35 * @since 1.0
36 * @author Benjamin Eberlei <kontakt@beberlei.de>
37 * @author Lukas Kahwe Smith <smith@pooteeweet.org>
39 class CouchDBClient
41 /** string \ufff0 */
42 const COLLATION_END = "\xEF\xBF\xB0";
44 /**
45 * Name of the CouchDB database
47 * @string
49 protected $databaseName;
51 /**
52 * The underlying HTTP Connection of the used DocumentManager.
54 * @var Client
56 private $httpClient;
58 /**
59 * CouchDB Version
61 * @var string
63 private $version = null;
65 static private $clients = array(
66 'socket' => 'Doctrine\CouchDB\HTTP\SocketClient',
67 'stream' => 'Doctrine\CouchDB\HTTP\StreamClient',
70 /**
71 * Factory method for CouchDBClients
73 * @param array $options
74 * @return CouchDBClient
75 * @throws \InvalidArgumentException
77 static public function create(array $options)
79 if (isset($options['url'])) {
80 $urlParts = parse_url($options['url']);
81 $urlOptions = ['user' => 'user', 'pass' => 'password', 'host' => 'host', 'path' => 'dbname', 'port' => 'port'];
82 foreach ($urlParts as $part => $value) {
83 switch($part) {
84 case 'host':
85 case 'user':
86 case 'port':
87 $options[$part] = $value;
88 break;
90 case 'path':
91 $path = explode('/', $value);
92 $options['dbname'] = array_pop($path);
93 $options['path'] = trim(implode('/', $path), '/');
94 break;
96 case 'pass':
97 $options['password'] = $value;
98 break;
100 case 'scheme':
101 $options['ssl'] = ($value === 'https');
102 break;
104 default:
105 break;
110 if (!isset($options['dbname'])) {
111 throw new \InvalidArgumentException("'dbname' is a required option to create a CouchDBClient");
114 $defaults = array(
115 'type' => 'socket',
116 'host' => 'localhost',
117 'port' => 5984,
118 'user' => null,
119 'password' => null,
120 'ip' => null,
121 'ssl' => false,
122 'path' => null,
123 'logging' => false,
124 'timeout' => 0.01,
126 $options = array_merge($defaults, $options);
128 if (!isset(self::$clients[$options['type']])) {
129 throw new \InvalidArgumentException(sprintf('There is no client implementation registered for %s, valid options are: %s',
130 $options['type'], implode(", ", array_keys(self::$clients))
133 $connectionClass = self::$clients[$options['type']];
134 $connection = new $connectionClass(
135 $options['host'],
136 $options['port'],
137 $options['user'],
138 $options['password'],
139 $options['ip'],
140 $options['ssl'],
141 $options['path'],
142 $options['timeout']
144 if ($options['logging'] === true) {
145 $connection = new HTTP\LoggingClient($connection);
147 return new static($connection, $options['dbname']);
151 * @param Client $client
152 * @param string $databaseName
154 public function __construct(Client $client, $databaseName)
156 $this->httpClient = $client;
157 $this->databaseName = $databaseName;
160 public function setHttpClient(Client $client)
162 $this->httpClient = $client;
166 * @return Client
168 public function getHttpClient()
170 return $this->httpClient;
173 public function getDatabase()
175 return $this->databaseName;
179 * Let CouchDB generate an array of UUIDs.
181 * @param int $count
182 * @return array
183 * @throws CouchDBException
185 public function getUuids($count = 1)
187 $count = (int)$count;
188 $response = $this->httpClient->request('GET', '/_uuids?count=' . $count);
190 if ($response->status != 200) {
191 throw new CouchDBException("Could not retrieve UUIDs from CouchDB.");
194 return $response->body['uuids'];
198 * Find a document by ID and return the HTTP response.
200 * @param string $id
201 * @return HTTP\Response
203 public function findDocument($id)
205 $documentPath = '/' . $this->databaseName . '/' . urlencode($id);
206 return $this->httpClient->request('GET', $documentPath);
210 * Find documents of all or the specified revisions.
212 * If $revisions is an array containing the revisions to be fetched, only
213 * the documents of those revisions are fetched. Else document of all
214 * leaf revisions are fetched.
216 * @param string $docId
217 * @param mixed $revisions
218 * @return HTTP\Response
220 public function findRevisions($docId, $revisions = null)
222 $path = '/' . $this->databaseName . '/' . urlencode($docId);
223 if (is_array($revisions)) {
224 // Fetch documents of only the specified leaf revisions.
225 $path .= '?open_revs=' . json_encode($revisions);
226 } else {
227 // Fetch documents of all leaf revisions.
228 $path .= '?open_revs=all';
230 // Set the Accept header to application/json to get a JSON array in the
231 // response's body. Without this the response is multipart/mixed stream.
232 return $this->httpClient->request(
233 'GET',
234 $path,
235 null,
236 false,
237 array('Accept' => 'application/json')
242 * Find many documents by passing their ids and return the HTTP response.
244 * @param array $ids
245 * @param null $limit
246 * @param null $offset
247 * @return HTTP\Response
249 public function findDocuments(array $ids, $limit = null, $offset = null)
251 $allDocsPath = '/' . $this->databaseName . '/_all_docs?include_docs=true';
252 if ($limit) {
253 $allDocsPath .= '&limit=' . (int)$limit;
255 if ($offset) {
256 $allDocsPath .= '&skip=' . (int)$offset;
259 return $this->httpClient->request('POST', $allDocsPath, json_encode(
260 array('keys' => array_values($ids)))
265 * Get all documents
267 * @param int|null $limit
268 * @param string|null $startKey
269 * @param string|null $endKey
270 * @param int|null $skip
271 * @param bool $descending
272 * @return HTTP\Response
274 public function allDocs($limit = null, $startKey = null, $endKey = null, $skip = null, $descending = false)
276 $allDocsPath = '/' . $this->databaseName . '/_all_docs?include_docs=true';
277 if ($limit) {
278 $allDocsPath .= '&limit=' . (int)$limit;
280 if ($startKey) {
281 $allDocsPath .= '&startkey="' . (string)$startKey.'"';
283 if (!is_null($endKey)) {
284 $allDocsPath .= '&endkey="' . (string)$endKey.'"';
286 if (!is_null($skip) && (int)$skip > 0) {
287 $allDocsPath .= '&skip=' . (int)$skip;
289 if (!is_null($descending) && (bool)$descending === true) {
290 $allDocsPath .= '&descending=true';
292 return $this->httpClient->request('GET', $allDocsPath);
296 * Get the current version of CouchDB.
298 * @throws HTTPException
299 * @return string
301 public function getVersion()
303 if ($this->version === null) {
304 $response = $this->httpClient->request('GET', '/');
305 if ($response->status != 200) {
306 throw HTTPException::fromResponse('/', $response);
309 $this->version = $response->body['version'];
311 return $this->version;
315 * Get all databases
317 * @throws HTTPException
318 * @return array
320 public function getAllDatabases()
322 $response = $this->httpClient->request('GET', '/_all_dbs');
323 if ($response->status != 200) {
324 throw HTTPException::fromResponse('/_all_dbs', $response);
327 return $response->body;
331 * Create a new database
333 * @throws HTTPException
334 * @param string $name
335 * @return void
337 public function createDatabase($name)
339 $response = $this->httpClient->request('PUT', '/' . urlencode($name));
341 if ($response->status != 201) {
342 throw HTTPException::fromResponse('/' . urlencode($name), $response);
347 * Drop a database
349 * @throws HTTPException
350 * @param string $name
351 * @return void
353 public function deleteDatabase($name)
355 $response = $this->httpClient->request('DELETE', '/' . urlencode($name));
357 if ($response->status != 200 && $response->status != 404) {
358 throw HTTPException::fromResponse('/' . urlencode($name), $response);
363 * Get Information about a database.
365 * @param string $name
366 * @return array
367 * @throws HTTPException
369 public function getDatabaseInfo($name = null)
371 $response = $this->httpClient->request('GET', '/' . ($name ? urlencode($name) : $this->databaseName));
373 if ($response->status != 200) {
374 throw HTTPException::fromResponse('/' . urlencode($name), $response);
377 return $response->body;
381 * Get changes.
383 * @param array $params
384 * @return array
385 * @throws HTTPException
387 public function getChanges(array $params = array())
389 $path = '/' . $this->databaseName . '/_changes';
391 $method = ((!isset($params['doc_ids']) || $params['doc_ids'] == null) ? "GET" : "POST");
392 $response = '';
394 if ($method == "GET") {
396 foreach ($params as $key => $value) {
397 if (isset($params[$key]) === true && is_bool($value) === true) {
398 $params[$key] = ($value) ? 'true' : 'false';
401 if (count($params) > 0) {
402 $query = http_build_query($params);
403 $path = $path.'?'.$query;
405 $response = $this->httpClient->request('GET', $path);
407 } else {
408 $path .= '?filter=_doc_ids';
409 $response = $this->httpClient->request('POST', $path, json_encode($params));
411 if ($response->status != 200) {
412 throw HTTPException::fromResponse($path, $response);
415 return $response->body;
419 * Create a bulk updater instance.
421 * @return BulkUpdater
423 public function createBulkUpdater()
425 return new BulkUpdater($this->httpClient, $this->databaseName);
429 * Execute a POST request against CouchDB inserting a new document, leaving the server to generate a uuid.
431 * @param array $data
432 * @return array<id, rev>
433 * @throws HTTPException
435 public function postDocument(array $data)
437 $path = '/' . $this->databaseName;
438 $response = $this->httpClient->request('POST', $path, json_encode($data));
440 if ($response->status != 201) {
441 throw HTTPException::fromResponse($path, $response);
444 return array($response->body['id'], $response->body['rev']);
448 * Execute a PUT request against CouchDB inserting or updating a document.
450 * @param array $data
451 * @param string $id
452 * @param string|null $rev
453 * @return array<id, rev>
454 * @throws HTTPException
456 public function putDocument($data, $id, $rev = null)
458 $data['_id'] = $id;
459 if ($rev) {
460 $data['_rev'] = $rev;
463 $path = '/' . $this->databaseName . '/' . urlencode($id);
464 $response = $this->httpClient->request('PUT', $path, json_encode($data));
466 if ($response->status != 201) {
467 throw HTTPException::fromResponse($path, $response);
470 return array($response->body['id'], $response->body['rev']);
474 * Delete a document.
476 * @param string $id
477 * @param string $rev
478 * @return void
479 * @throws HTTPException
481 public function deleteDocument($id, $rev)
483 $path = '/' . $this->databaseName . '/' . urlencode($id) . '?rev=' . $rev;
484 $response = $this->httpClient->request('DELETE', $path);
486 if ($response->status != 200) {
487 throw HTTPException::fromResponse($path, $response);
492 * @param string $designDocName
493 * @param string $viewName
494 * @param DesignDocument $designDoc
495 * @return View\Query
497 public function createViewQuery($designDocName, $viewName, DesignDocument $designDoc = null)
499 return new View\Query($this->httpClient, $this->databaseName, $designDocName, $viewName, $designDoc);
503 * Create or update a design document from the given in memory definition.
505 * @param string $designDocName
506 * @param DesignDocument $designDoc
507 * @return HTTP\Response
509 public function createDesignDocument($designDocName, DesignDocument $designDoc)
511 $data = $designDoc->getData();
512 $data['_id'] = '_design/' . $designDocName;
514 $documentPath = '/' . $this->databaseName . '/' . $data['_id'];
515 $response = $this->httpClient->request( 'GET', $documentPath );
517 if ($response->status == 200) {
518 $docData = $response->body;
519 $data['_rev'] = $docData['_rev'];
522 return $this->httpClient->request(
523 "PUT",
524 sprintf("/%s/_design/%s", $this->databaseName, $designDocName),
525 json_encode($data)
530 * GET /db/_compact
532 * Return array of data about compaction status.
534 * @return array
535 * @throws HTTPException
537 public function getCompactInfo()
539 $path = sprintf('/%s/_compact', $this->databaseName);
540 $response = $this->httpClient->request('GET', $path);
541 if ($response->status >= 400) {
542 throw HTTPException::fromResponse($path, $response);
544 return $response->body;
548 * POST /db/_compact
550 * @return array
551 * @throws HTTPException
553 public function compactDatabase()
555 $path = sprintf('/%s/_compact', $this->databaseName);
556 $response = $this->httpClient->request('POST', $path);
557 if ($response->status >= 400) {
558 throw HTTPException::fromResponse($path, $response);
560 return $response->body;
564 * POST /db/_compact/designDoc
566 * @param string $designDoc
567 * @return array
568 * @throws HTTPException
570 public function compactView($designDoc)
572 $path = sprintf('/%s/_compact/%s', $this->databaseName, $designDoc);
573 $response = $this->httpClient->request('POST', $path);
574 if ($response->status >= 400) {
575 throw HTTPException::fromResponse($path, $response);
577 return $response->body;
581 * POST /db/_view_cleanup
583 * @return array
584 * @throws HTTPException
586 public function viewCleanup()
588 $path = sprintf('/%s/_view_cleanup', $this->databaseName);
589 $response = $this->httpClient->request('POST', $path);
590 if ($response->status >= 400) {
591 throw HTTPException::fromResponse($path, $response);
593 return $response->body;
597 * POST /db/_replicate
599 * @param string $source
600 * @param string $target
601 * @param bool|null $cancel
602 * @param bool|null $continuous
603 * @param string|null $filter
604 * @param array|null $ids
605 * @param string|null $proxy
606 * @return array
607 * @throws HTTPException
609 public function replicate($source, $target, $cancel = null, $continuous = null, $filter = null, array $ids = null, $proxy = null)
611 $params = array('target' => $target, 'source' => $source);
612 if ($cancel !== null) {
613 $params['cancel'] = (bool)$cancel;
615 if ($continuous !== null) {
616 $params['continuous'] = (bool)$continuous;
618 if ($filter !== null) {
619 $params['filter'] = $filter;
621 if ($ids !== null) {
622 $params['doc_ids'] = $ids;
624 if ($proxy !== null) {
625 $params['proxy'] = $proxy;
627 $path = '/_replicate';
628 $response = $this->httpClient->request('POST', $path, json_encode($params));
629 if ($response->status >= 400) {
630 throw HTTPException::fromResponse($path, $response);
632 return $response->body;
636 * GET /_active_tasks
638 * @return array
639 * @throws HTTPException
641 public function getActiveTasks()
643 $response = $this->httpClient->request('GET', '/_active_tasks');
644 if ($response->status != 200) {
645 throw HTTPException::fromResponse('/_active_tasks', $response);
647 return $response->body;
651 * Get revision difference.
653 * @param array $data
654 * @return array
655 * @throws HTTPException
657 public function getRevisionDifference($data)
659 $path = '/' . $this->databaseName . '/_revs_diff';
660 $response = $this->httpClient->request('POST', $path, json_encode($data));
661 if ($response->status != 200) {
662 throw HTTPException::fromResponse($path, $response);
664 return $response->body;
668 * Transfer missing revisions to the target. The Content-Type of response
669 * from the source should be multipart/mixed.
671 * @param string $docId
672 * @param array $missingRevs
673 * @param CouchDBClient $target
674 * @return array|HTTP\ErrorResponse|string
675 * @throws HTTPException
677 public function transferChangedDocuments($docId, $missingRevs, CouchDBClient $target)
679 $path = '/' . $this->getDatabase() . '/' . $docId;
680 $params = array('revs' => true, 'latest' => true, 'open_revs' => json_encode($missingRevs));
681 $query = http_build_query($params);
682 $path .= '?' . $query;
684 $targetPath = '/' . $target->getDatabase() . '/' . $docId . '?new_edits=false';
686 $mutltipartHandler = new MultipartParserAndSender($this->getHttpClient(), $target->getHttpClient());
687 return $mutltipartHandler->request(
688 'GET',
689 $path,
690 $targetPath,
691 null,
692 array('Accept' => 'multipart/mixed')
698 * Get changes as a stream.
700 * This method similar to the getChanges() method. But instead of returning
701 * the set of changes, it returns the connection stream from which the response
702 * can be read line by line. This is useful when you want to continuously get changes
703 * as they occur. Filtered changes feed is not supported by this method.
705 * @param array $params
706 * @param bool $raw
707 * @return resource
708 * @throws HTTPException
710 public function getChangesAsStream(array $params = array())
712 // Set feed to continuous.
713 if (!isset($params['feed']) || $params['feed'] != 'continuous') {
714 $params['feed'] = 'continuous';
716 $path = '/' . $this->databaseName . '/_changes';
717 $connectionOptions = $this->getHttpClient()->getOptions();
718 $streamClient = new StreamClient(
719 $connectionOptions['host'],
720 $connectionOptions['port'],
721 $connectionOptions['username'],
722 $connectionOptions['password'],
723 $connectionOptions['ip'],
724 $connectionOptions['ssl'],
725 $connectionOptions['path']
728 foreach ($params as $key => $value) {
729 if (isset($params[$key]) === true && is_bool($value) === true) {
730 $params[$key] = ($value) ? 'true' : 'false';
733 if (count($params) > 0) {
734 $query = http_build_query($params);
735 $path = $path . '?' . $query;
737 $stream = $streamClient->getConnection('GET', $path, null);
739 $headers = $streamClient->getStreamHeaders($stream);
740 if (empty($headers['status'])) {
741 throw HTTPException::readFailure(
742 $connectionOptions['ip'],
743 $connectionOptions['port'],
744 'Received an empty response or not status code',
747 } elseif ($headers['status'] != 200) {
748 $body = '';
749 while (!feof($stream)) {
750 $body .= fgets($stream);
752 throw HTTPException::fromResponse($path, new Response($headers['status'], $headers, $body));
754 // Everything seems okay. Return the connection resource.
755 return $stream;
760 * Commit any recent changes to the specified database to disk.
762 * @return array
763 * @throws HTTPException
765 public function ensureFullCommit()
767 $path = '/' . $this->databaseName . '/_ensure_full_commit';
768 $response = $this->httpClient->request('POST', $path);
769 if ($response->status != 201) {
770 throw HTTPException::fromResponse($path, $response);
772 return $response->body;