Completed
Push — master ( 501f3c...af5ea2 )
by Filippo
03:21
created

Couch::queryTempView()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 31
Code Lines 21

Duplication

Lines 6
Ratio 19.35 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 6
loc 31
rs 8.439
cc 5
eloc 21
nc 16
nop 7
1
<?php
2
3
/**
4
 * @file Couch.php
5
 * @brief This file contains the Couch class.
6
 * @details
7
 * @author Filippo F. Fadda
8
 */
9
10
//! This is the main Elephant on Couch library namespace.
11
namespace EoC;
12
13
14
use EoC\Adapter\IClientAdapter;
15
use EoC\Message\Request;
16
use EoC\Message\Response;
17
18
19
/**
20
 * @brief The CouchDB's client. You need an instance of this class to interact with CouchDB.
21
 * @nosubgrouping
22
 * @todo Add Memcached support. Remember to use Memcached extension, not memcache.
23
 * @todo Add Post File support.
24
 * @todo Check ISO-8859-1 because CouchDB uses it, in particular utf8_encode().
25
 */
26
final class Couch {
27
28
  //! The user agent name.
29
  const USER_AGENT_NAME = "EoC Client";
30
31
  //! Default CouchDB revisions limit number.
32
  const REVS_LIMIT = 1000;
33
34
  /** @name Document Paths */
35
  //!@{
36
37
  const STD_DOC_PATH = ""; //!< Path for standard documents.
38
  const LOCAL_DOC_PATH = "_local/";  //!< Path for local documents.
39
  const DESIGN_DOC_PATH = "_design/";  //!< Path for design documents.
40
41
  //!@}
42
43
  // Socket or cURL HTTP client adapter.
44
  private $client;
45
46
47
  /**
48
   * @brief Creates a Couch class instance.
49
   * @param[in] IClientAdapter $adapter An instance of a class that implements the IClientAdapter interface.
50
   */
51
  public function __construct(IClientAdapter $adapter) {
52
    $this->client = $adapter;
53
  }
54
55
56
  /**
57
   * @brief Returns a CouchDB wild card.
58
   * @details A standard object is translated to JSON as `{}` same of a JavaScript empty object.
59
   * @retval [stdClass](http://php.net/manual/en/language.types.object.php)
60
   */
61
  public static function WildCard() {
62
    return new \stdClass();
63
  }
64
65
66
  /**
67
   * @brief Sets the document class and type in case the document hasn't one.
68
   * @param[in] Doc\IDoc $doc A document.
69
   */
70
  private function setDocInfo(Doc\IDoc $doc) {
71
    // Sets the class name.
72
    $class = get_class($doc);
73
    $doc->setClass($class);
74
75
    // Sets the document type.
76
    if (!$doc->hasType()) {
77
      preg_match('/([\w]+$)/', $class, $matches);
78
      $type = strtolower($matches[1]);
79
      $doc->setType($type);
80
    }
81
  }
82
83
84
  /**
85
   * @brief Overrides the rows, adding a new row for every key hasn't been matched.
86
   * @details CouchDB doesn't return rows for the keys a match is not found. To make joins having a row for each key is
87
   * essential. The algorithm below overrides the rows, adding a new row for every key hasn't been matched. The JSON
88
   * encoding is necessary because we might have complex keys. A complex key is no more than an array.
89
   * @param[in] array $keys An array containing the keys.
90
   * @param[out] array $rows An associative array containing the rows.
91
   */
92
  private function addMissingRows($keys, &$rows) {
93
94
    if (!empty($keys) && isset($rows)) {
95
96
      // These are the rows for the matched keys.
97
      $matches = [];
98
      foreach ($rows as $row) {
99
        $hash = md5(json_encode($row['key']));
100
        $matches[$hash] = $row;
101
      }
102
103
      $allRows = [];
104
      foreach ($keys as $key) {
105
        $hash = md5(json_encode($key));
106
107
        if (isset($matches[$hash])) // Match found.
108
          $allRows[] = $matches[$hash];
109
        else // No match found.
110
          $allRows[] = ['id' => NULL, 'key' => $key, 'value' => NULL];
111
      }
112
113
      // Overrides the response, replacing rows.
114
      $rows = $allRows;
115
    }
116
117
  }
118
119
120
  /**
121
   * @brief This method is used to send a Request to CouchDB.
122
   * @details If you want call a not supported CouchDB API, you can use this function to send your request.\n
123
   * You can also provide an instance of a class that implements the IChunkHook interface, to deal with a chunked
124
   * response.
125
   * @param[in] Request $request The Request object.
126
   * @param[in] IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
127
   * @return Response
128
   */
129
  public function send(Request $request, Hook\IChunkHook $chunkHook = NULL) {
130
    // Sets user agent information.
131
    $request->setHeaderField(Request::USER_AGENT_HF, self::USER_AGENT_NAME." ".Version::getNumber());
132
133
    // We accept JSON.
134
    $request->setHeaderField(Request::ACCEPT_HF, "application/json");
135
136
    // We close the connection after read the response.
137
    // NOTE: we don't use anymore the connection header field, because we use the same socket until the end of script.
138
    //$request->setHeaderField(Message::CONNECTION_HF, "close");
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
139
140
    $response = $this->client->send($request, $chunkHook);
141
142
    // 1xx - Informational Status Codes
143
    // 2xx - Success Status Codes
144
    // 3xx - Redirection Status Codes
145
    // 4xx - Client Error Status Codes
146
    // 5xx - Server Error Status Codes
147
    $statusCode = (int)$response->getStatusCode();
148
149
    switch ($statusCode) {
150
      case ($statusCode >= 200 && $statusCode < 300):
151
        break;
152
      case ($statusCode < 200):
153
        //$this->handleInformational($request, $response);
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
154
        break;
155
      case ($statusCode < 300):
156
        //$this->handleRedirection($request, $response);
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
157
        break;
158
      case ($statusCode < 400):
159
        throw new Exception\ClientErrorException($request, $response);
160
      case ($statusCode < 500):
161
        throw new Exception\ServerErrorException($request, $response);
162
      default:
163
        throw new Exception\UnknownResponseException($request, $response);
164
    }
165
166
    return $response;
167
  }
168
169
170
  /** @name Validation and Encoding Methods */
171
  //!@{
172
173
  /**
174
   * @brief This method raise an exception when a user provide an invalid document path.
175
   * @details This method is called by any other methods that interacts with CouchDB. You don't need to call, unless
176
   * you are making a not supported call to CouchDB.
177
   * @param[in] string $path Document path.
178
   * @param[in] bool $excludeLocal Document path.
179
   */
180
  public function validateDocPath($path, $excludeLocal = FALSE) {
181
    if (empty($path)) // STD_DOC_PATH
182
      return;
183
184
    if ($path == self::DESIGN_DOC_PATH)
185
      return;
186
    elseif ($path == self::LOCAL_DOC_PATH && $excludeLocal)
187
      throw new \InvalidArgumentException("Local document doesn't have attachments.");
188
    else
189
      throw new \InvalidArgumentException("Invalid document path.");
190
  }
191
192
193
  /**
194
   * @brief Encodes the document id.
195
   * @details This method is called by any other methods that interacts with CouchDB. You don't need to call, unless
196
   * you are making a not supported call to CouchDB.
197
   * @param string $docId Document id.
198
   */
199
  public function validateAndEncodeDocId(&$docId) {
200
    if (!empty($docId))
201
      $docId = rawurlencode($docId);
202
    else
203
      throw new \InvalidArgumentException("\$docId must be a non-empty string.");
204
  }
205
206
  //!@}
207
208
209
  /** @name Miscellaneous Methods */
210
  //!@{
211
212
  /**
213
   * @brief Creates the admin user.
214
   * @todo Implement the method.
215
   */
216
  public function createAdminUser() {
217
    throw new \BadMethodCallException("The method `createAdminUser()` is not available.");
218
  }
219
220
221
  /**
222
   * @brief Restarts the server.
223
   * @attention Requires admin privileges.
224
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#restart
225
   * @bug [COUCHDB-947](https://issues.apache.org/jira/browse/COUCHDB-947)
226
   */
227
  public function restartServer() {
228
    $request = new Request(Request::POST_METHOD, "/_restart");
229
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
230
231
    // There is a bug in CouchDB, sometimes it doesn't return the 200 Status Code because it closes the connection
232
    // before the client has received the entire response. To avoid problems, we trap the exception and we go on.
233
    try {
234
      $this->send($request);
235
    }
236
    catch (\Exception $e) {
237
      if ($e->getCode() > 0)
238
        throw $e;
239
    }
240
  }
241
242
243
  /**
244
   * @brief Returns an object that contains MOTD, server and client and PHP versions.
245
   * @details The MOTD can be specified in CouchDB configuration files. This function returns more information
246
   * compared to the CouchDB standard REST call.
247
   * @retval Info::ServerInfo
248
   */
249
  public function getServerInfo() {
250
    $response = $this->send(new Request(Request::GET_METHOD, "/"));
251
    $info = $response->getBodyAsArray();
252
    return new Info\ServerInfo($info["couchdb"], $info["version"]);
253
  }
254
255
256
  /**
257
   * @brief Returns information about the Elephant on Couch client.
258
   * @retval Info::ClientInfo
259
   */
260
  public function getClientInfo() {
261
    return new Info\ClientInfo();
262
  }
263
264
265
  /**
266
   * @brief Returns the favicon.ico file.
267
   * @details The favicon is a part of the admin interface, but the handler for it is special as CouchDB tries to make
268
   * sure that the favicon is cached for one year. Returns a string that represents the icon.
269
   * @retval string
270
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#favicon-ico
271
   */
272
  public function getFavicon() {
273
    $response = $this->send(new Request(Request::GET_METHOD, "/favicon.ico"));
274
275
    if ($response->getHeaderFieldValue(Request::CONTENT_TYPE_HF) == "image/x-icon")
276
      return $response->getBody();
277
    else
278
      throw new \InvalidArgumentException("Content-Type must be image/x-icon.");
279
  }
280
281
282
  /**
283
   * @brief Returns server statistics.
284
   * @retval array An associative array
285
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#stats
286
   */
287
  public function getStats() {
288
    return $this->send(new Request(Request::GET_METHOD, "/_stats"))->getBodyAsArray();
289
  }
290
291
292
  /**
293
   * @brief Returns a list of all databases on this server.
294
   * @retval array of string
295
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#all-dbs
296
   */
297
  public function getAllDbs() {
298
    return $this->send(new Request(Request::GET_METHOD, "/_all_dbs"))->getBodyAsArray();
299
  }
300
301
302
  /**
303
   * @brief Returns a list of all database events in the CouchDB instance.
304
   * @param[in] DbUpdatesFeedOpts $opts Additional options.
305
   * @retval Response
306
   * @attention Requires admin privileges.
307
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#db-updates
308
   */
309 View Code Duplication
  public function getDbUpdates(Opt\DbUpdatesFeedOpts $opts = NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
    $request = new Request(Request::GET_METHOD, "/_db_updates");
311
312
    if (isset($opts))
313
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
314
315
    return $this->send($request)->getBodyAsArray();
316
  }
317
318
319
  /**
320
   * @brief Returns a list of running tasks.
321
   * @attention Requires admin privileges.
322
   * @retval array An associative array
323
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#active-tasks
324
   */
325
  public function getActiveTasks() {
326
    return $this->send(new Request(Request::GET_METHOD, "/_active_tasks"))->getBodyAsArray();
327
  }
328
329
330
  /**
331
   * @brief Returns the tail of the server's log file.
332
   * @attention Requires admin privileges.
333
   * @param[in] integer $bytes How many bytes to return from the end of the log file.
334
   * @retval string
335
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#log
336
   */
337
  public function getLogTail($bytes = 1000) {
338
    if (is_int($bytes) and ($bytes > 0)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
339
      $request = new Request(Request::GET_METHOD, "/_log");
340
      $request->setQueryParam("bytes", $bytes);
341
      return $this->send($request)->getBody();
342
    }
343
    else
344
      throw new \InvalidArgumentException("\$bytes must be a positive integer.");
345
  }
346
347
348
  /**
349
   * @brief Returns a list of generated UUIDs.
350
   * @param[in] integer $count Requested UUIDs number.
351
   * @retval string|array If `$count == 1` (default) returns a string else returns an array of strings.
352
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#uuids
353
   */
354
  public function getUuids($count = 1) {
355
    if (is_int($count) and ($count > 0)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
356
      $request = new Request(Request::GET_METHOD, "/_uuids");
357
      $request->setQueryParam("count", $count);
358
359
      $response = $this->send($request);
360
361
      if ($count == 1) // We don't need to use === operator because, just above, we made a type checking.
362
        return $response->getBodyAsArray()['uuids'][0];
363
      else
364
        return $response->getBodyAsArray()['uuids'];
365
    }
366
    else
367
      throw new \InvalidArgumentException("\$count must be a positive integer.");
368
  }
369
370
  //!@}
371
372
373
  /** @name Server Configuration Methods */
374
  //!@{
375
376
  /**
377
   * @brief Returns the entire server configuration or a single section or a single configuration value of a section.
378
   * @param[in] string $section Requested section.
379
   * @param[in] string $key Requested key.
380
   * @retval string|array An array with the configuration keys or a simple string in case of a single key.
381
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#get--_config
382
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#get--_config-section
383
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#config-section-key
384
   */
385
  public function getConfig($section = "", $key = "") {
386
    $path = "/_config";
387
388
    if (!empty($section)) {
389
      $path .= "/".$section;
390
391
      if (!empty($key))
392
        $path .= "/".$key;
393
    }
394
395
    return $this->send(new Request(Request::GET_METHOD, $path))->getBodyAsArray();
396
  }
397
398
399
  /**
400
   * @brief Sets a single configuration value in a given section to server configuration.
401
   * @param[in] string $section The configuration section.
402
   * @param[in] string $key The key.
403
   * @param[in] string $value The value for the key.
404
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#put--_config-section-key
405
   */
406
  public function setConfigKey($section, $key, $value) {
407
    if (!is_string($section) or empty($section))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
408
      throw new \InvalidArgumentException("\$section must be a not empty string.");
409
410
    if (!is_string($key) or empty($key))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
411
      throw new \InvalidArgumentException("\$key must be a not empty string.");
412
413
    if (is_null($value))
414
      throw new \InvalidArgumentException("\$value cannot be null.");
415
416
    $request = new Request(Request::PUT_METHOD, "/_config/".$section."/".$key);
417
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
418
    $request->setBody(json_encode(utf8_encode($value)));
419
    $this->send($request);
420
  }
421
422
423
  /**
424
   * @brief Deletes a single configuration value from a given section in server configuration.
425
   * @param[in] string $section The configuration section.
426
   * @param[in] string $key The key.
427
   * @see http://docs.couchdb.org/en/latest/api/configuration.html#delete-config-section-key
428
   */
429
  public function deleteConfigKey($section, $key) {
430
    if (!is_string($section) or empty($section))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
431
      throw new \InvalidArgumentException("\$section must be a not empty string.");
432
433
    if (!is_string($key) or empty($key))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
434
      throw new \InvalidArgumentException("\$key must be a not empty string.");
435
436
    $this->send(new Request(Request::DELETE_METHOD, "/_config/".$section."/".$key));
437
  }
438
439
  //!@}
440
441
442
  /** @name Cookie Authentication */
443
  //!@{
444
445
  /**
446
   * @brief Returns cookie based login user information.
447
   * @retval Response
448
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#get--_session
449
   */
450
  public function getSession() {
451
    return $this->send(new Request(Request::GET_METHOD, "/_session"));
452
  }
453
454
455
  /**
456
   * @brief Makes cookie based user login.
457
   * @retval Response
458
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#post--_session
459
   */
460
  public function setSession($userName, $password) {
461
    if (!is_string($userName) or empty($userName))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
462
      throw new \InvalidArgumentException("\$userName must be a not empty string.");
463
464
    if (!is_string($password) or empty($password))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
465
      throw new \InvalidArgumentException("\$password must be a not empty string.");
466
467
    $request = new Request(Request::POST_METHOD, "/_session");
468
469
    $request->setHeaderField(Request::X_COUCHDB_WWW_AUTHENTICATE_HF, "Cookie");
470
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/x-www-form-urlencoded");
471
472
    $request->setQueryParam("name", $userName);
473
    $request->setQueryParam("password", $password);
474
475
    return $this->send($request);
476
  }
477
478
479
  /**
480
   * @brief Makes user logout.
481
   * @retval Response
482
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#delete--_session
483
   */
484
  public function deleteSession() {
485
    return $this->send(new Request(Request::DELETE_METHOD, "/_session"));
486
  }
487
488
  //!@}
489
490
491
  /** @name OAuth Authentication */
492
  //! @see http://docs.couchdb.org/en/latest/api/server/authn.html#oauth-authentication
493
  //!@{
494
495
  /**
496
   * @brief
497
   * @todo To be implemented and documented.
498
   */
499
  public function getAccessToken() {
500
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/access_token"));
501
  }
502
503
504
  /**
505
   * @brief
506
   * @todo To be implemented and documented.
507
   */
508
  public function getAuthorize() {
509
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/authorize"));
510
  }
511
512
513
  /**
514
   * @brief
515
   * @todo To be implemented and documented.
516
   */
517
  public function setAuthorize() {
518
    return $this->send(new Request(Request::POST_METHOD, "/_oauth/authorize"));
519
  }
520
521
522
  /**
523
   * @brief
524
   * @todo To be implemented and documented.
525
   */
526
  public function requestToken() {
527
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/request_token"));
528
  }
529
530
  //!@}
531
532
533
  /**
534
   * @brief Creates a new database and selects it.
535
   * @param[in] string $name The database name. A database must be named with all lowercase letters (a-z),
536
   * digits (0-9), or any of the _$()+-/ characters and must end with a slash in the URL. The name has to start with a
537
   * lowercase letter (a-z).
538
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#put--db
539
   */
540
  public function createDb($name) {
541
    # \A[a-z][a-z\d_$()+-/]++\z
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
542
    #
543
    # Assert position at the beginning of the string «\A»
544
    # Match a single character in the range between “a” and “z” «[a-z]»
545
    # Match a single character present in the list below «[a-z\d_$()+-/]++»
546
    #    Between one and unlimited times, as many times as possible, without giving back (possessive) «++»
547
    #    A character in the range between “a” and “z” «a-z»
548
    #    A single digit 0..9 «\d»
549
    #    One of the characters “_$()” «_$()»
550
    #    A character in the range between “+” and “/” «+-/»
551
    # Assert position at the very end of the string «\z»
552
    if (preg_match('%\A[a-z][a-z\d_$()+-/]++\z%', $name) === FALSE)
553
      throw new \InvalidArgumentException("Invalid database name.");
554
555
    $this->send(new Request(Request::PUT_METHOD, "/".rawurlencode($name)."/"));
556
  }
557
558
559
  /**
560
   * @brief Deletes an existing database.
561
   * @param[in] string $name The database name.
562
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#delete--db
563
   */
564
  public function deleteDb($name) {
565
    $this->send(new Request(Request::DELETE_METHOD, "/".rawurlencode($name)));
566
  }
567
568
569
  /**
570
   * @brief Returns information about the selected database.
571
   * @param[in] string $name The database name.
572
   * @retval Info::DbInfo
573
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#get--db
574
   */
575
  public function getDbInfo($name) {
576
    return new Info\Dbinfo($this->send(new Request(Request::GET_METHOD, "/".rawurlencode($name)."/"))->getBodyAsArray());
577
  }
578
579
580
  /**
581
   * @brief Obtains a list of the changes made to the database. This can be used to monitor for update and modifications
582
   * to the database for post processing or synchronization.
583
   * @param[in] string $name The database name.
584
   * @param[in] ChangesFeedOpts $opts Additional options.
585
   * @retval Response
586
   * @see http://docs.couchdb.org/en/latest/api/database/changes.html
587
   */
588 View Code Duplication
  public function getDbChanges($name, Opt\ChangesFeedOpts $opts = NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
589
    $request = new Request(Request::GET_METHOD, "/".rawurlencode($name)."/_changes");
590
591
    if (isset($opts))
592
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
593
594
    return $this->send($request);
595
  }
596
597
598
  /**
599
   * @brief Starts a compaction for the current selected database.
600
   * @details Writes a new version of the database file, removing any unused sections from the new version during write.
601
   * Because a new file is temporary created for this purpose, you will need twice the current storage space of the
602
   * specified database in order for the compaction routine to complete.\n
603
   * Removes old revisions of documents from the database, up to the per-database limit specified by the `_revs_limit`
604
   * database setting.\n
605
   * Compaction can only be requested on an individual database; you cannot compact all the databases for a CouchDB
606
   * instance.\n
607
   * The compaction process runs as a background process. You can determine if the compaction process is operating on a
608
   * database by obtaining the database meta information, the `compact_running` value of the returned database
609
   * structure will be set to true. You can also obtain a list of running processes to determine whether compaction is
610
   * currently running, using getActiveTasks().
611
   * @param[in] string $name The database name.
612
   * @attention Requires admin privileges.
613
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html
614
   */
615 View Code Duplication
  public function compactDb($name) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
616
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($name)."/_compact");
617
618
    // A POST method requires Content-Type header.
619
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
620
621
    $this->send($request);
622
  }
623
624
625
  /**
626
   * @brief Compacts the specified view.
627
   * @details If you have very large views or are tight on space, you might consider compaction as well. To run compact
628
   * for a particular view on a particular database, use this method.
629
   * @param[in] string $dbName The database name.
630
   * @param[in] string $designDocName Name of the design document where is stored the view.
631
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-compact-design-doc
632
   */
633
  public function compactView($dbName, $designDocName) {
634
    $path = "/".rawurlencode($dbName)."/_compact/".$designDocName;
635
636
    $request = new Request(Request::POST_METHOD, $path);
637
638
    // A POST method requires Content-Type header.
639
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
640
641
    $this->send($request);
642
  }
643
644
645
  /**
646
   * @brief Removes all outdated view indexes.
647
   * @details Old views files remain on disk until you explicitly run cleanup.
648
   * @param[in] string $dbName The database name.
649
   * @attention Requires admin privileges.
650
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-view-cleanup
651
   */
652 View Code Duplication
  public function cleanupViews($dbName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
653
    $request =  new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_view_cleanup");
654
655
    // A POST method requires Content-Type header.
656
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
657
658
    $this->send($request);
659
  }
660
661
662
  /**
663
   * @brief Makes sure all uncommited database changes are written and synchronized to the disk.
664
   * @details Default CouchDB configuration use delayed commit to improve performances. So CouchDB allows operations to
665
   * be run against the disk without an explicit fsync after each operation. Synchronization takes time (the disk may
666
   * have to seek, on some platforms the hard disk cache buffer is flushed, etc.), so requiring an fsync for each update
667
   * deeply limits CouchDB's performance for non-bulk writers.\n
668
   * Delayed commit should be left set to true in the configuration settings. Anyway, you can still tell CouchDB to make
669
   * an fsync, calling the this method.
670
   * @param[in] string $dbName The database name.
671
   * @retval string The timestamp of the last time the database file was opened.
672
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-ensure-full-commit
673
   */
674 View Code Duplication
  public function ensureFullCommit($dbName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
675
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_ensure_full_commit");
676
677
    // A POST method requires Content-Type header.
678
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
679
680
    return $this->send($request)->getBodyAsArray()["instance_start_time"];
681
  }
682
683
  //!@}
684
685
686
  /**
687
   * @name Security Methods
688
   * @details The security object consists of two compulsory elements, admins and members, which are used to specify
689
   * the list of users and/or roles that have admin and members rights to the database respectively:
690
   * - members: they can read all types of documents from the DB, and they can write (and edit) documents to the
691
   * database except for design documents.
692
   * - admins: they have all the privileges of members plus the privileges: write (and edit) design documents, add/remove
693
   * database admins and members, set the database revisions limit and execute temporary views against the database.
694
   * They can not create a database nor delete a database.
695
   *
696
   * Both members and admins objects are contains two array-typed fields:
697
   * - users: List of CouchDB user names
698
   * - roles: List of users roles
699
   *
700
   * Any other additional fields in the security object are optional. The entire security object is made available to
701
   * validation and other internal functions so that the database can control and limit functionality.
702
   * If both the names and roles fields of either the admins or members properties are empty arrays, it means the
703
   * database has no admins or members.\n
704
   * Having no admins, only server admins (with the reserved _admin role) are able to update design document and make
705
   * other admin level changes.\n
706
   * Having no members, any user can write regular documents (any non-design document) and read documents from the database.
707
   * If there are any member names or roles defined for a database, then only authenticated users having a matching name
708
   * or role are allowed to read documents from the database.
709
   */
710
  //!@{
711
712
  /**
713
   * @brief Returns the special security object for the database.
714
   * @param[in] string $dbName The database name.
715
   * @retval string A JSON object.
716
   * @see http://docs.couchdb.org/en/latest/api/database/security.html#get--db-_security
717
   */
718
  public function getSecurityObj($dbName) {
719
    return $this->send(new Request(Request::GET_METHOD, "/".rawurlencode($dbName)."/_security"));
720
  }
721
722
723
  /**
724
   * @brief Sets the special security object for the database.
725
   * @param[in] string $dbName The database name.
726
   * @see http://docs.couchdb.org/en/latest/api/database/security.html#put--db-_security
727
   */
728
  public function setSecurityObj($dbName) {
729
    return $this->send(new Request(Request::PUT_METHOD, "/".rawurlencode($dbName)."/_security"));
730
  }
731
732
  //!@}
733
734
735
  /**
736
   * @name Database Replication Methods
737
   * @details The replication is an incremental one way process involving two databases (a source and a destination).
738
   * The aim of the replication is that at the end of the process, all active documents on the source database are also
739
   * in the destination database and all documents that were deleted in the source databases are also deleted (if
740
   * exists) on the destination database.\n
741
   * The replication process only copies the last revision of a document, so all previous revisions that were only on
742
   * the source database are not copied to the destination database.\n
743
   * Changes on the master will not automatically replicate to the slaves. To make replication continuous, you must set
744
   * `$continuous = true`. At this time, CouchDB does not remember continuous replications over a server restart.
745
   * Specifying a local source database and a remote target database is called push replication and a remote source and
746
   * local target is called pull replication. As of CouchDB 0.9, pull replication is a lot more efficient and resistant
747
   * to errors, and it is suggested that you use pull replication in most cases, especially if your documents are large
748
   * or you have large attachments.
749
   */
750
  //!@{
751
752
  /**
753
   * @brief Starts replication.
754
   @code
755
     startReplication("sourcedbname", "http://example.org/targetdbname", TRUE, TRUE);
756
   @endcode
757
   * @param[in] string $sourceDbUrl Source database URL.
758
   * @param[in] string $targetDbUrl Destination database URL.
759
   * @param[in] string $proxy (optional) Specify the optional proxy used to perform the replication.
760
   * @param[in] bool $createTargetDb (optional) The target database has to exist and is not implicitly created. You
761
   * can force the creation setting `$createTargetDb = true`.
762
   * @param[in] bool $continuous (optional) When `true` CouchDB will not stop after replicating all missing documents
763
   * from the source to the target.\n
764
   * At the time of writing, CouchDB doesn't remember continuous replications over a server restart. For the time being,
765
   * you are required to trigger them again when you restart CouchDB. In the future, CouchDB will allow you to define
766
   * permanent continuous replications that survive a server restart without you having to do anything.
767
   * @param[in] string|array $filter (optional) Name of a filter function that can choose which revisions get replicated.
768
   * You can also provide an array of document identifiers; if given, only these documents will be replicated.
769
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
770
   * docs, etc.
771
   * @retval Response
772
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#post--_replicate
773
   */
774
  public function startReplication($sourceDbUrl, $targetDbUrl, $proxy = NULL, $createTargetDb = TRUE,
775
                                     $continuous = FALSE, $filter = NULL, Opt\ViewQueryOpts $opts = NULL) {
776
    // Sets source and target databases.
777
    if (is_string($sourceDbUrl) && !empty($sourceDbUrl) &&
778
      is_string($targetDbUrl) && !empty($targetDbUrl)) {
779
      $body["source"] = $sourceDbUrl;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
780
      $body["target"] = $targetDbUrl;
781
    }
782
    else
783
      throw new \InvalidArgumentException("\$source_db_url and \$target_db_url must be non-empty strings.");
784
785
    if (!is_bool($continuous))
786
      throw new \InvalidArgumentException("\$continuous must be a bool.");
787
    elseif ($continuous)
788
      $body["continuous"] = $continuous;
789
790
    // Uses the specified proxy if any set.
791
    if (isset($proxy))
792
      $body["proxy"] = $this->proxy;
0 ignored issues
show
Bug introduced by
The property proxy does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
793
794
    // create_target option
795
    if (!is_bool($createTargetDb))
796
      throw new \InvalidArgumentException("\$createTargetDb must be a bool.");
797
    elseif ($createTargetDb)
798
      $body["create_target"] = $createTargetDb;
799
800
    if (!empty($filter)) {
801
      if (is_string($filter)) // filter option
802
        $body["filter"] = $filter;
803
      elseif (is_array($filter)) // doc_ids option
804
        $body["doc_ids"] = array_values($filter);
805
      else
806
        throw new \InvalidArgumentException("\$filter must be a string or an array.");
807
    }
808
809
    // queryParams option
810
    if (!is_null($opts)) {
811
      if ($opts instanceof Opt\ViewQueryOpts)
812
        $body["query_params"] = get_object_vars($opts);
813
      else
814
        throw new \InvalidArgumentException("\$queryParams must be an instance of ViewQueryOpts class.");
815
    }
816
817
    $request = new Request(Request::POST_METHOD, "/_replicate");
818
    $request->setBody($body);
819
820
    return $this->send($request);
821
  }
822
823
824
  /**
825
   * @brief Cancels replication.
826
   * @param[in] string $replicationId The replication ID.
827
   * @retval Response
828
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#canceling-continuous-replication
829
   */
830
  public function stopReplication($replicationId) {
831
    if (is_null($replicationId))
832
      throw new \InvalidArgumentException("You must provide a replication id.");
833
834
    $body["replication_id"] = $replicationId;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
835
    $body["cancel"] = TRUE;
836
837
    $request = new Request(Request::POST_METHOD, "/_replicate");
838
    $request->setBody($body);
839
840
    return $this->send($request);
841
  }
842
843
844
  /**
845
   * @brief
846
   * @details
847
   * @see http://wiki.apache.org/couchdb/Replication#Replicator_database
848
   * @see http://docs.couchbase.org/couchdb-release-1.1/index.html#couchb-release-1.1-replicatordb
849
   * @see https://gist.github.com/832610
850
   * @todo To be implemented and documented.
851
   */
852
  public function getReplicator() {
853
    throw new \BadMethodCallException("The method `getReplicator()` is not available.");
854
  }
855
856
  //!@}
857
858
859
  /** @name Query Documents Methods
860
   * @details Queries are the primary mechanism for retrieving a result set from a view. The result of a query is an
861
   * instance of `QueryResult`, a class that implements the [IteratorAggregate](http://php.net/manual/en/class.iteratoraggregate.php),
862
   * [Countable](http://php.net/manual/en/class.countable.php) and [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php)
863
   * interfaces, so you can use the result set as an array.
864
   */
865
  //!@{
866
867
  /**
868
   * @brief Returns a built-in view of all documents in this database. If keys are specified returns only certain rows.
869
   * @param[in] string $dbName The database name.
870
   * @param[in] array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
871
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
872
   * multi-document-fetch feature.
873
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
874
   * docs, etc.
875
   * @param[in] ChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
876
   * @retval Result::QueryResult The result of the query.
877
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#get--db-_all_docs
878
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_all_docs
879
   */
880
  public function queryAllDocs($dbName, array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, Hook\IChunkHook $chunkHook = NULL) {
881
    if (is_null($keys))
882
      $request = new Request(Request::GET_METHOD, "/".rawurlencode($dbName)."/_all_docs");
883
    else {
884
      $request = new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_all_docs");
885
      $request->setBody(json_encode(['keys' => $keys]));
886
    }
887
888
    if (isset($opts))
889
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
890
891
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
892
893
    return new Result\QueryResult($result);
894
  }
895
896
897
  /**
898
   * @brief Executes the given view and returns the result.
899
   * @param[in] string $dbName The database name.
900
   * @param[in] string $designDocName The design document's name.
901
   * @param[in] string $viewName The view's name.
902
   * @param[in] array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
903
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
904
   * multi-document-fetch feature.
905
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
906
   * docs, etc.
907
   * @param[in] IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
908
   * @retval Result::QueryResult The result of the query.
909
   * @attention Multiple keys request to a reduce function only supports `group=true` (identical to `group-level=exact`,
910
   * but it doesn't support `group_level` > 0.
911
   * CouchDB raises "Multi-key fetchs for reduce view must include `group=true`" error, in case you use `group_level`.
912
   * @see http://docs.couchdb.org/en/latest/api/ddoc/views.html#get--db-_design-ddoc-_view-view
913
   * @see http://docs.couchdb.org/en/latest/api/ddoc/views.html#post--db-_design-ddoc-_view-view
914
   */
915
  public function queryView($dbName, $designDocName, $viewName, array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, Hook\IChunkHook $chunkHook = NULL) {
916
    $this->validateAndEncodeDocId($designDocName);
917
918
    if (empty($viewName))
919
      throw new \InvalidArgumentException("You must provide a valid \$viewName.");
920
921
    if (empty($keys))
922
      $request = new Request(Request::GET_METHOD, "/".rawurlencode($dbName)."/_design/".$designDocName."/_view/".$viewName);
923
    else {
924
      $request = new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_design/".$designDocName."/_view/".$viewName);
925
      $request->setBody(json_encode(['keys' => $keys]));
926
    }
927
928 View Code Duplication
    if (isset($opts)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
929
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
930
      $includeMissingKeys = $opts->issetIncludeMissingKeys();
931
    }
932
    else
933
      $includeMissingKeys = FALSE;
934
935
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
936
937
    if ($includeMissingKeys)
938
      $this->addMissingRows($keys, $result['rows']);
939
940
    return new Result\QueryResult($result);
941
  }
942
943
944
  /**
945
   * @brief Executes the given view, both map and reduce functions, for all documents and returns the result.
946
   * @details Map and Reduce functions are provided by the programmer.
947
   * @attention Requires admin privileges.
948
   * @param[in] string $dbName The database name.
949
   * @param[in] string $mapFn The map function.
950
   * @param[in] string $reduceFn The reduce function.
951
   * @param[in] array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
952
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
953
   * multi-document-fetch feature.
954
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
955
   * docs, etc.
956
   * @param[in] string $language The language used to implement the map and reduce functions.
957
   * @param[in] IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
958
   * @retval Result::QueryResult The result of the query.
959
   * @see http://docs.couchdb.org/en/latest/api/database/temp-views.html#post--db-_temp_view
960
   */
961
  public function queryTempView($dbName, $mapFn, $reduceFn = "", array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, $language = 'php', Hook\IChunkHook $chunkHook = NULL) {
962
    $handler = new Handler\ViewHandler('temp');
963
    $handler->language = $language;
964
    $handler->mapFn = $mapFn;
965
    if (!empty($reduce))
0 ignored issues
show
Bug introduced by
The variable $reduce does not exist. Did you mean $reduceFn?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
966
      $handler->reduceFn = $reduceFn;
967
968
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_temp_view");
969
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
970
971
    $array = $handler->asArray();
972
973
    if (!empty($keys))
974
      $array['keys'] = $keys;
975
976
    $request->setBody(json_encode($array));
977
978 View Code Duplication
    if (isset($opts)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
979
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
980
      $includeMissingKeys = $opts->issetIncludeMissingKeys();
981
    }
982
    else
983
      $includeMissingKeys = FALSE;
984
985
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
986
987
    if ($includeMissingKeys)
988
      $this->addMissingRows($keys, $result['rows']);
989
990
    return new Result\QueryResult($result);
991
  }
992
993
  //!@}
994
995
996
  /** @name Revisions Management Methods */
997
  //!@{
998
999
  /**
1000
   * @brief Given a list of document revisions, returns the document revisions that do not exist in the database.
1001
   * @param[in] string $dbName The database name.
1002
   * @retval Response
1003
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#db-missing-revs
1004
   */
1005
  public function getMissingRevs($dbName) {
1006
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_missing_revs");
1007
1008
    return $this->send($request);
1009
  }
1010
1011
1012
  /**
1013
   * @brief Given a list of document revisions, returns differences between the given revisions and ones that are in
1014
   * the database.
1015
   * @param[in] string $dbName The database name.
1016
   * @retval Response
1017
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#db-revs-diff
1018
   */
1019
  public function getRevsDiff($dbName) {
1020
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($dbName)."/_missing_revs");
1021
1022
    return $this->send($request);
1023
  }
1024
1025
1026
  /**
1027
   * @brief Gets the limit of historical revisions to store for a single document in the database.
1028
   * @param[in] string $dbName The database name.
1029
   * @retval integer The maximum number of document revisions that will be tracked by CouchDB.
1030
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#get--db-_revs_limit
1031
   */
1032
  public function getRevsLimit($dbName) {
1033
    $request = new Request(Request::GET_METHOD, "/".rawurlencode($dbName)."/_revs_limit");
1034
1035
    return (integer)$this->send($request)->getBody();
1036
  }
1037
1038
1039
  /**
1040
   * @brief Sets the limit of historical revisions for a single document in the database.
1041
   * @param[in] string $dbName The database name.
1042
   * @param[in] integer $revsLimit (optional) Maximum number historical revisions for a single document in the database.
1043
   * Must be a positive integer.
1044
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#put--db-_revs_limit
1045
   */
1046
  public function setRevsLimit($dbName, $revsLimit = self::REVS_LIMIT) {
1047
    if (!is_int($revsLimit) or ($revsLimit <= 0))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1048
      throw new \InvalidArgumentException("\$revsLimit must be a positive integer.");
1049
1050
    $request = new Request(Request::PUT_METHOD, "/".rawurlencode($dbName)."/_revs_limit");
1051
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1052
    $request->setBody(json_encode($revsLimit));
1053
1054
    $this->send($request);
1055
  }
1056
1057
  //!@}
1058
1059
1060
  /** @name Documents Management Methods */
1061
  //!@{
1062
1063
  /**
1064
   * @brief Returns the document's entity tag, that can be used for caching or optimistic concurrency control purposes.
1065
   * The ETag Header is simply the document's revision in quotes.
1066
   * @details This function is not available for special documents. To get information about a design document, use
1067
   * the special function getDesignDocInfo().
1068
   * @param[in] string $dbName The database name.
1069
   * @param[in] string $docId The document's identifier.
1070
   * @retval string The document's revision.
1071
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#db-doc
1072
   */
1073 View Code Duplication
  public function getDocEtag($dbName, $docId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1074
    $this->validateAndEncodeDocId($docId);
1075
1076
    $path = "/".rawurlencode($dbName)."/".$docId;
1077
1078
    $request = new Request(Request::HEAD_METHOD, $path);
1079
1080
    // CouchDB ETag is included between quotation marks.
1081
    return trim($this->send($request)->getHeaderFieldValue(Response::ETAG_HF), '"');
1082
  }
1083
1084
1085
  /**
1086
   * @brief Returns the latest revision of the document.
1087
   * @details Since CouchDB uses different paths to store special documents, you must provide the document type for
1088
   * design and local documents.
1089
   * @param[in] string $dbName The database name.
1090
   * @param[in] string $docId The document's identifier.
1091
   * @param[in] string $path The document's path.
1092
   * @param[in] string $rev (optional) The document's revision.
1093
   * @param[in] DocOpts $opts Query options to get additional document information, like conflicts, attachments, etc.
1094
   * @retval object|Response An instance of Doc, LocalDoc, DesignDoc or any subclass of Doc.
1095
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
1096
   */
1097
  public function getDoc($dbName, $path, $docId, $rev = NULL, Opt\DocOpts $opts = NULL) {
1098
    $this->validateDocPath($path);
1099
    $this->validateAndEncodeDocId($docId);
1100
1101
    $requestPath = "/".rawurlencode($dbName)."/".$path.$docId;
1102
1103
    $request = new Request(Request::GET_METHOD, $requestPath);
1104
1105
    // Retrieves the specific revision of the document.
1106
    if (!empty($rev))
1107
      $request->setQueryParam("rev", (string)$rev);
1108
1109
    // If there are any options, add them to the request.
1110
    if (isset($opts)) {
1111
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
1112
      $ignoreClass = $opts->issetIgnoreClass();
1113
    }
1114
    else
1115
      $ignoreClass = FALSE;
1116
1117
    $response = $this->send($request);
1118
    $body = $response->getBodyAsArray();
1119
1120
    // We use `class` metadata to store an instance of a specialized document class. We can have Article and Book classes,
1121
    // both derived from Doc, with special properties and methods. Instead to convert them, we store the class name in a
1122
    // special attribute called 'class' within the others metadata. So, once we retrieve the document, the client creates
1123
    // an instance of the class we provided when we saved the document; we never need to convert it.
1124
    if (!$ignoreClass && isset($body['class'])) { // Special document class inherited from Doc or LocalDoc.
1125
      $class = "\\".$body['class'];
1126
      $doc = new $class;
1127
    }
1128
    elseif ($path == self::DESIGN_DOC_PATH)
1129
      $doc = new Doc\DesignDoc;
1130
    else
1131
      $doc = NULL;
1132
1133
    if (is_object($doc)) {
1134
      $doc->assignArray($body);
1135
      return $doc;
1136
    }
1137
    else
1138
      return $response;
1139
  }
1140
1141
1142
  /**
1143
   * @brief Inserts or updates a document into the selected database.
1144
   * @details Whether the `$doc` has an id we use a different HTTP method. Using POST CouchDB generates an id for the doc,
1145
   * using PUT instead we need to specify one. We can still use the function getUuids() to ask CouchDB for some ids.
1146
   * This is an internal detail. You have only to know that CouchDB can generate the document id for you.
1147
   * @param[in] string $dbName The database name.
1148
   * @param[in] Doc $doc The document you want insert or update.
1149
   * @param[in] bool $batchMode (optional) You can write documents to the database at a higher rate by using the batch
1150
   * option. This collects document writes together in memory (on a user-by-user basis) before they are committed to
1151
   * disk. This increases the risk of the documents not being stored in the event of a failure, since the documents are
1152
   * not written to disk immediately.
1153
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid
1154
   */
1155
  public function saveDoc($dbName, Doc\IDoc $doc, $batchMode = FALSE) {
1156
    // Whether the document has an id we use a different HTTP method. Using POST CouchDB generates an id for the doc
1157
    // using PUT we need to specify one. We can still use the function getUuids() to ask CouchDB for some ids.
1158
    if (!$doc->issetId())
1159
      $doc->setId(Generator\UUID::generate(Generator\UUID::UUID_RANDOM, Generator\UUID::FMT_STRING));
1160
1161
    $this->setDocInfo($doc);
1162
1163
    // We never use the POST method.
1164
    $method = Request::PUT_METHOD;
1165
1166
    $path = "/".rawurlencode($dbName)."/".$doc->getPath().$doc->getId();
1167
1168
    $request = new Request($method, $path);
1169
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1170
    $request->setBody($doc->asJson());
1171
1172
    // Enables batch mode.
1173
    if ($batchMode)
1174
      $request->setQueryParam("batch", "ok");
1175
1176
    $this->send($request);
1177
  }
1178
1179
1180
  /**
1181
   * @brief Deletes the specified document.
1182
   * @details To delete a document you must provide the document identifier and the revision number.
1183
   * @param[in] string $dbName The database name.
1184
   * @param[in] string $docId The document's identifier you want delete.
1185
   * @param[in] string $rev The document's revision number you want delete.
1186
   * @param[in] string $path The document path.
1187
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#delete--db-docid
1188
   */
1189
  public function deleteDoc($dbName, $path, $docId, $rev) {
1190
    $this->validateDocPath($path);
1191
    $this->validateAndEncodeDocId($docId);
1192
1193
    $path = "/".rawurlencode($dbName)."/".$path.$docId;
1194
1195
    $request = new Request(Request::DELETE_METHOD, $path);
1196
    $request->setQueryParam("rev", (string)$rev);
1197
1198
    // We could use another technique to send the revision number. Here just for documentation.
1199
    // $request->setHeader(Request::IF_MATCH_HEADER, (string)$rev);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1200
1201
    $this->send($request);
1202
  }
1203
1204
1205
  /**
1206
   * @brief Makes a duplicate of the specified document. If you want to overwrite an existing document, you need to
1207
   * specify the target document's revision with a `$rev` parameter.
1208
   * @details If you want copy a special document you must specify his type.
1209
   * @param[in] string $dbName The database name.
1210
   * @param[in] string $sourceDocId The source document id.
1211
   * @param[in] string $targetDocId The destination document id.
1212
   * @param[in] string $rev Needed when you want override an existent document.
1213
   * @param[in] string $path The document path.
1214
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#copy--db-docid
1215
   */
1216
  public function copyDoc($dbName, $path, $sourceDocId, $targetDocId, $rev = NULL) {
1217
    $this->validateDocPath($path);
1218
1219
    $this->validateAndEncodeDocId($sourceDocId);
1220
    $this->validateAndEncodeDocId($targetDocId);
1221
1222
    $path = "/".rawurlencode($dbName)."/".$path.$sourceDocId;
1223
1224
    // This request uses the special method COPY.
1225
    $request = new Request(Request::COPY_METHOD, $path);
1226
1227
    if (empty($rev))
1228
      $request->setHeaderField(Request::DESTINATION_HF, $targetDocId);
1229
    else
1230
      $request->setHeaderField(Request::DESTINATION_HF, $targetDocId."?rev=".(string)$rev);
1231
1232
    $this->send($request);
1233
  }
1234
1235
1236
  /**
1237
   * @brief The purge operation removes the references to the deleted documents from the database.
1238
   * @details A database purge permanently removes the references to deleted documents from the database. Deleting a
1239
   * document within CouchDB does not actually remove the document from the database, instead, the document is marked
1240
   * as deleted (and a new revision is created). This is to ensure that deleted documents are replicated to other
1241
   * databases as having been deleted. This also means that you can check the status of a document and identify that
1242
   * the document has been deleted.\n
1243
   * The purging of old documents is not replicated to other databases. If you are replicating between databases and
1244
   * have deleted a large number of documents you should run purge on each database.\n
1245
   * Purging documents does not remove the space used by them on disk. To reclaim disk space, you should run compactDb().\n
1246
   * @param[in] string $dbName The database name.
1247
   * @param[in] array $refs An array of references used to identify documents and revisions to delete. The array must
1248
   * contains instances of the DocRef class.
1249
   * @retval Response
1250
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#post--db-_purge
1251
   * @see http://wiki.apache.org/couchdb/Purge_Documents
1252
   */
1253
  public function purgeDocs($dbName, array $refs) {
1254
    $path = "/".rawurlencode($dbName)."/_purge";
1255
1256
    $request = new Request(Request::POST_METHOD, $path);
1257
1258
    $purge = [];
1259
    foreach ($refs as $ref)
1260
      $purge[] = $ref->asArray();
1261
1262
    $request->setBody(json_encode($purge));
1263
1264
    return $this->send($request);
1265
  }
1266
1267
1268
  /**
1269
   * @brief Inserts, updates and deletes documents in a bulk.
1270
   * @details Documents that are updated or deleted must contain the `rev` number. To delete a document, you should
1271
   * call delete() method on your document. When creating new documents the document ID is optional. For updating existing
1272
   * documents, you must provide the document ID and revision.
1273
   * @param[in] string $dbName The database name.
1274
   * @param[in] array $docs An array of documents you want insert, delete or update.
1275
   * @param[in] bool $fullCommit (optional) Makes sure all uncommited database changes are written and synchronized
1276
   * to the disk immediately.
1277
   * @param[in] bool $allOrNothing (optional) In all-or-nothing mode, either all documents are written to the database,
1278
   * or no documents are written to the database, in the event of a system failure during commit.\n
1279
   * You can ask CouchDB to check that all the documents pass your validation functions. If even one fails, none of the
1280
   * documents are written. If all documents pass validation, then all documents will be updated, even if that introduces
1281
   * a conflict for some or all of the documents.
1282
   * @param[in] bool $newEdits (optional) When `false` CouchDB pushes existing revisions instead of creating
1283
   * new ones. The response will not include entries for any of the successful revisions (since their rev IDs are
1284
   * already known to the sender), only for the ones that had errors. Also, the conflict error will never appear,
1285
   * since in this mode conflicts are allowed.
1286
   * @retval Response
1287
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#db-bulk-docs
1288
   * @see http://docs.couchdb.org/en/latest/json-structure.html#bulk-documents
1289
   * @see http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
1290
   */
1291
  public function performBulkOperations($dbName, array $docs, $fullCommit = FALSE, $allOrNothing = FALSE, $newEdits = TRUE) {
1292
    if (count($docs) == 0)
1293
      throw new \InvalidArgumentException("The \$docs array cannot be empty.");
1294
    else
1295
      $operations = [];
1296
1297
    $path = "/".rawurlencode($dbName)."/_bulk_docs";
1298
1299
    $request = new Request(Request::POST_METHOD, $path);
1300
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1301
1302
    if ($fullCommit)
1303
      $request->setHeaderField(Request::X_COUCHDB_FULL_COMMIT_HF, "full_commit");
1304
    else
1305
      $request->setHeaderField(Request::X_COUCHDB_FULL_COMMIT_HF, "delay_commit");
1306
1307
    if ($allOrNothing)
1308
      $operations['all_or_nothing'] = 'true';
1309
1310
    if (!$newEdits)
1311
      $operations['new_edits'] = 'false';
1312
1313
    foreach ($docs as $doc) {
1314
      $this->setDocInfo($doc);
1315
      $operations['docs'][] = $doc->asArray();
1316
    }
1317
1318
    $request->setBody(json_encode($operations));
1319
1320
    return $this->send($request);
1321
  }
1322
1323
  //!@}
1324
1325
1326
  /** @name Attachments Management Methods */
1327
  //!@{
1328
1329
1330
  /**
1331
   * @brief Returns the minimal amount of information about the specified attachment.
1332
   * @param[in] string $dbName The database name.
1333
   * @param[in] string $fileName The attachment's name.
1334
   * @param[in] string $docId The document's identifier.
1335
   * @param[in] string $path The document's path.
1336
   * @param[in] string $rev (optional) The document's revision.
1337
   * @retval string The document's revision.
1338
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#db-doc-attachment
1339
   */
1340 View Code Duplication
  public function getAttachmentInfo($dbName, $fileName, $path, $docId, $rev = NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1341
    $this->validateDocPath($path, TRUE);
1342
    $this->validateAndEncodeDocId($docId);
1343
1344
    $path = "/".rawurlencode($dbName)."/".$path.$docId."/".$fileName;
1345
1346
    $request = new Request(Request::HEAD_METHOD, $path);
1347
1348
    // In case we want retrieve a specific document revision.
1349
    if (!empty($rev))
1350
      $request->setQueryParam("rev", (string)$rev);
1351
1352
    return $this->send($request);
1353
  }
1354
1355
1356
  /**
1357
   * @brief Retrieves the attachment from the specified document.
1358
   * @param[in] string $dbName The database name.
1359
   * @param[in] string $fileName The attachment's name.
1360
   * @param[in] string $docId The document's identifier.
1361
   * @param[in] string $path The document's path.
1362
   * @param[in] string $rev (optional) The document's revision.
1363
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#get--db-docid-attname
1364
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#http-range-requests
1365
   * @todo Add support for Range request, using header "Range: bytes=0-12".
1366
   */
1367 View Code Duplication
  public function getAttachment($dbName, $fileName, $path, $docId, $rev = NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1368
    $this->validateDocPath($path, TRUE);
1369
    $this->validateAndEncodeDocId($docId);
1370
1371
    $path = "/".rawurlencode($dbName)."/".$path.$docId."/".$fileName;
1372
1373
    $request = new Request(Request::GET_METHOD, $path);
1374
1375
    // In case we want retrieve a specific document revision.
1376
    if (!empty($rev))
1377
      $request->setQueryParam("rev", (string)$rev);
1378
1379
    return $this->send($request)->getBody();
1380
  }
1381
1382
1383
  /**
1384
   * @brief Inserts or updates an attachment to the specified document.
1385
   * @param[in] string $dbName The database name.
1386
   * @param[in] string $fileName The attachment's name.
1387
   * @param[in] string $docId The document's identifier.
1388
   * @param[in] string $path The document's path.
1389
   * @param[in] string $rev (optional) The document's revision.
1390
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#put--db-docid-attname
1391
   */
1392
  public function putAttachment($dbName, $fileName, $path, $docId, $rev = NULL) {
1393
    $this->validateDocPath($path, TRUE);
1394
    $this->validateAndEncodeDocId($docId);
1395
1396
    $attachment = Doc\Attachment\Attachment::fromFile($fileName);
1397
1398
    $path = "/".rawurlencode($dbName)."/".$path.$docId."/".rawurlencode($attachment->getName());
1399
1400
    $request = new Request(Request::PUT_METHOD, $path);
1401
    $request->setHeaderField(Request::CONTENT_LENGTH_HF, $attachment->getContentLength());
1402
    $request->setHeaderField(Request::CONTENT_TYPE_HF, $attachment->getContentType());
1403
    $request->setBody(base64_encode($attachment->getData()));
1404
1405
    // In case of adding or updating an existence document.
1406
    if (!empty($rev))
1407
      $request->setQueryParam("rev", (string)$rev);
1408
1409
    return $this->send($request);
1410
  }
1411
1412
1413
  /**
1414
   * @brief Deletes an attachment from the document.
1415
   * @param[in] string $dbName The database name.
1416
   * @param[in] string $fileName The attachment's name.
1417
   * @param[in] string $docId The document's identifier.
1418
   * @param[in] string $path The document's path.
1419
   * @param[in] string $rev The document's revision.
1420
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#delete--db-docid-attname
1421
   */
1422 View Code Duplication
  public function deleteAttachment($dbName, $fileName, $path, $docId, $rev) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1423
    $this->validateDocPath($path, TRUE);
1424
    $this->validateAndEncodeDocId($docId);
1425
1426
    $path = "/".rawurlencode($dbName)."/".$path.$docId."/".rawurlencode($fileName);
1427
1428
    $request = new Request(Request::DELETE_METHOD, $path);
1429
    $request->setQueryParam("rev", (string)$rev);
1430
1431
    return $this->send($request);
1432
  }
1433
1434
  //!@}
1435
1436
1437
  /** @name Special Design Documents Management Methods */
1438
  //!@{
1439
1440
1441
  /**
1442
   * @brief Returns basic information about the design document and his views.
1443
   * @param[in] string $dbName The database name.
1444
   * @param[in] string $docName The design document's name.
1445
   * @retval array An associative array
1446
   * @see http://docs.couchdb.org/en/latest/api/ddoc/common.html#get--db-_design-ddoc-_info
1447
   */
1448 View Code Duplication
  public function getDesignDocInfo($dbName, $docName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1449
    $this->validateAndEncodeDocId($docName);
1450
1451
    $path = "/".rawurlencode($dbName)."/".self::DESIGN_DOC_PATH.$docName."/_info";
1452
1453
    $request = new Request(Request::GET_METHOD, $path);
1454
1455
    return $this->send($request)->getBodyAsArray();
1456
  }
1457
1458
1459
  /**
1460
   * @brief
1461
   * @details
1462
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_show-func
1463
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_show-func
1464
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_show-func-docid
1465
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_show-func-docid
1466
   * @todo To be implemented and documented.
1467
   */
1468
  public function showDoc($dbName, $designDocName, $showName, $docId = NULL) {
0 ignored issues
show
Unused Code introduced by
The parameter $dbName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $designDocName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $showName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $docId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1469
    throw new \BadMethodCallException("The method `showDoc()` is not available.");
1470
    // Invokes the show handler without a document
1471
    // /db/_design/design-doc/_show/show-name
1472
    // Invokes the show handler for the given document
1473
    // /db/_design/design-doc/_show/show-name/doc
1474
    // GET /db/_design/examples/_show/posts/somedocid
1475
    // GET /db/_design/examples/_show/people/otherdocid
1476
    // GET /db/_design/examples/_show/people/otherdocid?format=xml&details=true
1477
    // public function showDoc($designDocName, $funcName, $docId, $format, $details = FALSE) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1478
  }
1479
1480
1481
  /**
1482
   * @brief
1483
   * @details
1484
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_list-func-view
1485
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_list-func-view
1486
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_list-func-other-ddoc-view
1487
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_list-func-other-ddoc-view
1488
   * @todo To be implemented and documented.
1489
   */
1490
  public function listDocs($dbName, $designDocName, $listName, $docId = NULL) {
0 ignored issues
show
Unused Code introduced by
The parameter $dbName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $designDocName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $listName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $docId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1491
    throw new \BadMethodCallException("The method `listDocs()` is not available.");
1492
    // Invokes the list handler to translate the given view results
1493
    // Invokes the list handler to translate the given view results for certain documents
1494
    // GET /db/_design/examples/_list/index-posts/posts-by-date?descending=true&limit=10
1495
    // GET /db/_design/examples/_list/index-posts/posts-by-tag?key="howto"
1496
    // GET /db/_design/examples/_list/browse-people/people-by-name?startkey=["a"]&limit=10
1497
    // GET /db/_design/examples/_list/index-posts/other_ddoc/posts-by-tag?key="howto"
1498
    // public function listDocs($designDocName, $funcName, $viewName, $queryArgs, $keys = "") {
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1499
  }
1500
1501
1502
  /**
1503
   * @brief
1504
   * @details
1505
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_update-func
1506
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#put--db-_design-ddoc-_update-func-docid
1507
   * @todo To be implemented and documented.
1508
   */
1509
  public function callUpdateDocFunc($dbName, $designDocName, $funcName) {
0 ignored issues
show
Unused Code introduced by
The parameter $dbName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $designDocName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $funcName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1510
    throw new \BadMethodCallException("The method `callUpdateDocFunc()` is not available.");
1511
    // Invokes the update handler without a document
1512
    // /db/_design/design-doc/_update/update-name
1513
    // Invokes the update handler for the given document
1514
    // /db/_design/design-doc/_update/update-name/doc
1515
    // a PUT request against the handler function with a document id: /<database>/_design/<design>/_update/<function>/<docid>
1516
    // a POST request against the handler function without a document id: /<database>/_design/<design>/_update/<function>
1517
  }
1518
1519
  //!@}
1520
1521
}