Completed
Push — master ( 7c85d1...76501f )
by Filippo
06:24 queued 03:18
created

Couch   F

Complexity

Total Complexity 153

Size/Duplication

Total Lines 1518
Duplicated Lines 6.19 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 11
Bugs 2 Features 2
Metric Value
wmc 153
c 11
b 2
f 2
lcom 1
cbo 20
dl 94
loc 1518
rs 0.5217

65 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A WildCard() 0 3 1
A setDocInfo() 0 12 2
B addMissingRows() 0 26 6
A setDbPrefix() 0 3 1
A getDbPrefix() 0 3 1
C send() 0 39 7
B validateDocPath() 0 11 5
A validateAndEncodeDocId() 0 6 2
A createAdminUser() 0 3 1
A restartServer() 0 14 3
A getServerInfo() 0 5 1
A getClientInfo() 0 3 1
A getFavicon() 0 8 2
A getStats() 0 3 1
A getAllDbs() 0 3 1
A getDbUpdates() 0 8 2
A getActiveTasks() 0 3 1
A getLogTail() 0 9 3
A getUuids() 0 15 4
A getConfig() 0 12 3
B setConfigKey() 0 15 6
B deleteConfigKey() 0 9 5
A getSession() 0 3 1
B setSession() 0 17 5
A deleteSession() 0 3 1
A getAccessToken() 0 3 1
A getAuthorize() 0 3 1
A setAuthorize() 0 3 1
A requestToken() 0 3 1
A createDb() 0 17 2
A deleteDb() 0 3 1
A getDbInfo() 0 3 1
A getDbChanges() 0 8 2
A compactDb() 8 8 1
A compactView() 0 10 1
A cleanupViews() 8 8 1
A ensureFullCommit() 8 8 1
A getSecurityObj() 0 3 1
A setSecurityObj() 0 3 1
C startReplication() 0 48 15
A stopReplication() 0 12 2
A getReplicator() 0 3 1
A queryAllDocs() 0 15 3
B queryView() 6 27 5
B queryTempView() 6 31 5
A getMissingRevs() 0 5 1
A getRevsDiff() 0 5 1
A getRevsLimit() 0 5 1
A setRevsLimit() 0 10 3
A getDocEtag() 10 10 1
C getDoc() 0 43 7
A saveDoc() 0 23 3
A deleteDoc() 0 14 1
A copyDoc() 0 18 2
A purgeDocs() 0 13 2
B performBulkOperations() 0 31 6
A getAttachmentInfo() 14 14 2
A getAttachment() 14 14 2
A putAttachment() 0 19 2
A deleteAttachment() 11 11 1
A getDesignDocInfo() 9 9 1
A showDoc() 0 11 1
A listDocs() 0 10 1
A callUpdateDocFunc() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Couch often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Couch, and based on these observations, apply Extract Interface, too.

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
  // A database's name prefix.
47
  private $prefix = '';
48
49
50
  /**
51
   * @brief Creates a Couch class instance.
52
   * @param[in] IClientAdapter $adapter An instance of a class that implements the IClientAdapter interface.
53
   */
54
  public function __construct(IClientAdapter $adapter) {
55
    $this->client = $adapter;
56
  }
57
58
59
  /**
60
   * @brief Returns a CouchDB wild card.
61
   * @details A standard object is translated to JSON as `{}` same of a JavaScript empty object.
62
   * @retval [stdClass](http://php.net/manual/en/language.types.object.php)
63
   */
64
  public static function WildCard() {
65
    return new \stdClass();
66
  }
67
68
69
  /**
70
   * @brief Sets the document class and type in case the document hasn't one.
71
   * @param[in] Doc\IDoc $doc A document.
72
   */
73
  private function setDocInfo(Doc\IDoc $doc) {
74
    // Sets the class name.
75
    $class = get_class($doc);
76
    $doc->setClass($class);
77
78
    // Sets the document type.
79
    if (!$doc->hasType()) {
80
      preg_match('/([\w]+$)/', $class, $matches);
81
      $type = strtolower($matches[1]);
82
      $doc->setType($type);
83
    }
84
  }
85
86
87
  /**
88
   * @brief Overrides the rows, adding a new row for every key hasn't been matched.
89
   * @details CouchDB doesn't return rows for the keys a match is not found. To make joins having a row for each key is
90
   * essential. The algorithm below overrides the rows, adding a new row for every key hasn't been matched. The JSON
91
   * encoding is necessary because we might have complex keys. A complex key is no more than an array.
92
   * @param[in] array $keys An array containing the keys.
93
   * @param[out] array $rows An associative array containing the rows.
94
   */
95
  private function addMissingRows($keys, &$rows) {
96
97
    if (!empty($keys) && isset($rows)) {
98
99
      // These are the rows for the matched keys.
100
      $matches = [];
101
      foreach ($rows as $row) {
102
        $hash = md5(json_encode($row['key']));
103
        $matches[$hash] = $row;
104
      }
105
106
      $allRows = [];
107
      foreach ($keys as $key) {
108
        $hash = md5(json_encode($key));
109
110
        if (isset($matches[$hash])) // Match found.
111
          $allRows[] = $matches[$hash];
112
        else // No match found.
113
          $allRows[] = ['id' => NULL, 'key' => $key, 'value' => NULL];
114
      }
115
116
      // Overrides the response, replacing rows.
117
      $rows = $allRows;
118
    }
119
120
  }
121
122
123
  /**
124
   * @brief Sets a prefix which is used to compose the database's name.
125
   * @param[in] string $prefix A string prefix.
126
   */
127
  public function setDbPrefix($prefix) {
128
    $this->prefix = $prefix;
129
  }
130
131
132
  /**
133
   * @brief Gets the prefix used to compose the database's name if any.
134
   * @return string
135
   */
136
  public function getDbPrefix() {
137
    return $this->prefix;
138
  }
139
140
141
  /**
142
   * @brief This method is used to send a Request to CouchDB.
143
   * @details If you want call a not supported CouchDB API, you can use this function to send your request.\n
144
   * You can also provide an instance of a class that implements the IChunkHook interface, to deal with a chunked
145
   * response.
146
   * @param[in] Request $request The Request object.
147
   * @param[in] IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
148
   * @return Response
149
   */
150
  public function send(Request $request, Hook\IChunkHook $chunkHook = NULL) {
151
    // Sets user agent information.
152
    $request->setHeaderField(Request::USER_AGENT_HF, self::USER_AGENT_NAME." ".Version::getNumber());
153
154
    // We accept JSON.
155
    $request->setHeaderField(Request::ACCEPT_HF, "application/json");
156
157
    // We close the connection after read the response.
158
    // NOTE: we don't use anymore the connection header field, because we use the same socket until the end of script.
159
    //$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...
160
161
    $response = $this->client->send($request, $chunkHook);
162
163
    // 1xx - Informational Status Codes
164
    // 2xx - Success Status Codes
165
    // 3xx - Redirection Status Codes
166
    // 4xx - Client Error Status Codes
167
    // 5xx - Server Error Status Codes
168
    $statusCode = (int)$response->getStatusCode();
169
170
    switch ($statusCode) {
171
      case ($statusCode >= 200 && $statusCode < 300):
172
        break;
173
      case ($statusCode < 200):
174
        //$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...
175
        break;
176
      case ($statusCode < 300):
177
        //$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...
178
        break;
179
      case ($statusCode < 400):
180
        throw new Exception\ClientErrorException($request, $response);
181
      case ($statusCode < 500):
182
        throw new Exception\ServerErrorException($request, $response);
183
      default:
184
        throw new Exception\UnknownResponseException($request, $response);
185
    }
186
187
    return $response;
188
  }
189
190
191
  /** @name Validation and Encoding Methods */
192
  //!@{
193
194
  /**
195
   * @brief This method raise an exception when a user provide an invalid document path.
196
   * @details This method is called by any other methods that interacts with CouchDB. You don't need to call, unless
197
   * you are making a not supported call to CouchDB.
198
   * @param[in] string $path Document path.
199
   * @param[in] bool $excludeLocal Document path.
200
   */
201
  public function validateDocPath($path, $excludeLocal = FALSE) {
202
    if (empty($path)) // STD_DOC_PATH
203
      return;
204
205
    if ($path == self::DESIGN_DOC_PATH)
206
      return;
207
    elseif ($path == self::LOCAL_DOC_PATH && $excludeLocal)
208
      throw new \InvalidArgumentException("Local document doesn't have attachments.");
209
    else
210
      throw new \InvalidArgumentException("Invalid document path.");
211
  }
212
213
214
  /**
215
   * @brief Encodes the document id.
216
   * @details This method is called by any other methods that interacts with CouchDB. You don't need to call, unless
217
   * you are making a not supported call to CouchDB.
218
   * @param string $docId Document id.
219
   */
220
  public function validateAndEncodeDocId(&$docId) {
221
    if (!empty($docId))
222
      $docId = rawurlencode($docId);
223
    else
224
      throw new \InvalidArgumentException("\$docId must be a non-empty string.");
225
  }
226
227
  //!@}
228
229
230
  /** @name Miscellaneous Methods */
231
  //!@{
232
233
  /**
234
   * @brief Creates the admin user.
235
   * @todo Implement the method.
236
   */
237
  public function createAdminUser() {
238
    throw new \BadMethodCallException("The method `createAdminUser()` is not available.");
239
  }
240
241
242
  /**
243
   * @brief Restarts the server.
244
   * @attention Requires admin privileges.
245
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#restart
246
   * @bug [COUCHDB-947](https://issues.apache.org/jira/browse/COUCHDB-947)
247
   */
248
  public function restartServer() {
249
    $request = new Request(Request::POST_METHOD, "/_restart");
250
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
251
252
    // There is a bug in CouchDB, sometimes it doesn't return the 200 Status Code because it closes the connection
253
    // before the client has received the entire response. To avoid problems, we trap the exception and we go on.
254
    try {
255
      $this->send($request);
256
    }
257
    catch (\Exception $e) {
258
      if ($e->getCode() > 0)
259
        throw $e;
260
    }
261
  }
262
263
264
  /**
265
   * @brief Returns an object that contains MOTD, server and client and PHP versions.
266
   * @details The MOTD can be specified in CouchDB configuration files. This function returns more information
267
   * compared to the CouchDB standard REST call.
268
   * @retval Info::ServerInfo
269
   */
270
  public function getServerInfo() {
271
    $response = $this->send(new Request(Request::GET_METHOD, "/"));
272
    $info = $response->getBodyAsArray();
273
    return new Info\ServerInfo($info["couchdb"], $info["version"]);
274
  }
275
276
277
  /**
278
   * @brief Returns information about the Elephant on Couch client.
279
   * @retval Info::ClientInfo
280
   */
281
  public function getClientInfo() {
282
    return new Info\ClientInfo();
283
  }
284
285
286
  /**
287
   * @brief Returns the favicon.ico file.
288
   * @details The favicon is a part of the admin interface, but the handler for it is special as CouchDB tries to make
289
   * sure that the favicon is cached for one year. Returns a string that represents the icon.
290
   * @retval string
291
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#favicon-ico
292
   */
293
  public function getFavicon() {
294
    $response = $this->send(new Request(Request::GET_METHOD, "/favicon.ico"));
295
296
    if ($response->getHeaderFieldValue(Request::CONTENT_TYPE_HF) == "image/x-icon")
297
      return $response->getBody();
298
    else
299
      throw new \InvalidArgumentException("Content-Type must be image/x-icon.");
300
  }
301
302
303
  /**
304
   * @brief Returns server statistics.
305
   * @retval array An associative array
306
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#stats
307
   */
308
  public function getStats() {
309
    return $this->send(new Request(Request::GET_METHOD, "/_stats"))->getBodyAsArray();
310
  }
311
312
313
  /**
314
   * @brief Returns a list of all databases on this server.
315
   * @retval array of string
316
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#all-dbs
317
   */
318
  public function getAllDbs() {
319
    return $this->send(new Request(Request::GET_METHOD, "/_all_dbs"))->getBodyAsArray();
320
  }
321
322
323
  /**
324
   * @brief Returns a list of all database events in the CouchDB instance.
325
   * @param[in] DbUpdatesFeedOpts $opts Additional options.
326
   * @retval Response
327
   * @attention Requires admin privileges.
328
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#db-updates
329
   */
330
  public function getDbUpdates(Opt\DbUpdatesFeedOpts $opts = NULL) {
331
    $request = new Request(Request::GET_METHOD, "/_db_updates");
332
333
    if (isset($opts))
334
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
335
336
    return $this->send($request)->getBodyAsArray();
337
  }
338
339
340
  /**
341
   * @brief Returns a list of running tasks.
342
   * @attention Requires admin privileges.
343
   * @retval array An associative array
344
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#active-tasks
345
   */
346
  public function getActiveTasks() {
347
    return $this->send(new Request(Request::GET_METHOD, "/_active_tasks"))->getBodyAsArray();
348
  }
349
350
351
  /**
352
   * @brief Returns the tail of the server's log file.
353
   * @attention Requires admin privileges.
354
   * @param[in] integer $bytes How many bytes to return from the end of the log file.
355
   * @retval string
356
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#log
357
   */
358
  public function getLogTail($bytes = 1000) {
359
    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...
360
      $request = new Request(Request::GET_METHOD, "/_log");
361
      $request->setQueryParam("bytes", $bytes);
362
      return $this->send($request)->getBody();
363
    }
364
    else
365
      throw new \InvalidArgumentException("\$bytes must be a positive integer.");
366
  }
367
368
369
  /**
370
   * @brief Returns a list of generated UUIDs.
371
   * @param[in] integer $count Requested UUIDs number.
372
   * @retval string|array If `$count == 1` (default) returns a string else returns an array of strings.
373
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#uuids
374
   */
375
  public function getUuids($count = 1) {
376
    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...
377
      $request = new Request(Request::GET_METHOD, "/_uuids");
378
      $request->setQueryParam("count", $count);
379
380
      $response = $this->send($request);
381
382
      if ($count == 1) // We don't need to use === operator because, just above, we made a type checking.
383
        return $response->getBodyAsArray()['uuids'][0];
384
      else
385
        return $response->getBodyAsArray()['uuids'];
386
    }
387
    else
388
      throw new \InvalidArgumentException("\$count must be a positive integer.");
389
  }
390
391
  //!@}
392
393
394
  /** @name Server Configuration Methods */
395
  //!@{
396
397
  /**
398
   * @brief Returns the entire server configuration or a single section or a single configuration value of a section.
399
   * @param[in] string $section Requested section.
400
   * @param[in] string $key Requested key.
401
   * @retval string|array An array with the configuration keys or a simple string in case of a single key.
402
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#get--_config
403
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#get--_config-section
404
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#config-section-key
405
   */
406
  public function getConfig($section = "", $key = "") {
407
    $path = "/_config";
408
409
    if (!empty($section)) {
410
      $path .= "/".$section;
411
412
      if (!empty($key))
413
        $path .= "/".$key;
414
    }
415
416
    return $this->send(new Request(Request::GET_METHOD, $path))->getBodyAsArray();
417
  }
418
419
420
  /**
421
   * @brief Sets a single configuration value in a given section to server configuration.
422
   * @param[in] string $section The configuration section.
423
   * @param[in] string $key The key.
424
   * @param[in] string $value The value for the key.
425
   * @see http://docs.couchdb.org/en/latest/api/server/configuration.html#put--_config-section-key
426
   */
427
  public function setConfigKey($section, $key, $value) {
428
    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...
429
      throw new \InvalidArgumentException("\$section must be a not empty string.");
430
431
    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...
432
      throw new \InvalidArgumentException("\$key must be a not empty string.");
433
434
    if (is_null($value))
435
      throw new \InvalidArgumentException("\$value cannot be null.");
436
437
    $request = new Request(Request::PUT_METHOD, "/_config/".$section."/".$key);
438
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
439
    $request->setBody(json_encode(utf8_encode($value)));
440
    $this->send($request);
441
  }
442
443
444
  /**
445
   * @brief Deletes a single configuration value from a given section in server configuration.
446
   * @param[in] string $section The configuration section.
447
   * @param[in] string $key The key.
448
   * @see http://docs.couchdb.org/en/latest/api/configuration.html#delete-config-section-key
449
   */
450
  public function deleteConfigKey($section, $key) {
451
    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...
452
      throw new \InvalidArgumentException("\$section must be a not empty string.");
453
454
    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...
455
      throw new \InvalidArgumentException("\$key must be a not empty string.");
456
457
    $this->send(new Request(Request::DELETE_METHOD, "/_config/".$section."/".$key));
458
  }
459
460
  //!@}
461
462
463
  /** @name Cookie Authentication */
464
  //!@{
465
466
  /**
467
   * @brief Returns cookie based login user information.
468
   * @retval Response
469
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#get--_session
470
   */
471
  public function getSession() {
472
    return $this->send(new Request(Request::GET_METHOD, "/_session"));
473
  }
474
475
476
  /**
477
   * @brief Makes cookie based user login.
478
   * @retval Response
479
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#post--_session
480
   */
481
  public function setSession($userName, $password) {
482
    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...
483
      throw new \InvalidArgumentException("\$userName must be a not empty string.");
484
485
    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...
486
      throw new \InvalidArgumentException("\$password must be a not empty string.");
487
488
    $request = new Request(Request::POST_METHOD, "/_session");
489
490
    $request->setHeaderField(Request::X_COUCHDB_WWW_AUTHENTICATE_HF, "Cookie");
491
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/x-www-form-urlencoded");
492
493
    $request->setQueryParam("name", $userName);
494
    $request->setQueryParam("password", $password);
495
496
    return $this->send($request);
497
  }
498
499
500
  /**
501
   * @brief Makes user logout.
502
   * @retval Response
503
   * @see http://docs.couchdb.org/en/latest/api/server/authn.html#delete--_session
504
   */
505
  public function deleteSession() {
506
    return $this->send(new Request(Request::DELETE_METHOD, "/_session"));
507
  }
508
509
  //!@}
510
511
512
  /** @name OAuth Authentication */
513
  //! @see http://docs.couchdb.org/en/latest/api/server/authn.html#oauth-authentication
514
  //!@{
515
516
  /**
517
   * @brief
518
   * @todo To be implemented and documented.
519
   */
520
  public function getAccessToken() {
521
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/access_token"));
522
  }
523
524
525
  /**
526
   * @brief
527
   * @todo To be implemented and documented.
528
   */
529
  public function getAuthorize() {
530
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/authorize"));
531
  }
532
533
534
  /**
535
   * @brief
536
   * @todo To be implemented and documented.
537
   */
538
  public function setAuthorize() {
539
    return $this->send(new Request(Request::POST_METHOD, "/_oauth/authorize"));
540
  }
541
542
543
  /**
544
   * @brief
545
   * @todo To be implemented and documented.
546
   */
547
  public function requestToken() {
548
    return $this->send(new Request(Request::GET_METHOD, "/_oauth/request_token"));
549
  }
550
551
  //!@}
552
553
554
  /**
555
   * @brief Creates a new database and selects it.
556
   * @param[in] string $name The database name. A database must be named with all lowercase letters (a-z),
557
   * digits (0-9), or any of the _$()+-/ characters and must end with a slash in the URL. The name has to start with a
558
   * lowercase letter (a-z).
559
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#put--db
560
   */
561
  public function createDb($name) {
562
    # \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...
563
    #
564
    # Assert position at the beginning of the string «\A»
565
    # Match a single character in the range between “a” and “z” «[a-z]»
566
    # Match a single character present in the list below «[a-z\d_$()+-/]++»
567
    #    Between one and unlimited times, as many times as possible, without giving back (possessive) «++»
568
    #    A character in the range between “a” and “z” «a-z»
569
    #    A single digit 0..9 «\d»
570
    #    One of the characters “_$()” «_$()»
571
    #    A character in the range between “+” and “/” «+-/»
572
    # Assert position at the very end of the string «\z»
573
    if (preg_match('%\A[a-z][a-z\d_$()+-/]++\z%', $name) === FALSE)
574
      throw new \InvalidArgumentException("Invalid database name.");
575
576
    $this->send(new Request(Request::PUT_METHOD, "/".rawurlencode($this->prefix.$name)."/"));
577
  }
578
579
580
  /**
581
   * @brief Deletes an existing database.
582
   * @param[in] string $name The database name.
583
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#delete--db
584
   */
585
  public function deleteDb($name) {
586
    $this->send(new Request(Request::DELETE_METHOD, "/".rawurlencode($this->prefix.$name)));
587
  }
588
589
590
  /**
591
   * @brief Returns information about the selected database.
592
   * @param[in] string $name The database name.
593
   * @retval Info::DbInfo
594
   * @see http://docs.couchdb.org/en/latest/api/database/common.html#get--db
595
   */
596
  public function getDbInfo($name) {
597
    return new Info\Dbinfo($this->send(new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$name)."/"))->getBodyAsArray());
598
  }
599
600
601
  /**
602
   * @brief Obtains a list of the changes made to the database. This can be used to monitor for update and modifications
603
   * to the database for post processing or synchronization.
604
   * @param[in] string $name The database name.
605
   * @param[in] ChangesFeedOpts $opts Additional options.
606
   * @retval Response
607
   * @see http://docs.couchdb.org/en/latest/api/database/changes.html
608
   */
609
  public function getDbChanges($name, Opt\ChangesFeedOpts $opts = NULL) {
610
    $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$name)."/_changes");
611
612
    if (isset($opts))
613
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
614
615
    return $this->send($request);
616
  }
617
618
619
  /**
620
   * @brief Starts a compaction for the current selected database.
621
   * @details Writes a new version of the database file, removing any unused sections from the new version during write.
622
   * Because a new file is temporary created for this purpose, you will need twice the current storage space of the
623
   * specified database in order for the compaction routine to complete.\n
624
   * Removes old revisions of documents from the database, up to the per-database limit specified by the `_revs_limit`
625
   * database setting.\n
626
   * Compaction can only be requested on an individual database; you cannot compact all the databases for a CouchDB
627
   * instance.\n
628
   * The compaction process runs as a background process. You can determine if the compaction process is operating on a
629
   * database by obtaining the database meta information, the `compact_running` value of the returned database
630
   * structure will be set to true. You can also obtain a list of running processes to determine whether compaction is
631
   * currently running, using getActiveTasks().
632
   * @param[in] string $name The database name.
633
   * @attention Requires admin privileges.
634
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html
635
   */
636 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...
637
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$name)."/_compact");
638
639
    // A POST method requires Content-Type header.
640
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
641
642
    $this->send($request);
643
  }
644
645
646
  /**
647
   * @brief Compacts the specified view.
648
   * @details If you have very large views or are tight on space, you might consider compaction as well. To run compact
649
   * for a particular view on a particular database, use this method.
650
   * @param[in] string $dbName The database name.
651
   * @param[in] string $designDocName Name of the design document where is stored the view.
652
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-compact-design-doc
653
   */
654
  public function compactView($dbName, $designDocName) {
655
    $path = "/".rawurlencode($this->prefix.$dbName)."/_compact/".$designDocName;
656
657
    $request = new Request(Request::POST_METHOD, $path);
658
659
    // A POST method requires Content-Type header.
660
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
661
662
    $this->send($request);
663
  }
664
665
666
  /**
667
   * @brief Removes all outdated view indexes.
668
   * @details Old views files remain on disk until you explicitly run cleanup.
669
   * @param[in] string $dbName The database name.
670
   * @attention Requires admin privileges.
671
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-view-cleanup
672
   */
673 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...
674
    $request =  new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_view_cleanup");
675
676
    // A POST method requires Content-Type header.
677
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
678
679
    $this->send($request);
680
  }
681
682
683
  /**
684
   * @brief Makes sure all uncommited database changes are written and synchronized to the disk.
685
   * @details Default CouchDB configuration use delayed commit to improve performances. So CouchDB allows operations to
686
   * be run against the disk without an explicit fsync after each operation. Synchronization takes time (the disk may
687
   * have to seek, on some platforms the hard disk cache buffer is flushed, etc.), so requiring an fsync for each update
688
   * deeply limits CouchDB's performance for non-bulk writers.\n
689
   * Delayed commit should be left set to true in the configuration settings. Anyway, you can still tell CouchDB to make
690
   * an fsync, calling the this method.
691
   * @param[in] string $dbName The database name.
692
   * @retval string The timestamp of the last time the database file was opened.
693
   * @see http://docs.couchdb.org/en/latest/api/database/compact.html#db-ensure-full-commit
694
   */
695 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...
696
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_ensure_full_commit");
697
698
    // A POST method requires Content-Type header.
699
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
700
701
    return $this->send($request)->getBodyAsArray()["instance_start_time"];
702
  }
703
704
  //!@}
705
706
707
  /**
708
   * @name Security Methods
709
   * @details The security object consists of two compulsory elements, admins and members, which are used to specify
710
   * the list of users and/or roles that have admin and members rights to the database respectively:
711
   * - members: they can read all types of documents from the DB, and they can write (and edit) documents to the
712
   * database except for design documents.
713
   * - admins: they have all the privileges of members plus the privileges: write (and edit) design documents, add/remove
714
   * database admins and members, set the database revisions limit and execute temporary views against the database.
715
   * They can not create a database nor delete a database.
716
   *
717
   * Both members and admins objects are contains two array-typed fields:
718
   * - users: List of CouchDB user names
719
   * - roles: List of users roles
720
   *
721
   * Any other additional fields in the security object are optional. The entire security object is made available to
722
   * validation and other internal functions so that the database can control and limit functionality.
723
   * If both the names and roles fields of either the admins or members properties are empty arrays, it means the
724
   * database has no admins or members.\n
725
   * Having no admins, only server admins (with the reserved _admin role) are able to update design document and make
726
   * other admin level changes.\n
727
   * Having no members, any user can write regular documents (any non-design document) and read documents from the database.
728
   * If there are any member names or roles defined for a database, then only authenticated users having a matching name
729
   * or role are allowed to read documents from the database.
730
   */
731
  //!@{
732
733
  /**
734
   * @brief Returns the special security object for the database.
735
   * @param[in] string $dbName The database name.
736
   * @retval string A JSON object.
737
   * @see http://docs.couchdb.org/en/latest/api/database/security.html#get--db-_security
738
   */
739
  public function getSecurityObj($dbName) {
740
    return $this->send(new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_security"));
741
  }
742
743
744
  /**
745
   * @brief Sets the special security object for the database.
746
   * @param[in] string $dbName The database name.
747
   * @see http://docs.couchdb.org/en/latest/api/database/security.html#put--db-_security
748
   */
749
  public function setSecurityObj($dbName) {
750
    return $this->send(new Request(Request::PUT_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_security"));
751
  }
752
753
  //!@}
754
755
756
  /**
757
   * @name Database Replication Methods
758
   * @details The replication is an incremental one way process involving two databases (a source and a destination).
759
   * The aim of the replication is that at the end of the process, all active documents on the source database are also
760
   * in the destination database and all documents that were deleted in the source databases are also deleted (if
761
   * exists) on the destination database.\n
762
   * The replication process only copies the last revision of a document, so all previous revisions that were only on
763
   * the source database are not copied to the destination database.\n
764
   * Changes on the master will not automatically replicate to the slaves. To make replication continuous, you must set
765
   * `$continuous = true`. At this time, CouchDB does not remember continuous replications over a server restart.
766
   * Specifying a local source database and a remote target database is called push replication and a remote source and
767
   * local target is called pull replication. As of CouchDB 0.9, pull replication is a lot more efficient and resistant
768
   * to errors, and it is suggested that you use pull replication in most cases, especially if your documents are large
769
   * or you have large attachments.
770
   */
771
  //!@{
772
773
  /**
774
   * @brief Starts replication.
775
   @code
776
     startReplication("sourcedbname", "http://example.org/targetdbname", TRUE, TRUE);
777
   @endcode
778
   * @param[in] string $sourceDbUrl Source database URL.
779
   * @param[in] string $targetDbUrl Destination database URL.
780
   * @param[in] string $proxy (optional) Specify the optional proxy used to perform the replication.
781
   * @param[in] bool $createTargetDb (optional) The target database has to exist and is not implicitly created. You
782
   * can force the creation setting `$createTargetDb = true`.
783
   * @param[in] bool $continuous (optional) When `true` CouchDB will not stop after replicating all missing documents
784
   * from the source to the target.\n
785
   * At the time of writing, CouchDB doesn't remember continuous replications over a server restart. For the time being,
786
   * you are required to trigger them again when you restart CouchDB. In the future, CouchDB will allow you to define
787
   * permanent continuous replications that survive a server restart without you having to do anything.
788
   * @param[in] string|array $filter (optional) Name of a filter function that can choose which revisions get replicated.
789
   * You can also provide an array of document identifiers; if given, only these documents will be replicated.
790
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
791
   * docs, etc.
792
   * @retval Response
793
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#post--_replicate
794
   */
795
  public function startReplication($sourceDbUrl, $targetDbUrl, $proxy = NULL, $createTargetDb = TRUE,
796
                                     $continuous = FALSE, $filter = NULL, Opt\ViewQueryOpts $opts = NULL) {
797
    // Sets source and target databases.
798
    if (is_string($sourceDbUrl) && !empty($sourceDbUrl) &&
799
      is_string($targetDbUrl) && !empty($targetDbUrl)) {
800
      $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...
801
      $body["target"] = $targetDbUrl;
802
    }
803
    else
804
      throw new \InvalidArgumentException("\$source_db_url and \$target_db_url must be non-empty strings.");
805
806
    if (!is_bool($continuous))
807
      throw new \InvalidArgumentException("\$continuous must be a bool.");
808
    elseif ($continuous)
809
      $body["continuous"] = $continuous;
810
811
    // Uses the specified proxy if any set.
812
    if (isset($proxy))
813
      $body["proxy"] = $proxy;
814
815
    // create_target option
816
    if (!is_bool($createTargetDb))
817
      throw new \InvalidArgumentException("\$createTargetDb must be a bool.");
818
    elseif ($createTargetDb)
819
      $body["create_target"] = $createTargetDb;
820
821
    if (!empty($filter)) {
822
      if (is_string($filter)) // filter option
823
        $body["filter"] = $filter;
824
      elseif (is_array($filter)) // doc_ids option
825
        $body["doc_ids"] = array_values($filter);
826
      else
827
        throw new \InvalidArgumentException("\$filter must be a string or an array.");
828
    }
829
830
    // queryParams option
831
    if (!is_null($opts)) {
832
      if ($opts instanceof Opt\ViewQueryOpts)
833
        $body["query_params"] = get_object_vars($opts);
834
      else
835
        throw new \InvalidArgumentException("\$queryParams must be an instance of ViewQueryOpts class.");
836
    }
837
838
    $request = new Request(Request::POST_METHOD, "/_replicate");
839
    $request->setBody($body);
840
841
    return $this->send($request);
842
  }
843
844
845
  /**
846
   * @brief Cancels replication.
847
   * @param[in] string $replicationId The replication ID.
848
   * @retval Response
849
   * @see http://docs.couchdb.org/en/latest/api/server/common.html#canceling-continuous-replication
850
   */
851
  public function stopReplication($replicationId) {
852
    if (is_null($replicationId))
853
      throw new \InvalidArgumentException("You must provide a replication id.");
854
855
    $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...
856
    $body["cancel"] = TRUE;
857
858
    $request = new Request(Request::POST_METHOD, "/_replicate");
859
    $request->setBody($body);
860
861
    return $this->send($request);
862
  }
863
864
865
  /**
866
   * @brief
867
   * @details
868
   * @see http://wiki.apache.org/couchdb/Replication#Replicator_database
869
   * @see http://docs.couchbase.org/couchdb-release-1.1/index.html#couchb-release-1.1-replicatordb
870
   * @see https://gist.github.com/832610
871
   * @todo To be implemented and documented.
872
   */
873
  public function getReplicator() {
874
    throw new \BadMethodCallException("The method `getReplicator()` is not available.");
875
  }
876
877
  //!@}
878
879
880
  /** @name Query Documents Methods
881
   * @details Queries are the primary mechanism for retrieving a result set from a view. The result of a query is an
882
   * instance of `QueryResult`, a class that implements the [IteratorAggregate](http://php.net/manual/en/class.iteratoraggregate.php),
883
   * [Countable](http://php.net/manual/en/class.countable.php) and [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php)
884
   * interfaces, so you can use the result set as an array.
885
   */
886
  //!@{
887
888
  /**
889
   * @brief Returns a built-in view of all documents in this database. If keys are specified returns only certain rows.
890
   * @param[in] string $dbName The database name.
891
   * @param[in] array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
892
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
893
   * multi-document-fetch feature.
894
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
895
   * docs, etc.
896
   * @param[in] ChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
897
   * @retval Result::QueryResult The result of the query.
898
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#get--db-_all_docs
899
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_all_docs
900
   */
901
  public function queryAllDocs($dbName, array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, Hook\IChunkHook $chunkHook = NULL) {
902
    if (is_null($keys))
903
      $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_all_docs");
904
    else {
905
      $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_all_docs");
906
      $request->setBody(json_encode(['keys' => $keys]));
907
    }
908
909
    if (isset($opts))
910
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
911
912
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
913
914
    return new Result\QueryResult($result);
915
  }
916
917
918
  /**
919
   * @brief Executes the given view and returns the result.
920
   * @param[in] string $dbName The database name.
921
   * @param[in] string $designDocName The design document's name.
922
   * @param[in] string $viewName The view's name.
923
   * @param[in] array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
924
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
925
   * multi-document-fetch feature.
926
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
927
   * docs, etc.
928
   * @param[in] IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
929
   * @retval Result::QueryResult The result of the query.
930
   * @attention Multiple keys request to a reduce function only supports `group=true` (identical to `group-level=exact`,
931
   * but it doesn't support `group_level` > 0.
932
   * CouchDB raises "Multi-key fetchs for reduce view must include `group=true`" error, in case you use `group_level`.
933
   * @see http://docs.couchdb.org/en/latest/api/ddoc/views.html#get--db-_design-ddoc-_view-view
934
   * @see http://docs.couchdb.org/en/latest/api/ddoc/views.html#post--db-_design-ddoc-_view-view
935
   */
936
  public function queryView($dbName, $designDocName, $viewName, array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, Hook\IChunkHook $chunkHook = NULL) {
937
    $this->validateAndEncodeDocId($designDocName);
938
939
    if (empty($viewName))
940
      throw new \InvalidArgumentException("You must provide a valid \$viewName.");
941
942
    if (empty($keys))
943
      $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_design/".$designDocName."/_view/".$viewName);
944
    else {
945
      $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_design/".$designDocName."/_view/".$viewName);
946
      $request->setBody(json_encode(['keys' => $keys]));
947
    }
948
949 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...
950
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
951
      $includeMissingKeys = $opts->issetIncludeMissingKeys();
952
    }
953
    else
954
      $includeMissingKeys = FALSE;
955
956
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
957
958
    if ($includeMissingKeys)
959
      $this->addMissingRows($keys, $result['rows']);
960
961
    return new Result\QueryResult($result);
962
  }
963
964
965
  /**
966
   * @brief Executes the given view, both map and reduce functions, for all documents and returns the result.
967
   * @details Map and Reduce functions are provided by the programmer.
968
   * @attention Requires admin privileges.
969
   * @param[in] string $dbName The database name.
970
   * @param[in] string $mapFn The map function.
971
   * @param[in] string $reduceFn The reduce function.
972
   * @param[in] array $keys (optional) Used to retrieve just the view rows matching that set of keys. Rows are returned
973
   * in the order of the specified keys. Combining this feature with Opt\ViewQueryOpts.includeDocs() results in the so-called
974
   * multi-document-fetch feature.
975
   * @param[in] ViewQueryOpts $opts (optional) Query options to get additional information, grouping results, include
976
   * docs, etc.
977
   * @param[in] string $language The language used to implement the map and reduce functions.
978
   * @param[in] IChunkHook $chunkHook (optional) A class instance that implements the IChunkHook interface.
979
   * @retval Result::QueryResult The result of the query.
980
   * @see http://docs.couchdb.org/en/latest/api/database/temp-views.html#post--db-_temp_view
981
   */
982
  public function queryTempView($dbName, $mapFn, $reduceFn = "", array $keys = NULL, Opt\ViewQueryOpts $opts = NULL, $language = 'php', Hook\IChunkHook $chunkHook = NULL) {
983
    $handler = new Handler\ViewHandler('temp');
984
    $handler->language = $language;
985
    $handler->mapFn = $mapFn;
986
    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...
987
      $handler->reduceFn = $reduceFn;
988
989
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_temp_view");
990
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
991
992
    $array = $handler->asArray();
993
994
    if (!empty($keys))
995
      $array['keys'] = $keys;
996
997
    $request->setBody(json_encode($array));
998
999 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...
1000
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
1001
      $includeMissingKeys = $opts->issetIncludeMissingKeys();
1002
    }
1003
    else
1004
      $includeMissingKeys = FALSE;
1005
1006
    $result = $this->send($request, $chunkHook)->getBodyAsArray();
1007
1008
    if ($includeMissingKeys)
1009
      $this->addMissingRows($keys, $result['rows']);
1010
1011
    return new Result\QueryResult($result);
1012
  }
1013
1014
  //!@}
1015
1016
1017
  /** @name Revisions Management Methods */
1018
  //!@{
1019
1020
  /**
1021
   * @brief Given a list of document revisions, returns the document revisions that do not exist in the database.
1022
   * @param[in] string $dbName The database name.
1023
   * @retval Response
1024
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#db-missing-revs
1025
   */
1026
  public function getMissingRevs($dbName) {
1027
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_missing_revs");
1028
1029
    return $this->send($request);
1030
  }
1031
1032
1033
  /**
1034
   * @brief Given a list of document revisions, returns differences between the given revisions and ones that are in
1035
   * the database.
1036
   * @param[in] string $dbName The database name.
1037
   * @retval Response
1038
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#db-revs-diff
1039
   */
1040
  public function getRevsDiff($dbName) {
1041
    $request = new Request(Request::POST_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_missing_revs");
1042
1043
    return $this->send($request);
1044
  }
1045
1046
1047
  /**
1048
   * @brief Gets the limit of historical revisions to store for a single document in the database.
1049
   * @param[in] string $dbName The database name.
1050
   * @retval integer The maximum number of document revisions that will be tracked by CouchDB.
1051
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#get--db-_revs_limit
1052
   */
1053
  public function getRevsLimit($dbName) {
1054
    $request = new Request(Request::GET_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_revs_limit");
1055
1056
    return (integer)$this->send($request)->getBody();
1057
  }
1058
1059
1060
  /**
1061
   * @brief Sets the limit of historical revisions for a single document in the database.
1062
   * @param[in] string $dbName The database name.
1063
   * @param[in] integer $revsLimit (optional) Maximum number historical revisions for a single document in the database.
1064
   * Must be a positive integer.
1065
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#put--db-_revs_limit
1066
   */
1067
  public function setRevsLimit($dbName, $revsLimit = self::REVS_LIMIT) {
1068
    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...
1069
      throw new \InvalidArgumentException("\$revsLimit must be a positive integer.");
1070
1071
    $request = new Request(Request::PUT_METHOD, "/".rawurlencode($this->prefix.$dbName)."/_revs_limit");
1072
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1073
    $request->setBody(json_encode($revsLimit));
1074
1075
    $this->send($request);
1076
  }
1077
1078
  //!@}
1079
1080
1081
  /** @name Documents Management Methods */
1082
  //!@{
1083
1084
  /**
1085
   * @brief Returns the document's entity tag, that can be used for caching or optimistic concurrency control purposes.
1086
   * The ETag Header is simply the document's revision in quotes.
1087
   * @details This function is not available for special documents. To get information about a design document, use
1088
   * the special function getDesignDocInfo().
1089
   * @param[in] string $dbName The database name.
1090
   * @param[in] string $docId The document's identifier.
1091
   * @retval string The document's revision.
1092
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#db-doc
1093
   * @bug CouchDB ETag is
1094
   */
1095 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...
1096
    $this->validateAndEncodeDocId($docId);
1097
1098
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$docId;
1099
1100
    $request = new Request(Request::HEAD_METHOD, $path);
1101
1102
    // CouchDB ETag is included between quotation marks.
1103
    return trim($this->send($request)->getHeaderFieldValue(Response::ETAG_HF), '"');
1104
  }
1105
1106
1107
  /**
1108
   * @brief Returns the latest revision of the document.
1109
   * @details Since CouchDB uses different paths to store special documents, you must provide the document type for
1110
   * design and local documents.
1111
   * @param[in] string $dbName The database name.
1112
   * @param[in] string $docId The document's identifier.
1113
   * @param[in] string $path The document's path.
1114
   * @param[in] string $rev (optional) The document's revision.
1115
   * @param[in] DocOpts $opts Query options to get additional document information, like conflicts, attachments, etc.
1116
   * @retval object|Response An instance of Doc, LocalDoc, DesignDoc or any subclass of Doc.
1117
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
1118
   */
1119
  public function getDoc($dbName, $path, $docId, $rev = NULL, Opt\DocOpts $opts = NULL) {
1120
    $this->validateDocPath($path);
1121
    $this->validateAndEncodeDocId($docId);
1122
1123
    $requestPath = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId;
1124
1125
    $request = new Request(Request::GET_METHOD, $requestPath);
1126
1127
    // Retrieves the specific revision of the document.
1128
    if (!empty($rev))
1129
      $request->setQueryParam("rev", (string)$rev);
1130
1131
    // If there are any options, add them to the request.
1132
    if (isset($opts)) {
1133
      $request->setMultipleQueryParamsAtOnce($opts->asArray());
1134
      $ignoreClass = $opts->issetIgnoreClass();
1135
    }
1136
    else
1137
      $ignoreClass = FALSE;
1138
1139
    $response = $this->send($request);
1140
    $body = $response->getBodyAsArray();
1141
1142
    // We use `class` metadata to store an instance of a specialized document class. We can have Article and Book classes,
1143
    // both derived from Doc, with special properties and methods. Instead to convert them, we store the class name in a
1144
    // special attribute called 'class' within the others metadata. So, once we retrieve the document, the client creates
1145
    // an instance of the class we provided when we saved the document; we never need to convert it.
1146
    if (!$ignoreClass && isset($body['class'])) { // Special document class inherited from Doc or LocalDoc.
1147
      $class = "\\".$body['class'];
1148
      $doc = new $class;
1149
    }
1150
    elseif ($path == self::DESIGN_DOC_PATH)
1151
      $doc = new Doc\DesignDoc;
1152
    else
1153
      $doc = NULL;
1154
1155
    if (is_object($doc)) {
1156
      $doc->assignArray($body);
1157
      return $doc;
1158
    }
1159
    else
1160
      return $response;
1161
  }
1162
1163
1164
  /**
1165
   * @brief Inserts or updates a document into the selected database.
1166
   * @details Whether the `$doc` has an id we use a different HTTP method. Using POST CouchDB generates an id for the doc,
1167
   * using PUT instead we need to specify one. We can still use the function getUuids() to ask CouchDB for some ids.
1168
   * This is an internal detail. You have only to know that CouchDB can generate the document id for you.
1169
   * @param[in] string $dbName The database name.
1170
   * @param[in] Doc $doc The document you want insert or update.
1171
   * @param[in] bool $batchMode (optional) You can write documents to the database at a higher rate by using the batch
1172
   * option. This collects document writes together in memory (on a user-by-user basis) before they are committed to
1173
   * disk. This increases the risk of the documents not being stored in the event of a failure, since the documents are
1174
   * not written to disk immediately.
1175
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#put--db-docid
1176
   */
1177
  public function saveDoc($dbName, Doc\IDoc $doc, $batchMode = FALSE) {
1178
    // Whether the document has an id we use a different HTTP method. Using POST CouchDB generates an id for the doc
1179
    // using PUT we need to specify one. We can still use the function getUuids() to ask CouchDB for some ids.
1180
    if (!$doc->issetId())
1181
      $doc->setId(Generator\UUID::generate(Generator\UUID::UUID_RANDOM, Generator\UUID::FMT_STRING));
1182
1183
    $this->setDocInfo($doc);
1184
1185
    // We never use the POST method.
1186
    $method = Request::PUT_METHOD;
1187
1188
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$doc->getPath().$doc->getId();
1189
1190
    $request = new Request($method, $path);
1191
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1192
    $request->setBody($doc->asJson());
1193
1194
    // Enables batch mode.
1195
    if ($batchMode)
1196
      $request->setQueryParam("batch", "ok");
1197
1198
    $this->send($request);
1199
  }
1200
1201
1202
  /**
1203
   * @brief Deletes the specified document.
1204
   * @details To delete a document you must provide the document identifier and the revision number.
1205
   * @param[in] string $dbName The database name.
1206
   * @param[in] string $docId The document's identifier you want delete.
1207
   * @param[in] string $rev The document's revision number you want delete.
1208
   * @param[in] string $path The document path.
1209
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#delete--db-docid
1210
   */
1211
  public function deleteDoc($dbName, $path, $docId, $rev) {
1212
    $this->validateDocPath($path);
1213
    $this->validateAndEncodeDocId($docId);
1214
1215
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId;
1216
1217
    $request = new Request(Request::DELETE_METHOD, $path);
1218
    $request->setQueryParam("rev", (string)$rev);
1219
1220
    // We could use another technique to send the revision number. Here just for documentation.
1221
    // $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...
1222
1223
    $this->send($request);
1224
  }
1225
1226
1227
  /**
1228
   * @brief Makes a duplicate of the specified document. If you want to overwrite an existing document, you need to
1229
   * specify the target document's revision with a `$rev` parameter.
1230
   * @details If you want copy a special document you must specify his type.
1231
   * @param[in] string $dbName The database name.
1232
   * @param[in] string $sourceDocId The source document id.
1233
   * @param[in] string $targetDocId The destination document id.
1234
   * @param[in] string $rev Needed when you want override an existent document.
1235
   * @param[in] string $path The document path.
1236
   * @see http://docs.couchdb.org/en/latest/api/document/common.html#copy--db-docid
1237
   */
1238
  public function copyDoc($dbName, $path, $sourceDocId, $targetDocId, $rev = NULL) {
1239
    $this->validateDocPath($path);
1240
1241
    $this->validateAndEncodeDocId($sourceDocId);
1242
    $this->validateAndEncodeDocId($targetDocId);
1243
1244
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$sourceDocId;
1245
1246
    // This request uses the special method COPY.
1247
    $request = new Request(Request::COPY_METHOD, $path);
1248
1249
    if (empty($rev))
1250
      $request->setHeaderField(Request::DESTINATION_HF, $targetDocId);
1251
    else
1252
      $request->setHeaderField(Request::DESTINATION_HF, $targetDocId."?rev=".(string)$rev);
1253
1254
    $this->send($request);
1255
  }
1256
1257
1258
  /**
1259
   * @brief The purge operation removes the references to the deleted documents from the database.
1260
   * @details A database purge permanently removes the references to deleted documents from the database. Deleting a
1261
   * document within CouchDB does not actually remove the document from the database, instead, the document is marked
1262
   * as deleted (and a new revision is created). This is to ensure that deleted documents are replicated to other
1263
   * databases as having been deleted. This also means that you can check the status of a document and identify that
1264
   * the document has been deleted.\n
1265
   * The purging of old documents is not replicated to other databases. If you are replicating between databases and
1266
   * have deleted a large number of documents you should run purge on each database.\n
1267
   * Purging documents does not remove the space used by them on disk. To reclaim disk space, you should run compactDb().\n
1268
   * @param[in] string $dbName The database name.
1269
   * @param[in] array $refs An array of references used to identify documents and revisions to delete. The array must
1270
   * contains instances of the DocRef class.
1271
   * @retval Response
1272
   * @see http://docs.couchdb.org/en/latest/api/database/misc.html#post--db-_purge
1273
   * @see http://wiki.apache.org/couchdb/Purge_Documents
1274
   */
1275
  public function purgeDocs($dbName, array $refs) {
1276
    $path = "/".rawurlencode($this->prefix.$dbName)."/_purge";
1277
1278
    $request = new Request(Request::POST_METHOD, $path);
1279
1280
    $purge = [];
1281
    foreach ($refs as $ref)
1282
      $purge[] = $ref->asArray();
1283
1284
    $request->setBody(json_encode($purge));
1285
1286
    return $this->send($request);
1287
  }
1288
1289
1290
  /**
1291
   * @brief Inserts, updates and deletes documents in a bulk.
1292
   * @details Documents that are updated or deleted must contain the `rev` number. To delete a document, you should
1293
   * call delete() method on your document. When creating new documents the document ID is optional. For updating existing
1294
   * documents, you must provide the document ID and revision.
1295
   * @param[in] string $dbName The database name.
1296
   * @param[in] array $docs An array of documents you want insert, delete or update.
1297
   * @param[in] bool $fullCommit (optional) Makes sure all uncommited database changes are written and synchronized
1298
   * to the disk immediately.
1299
   * @param[in] bool $allOrNothing (optional) In all-or-nothing mode, either all documents are written to the database,
1300
   * or no documents are written to the database, in the event of a system failure during commit.\n
1301
   * You can ask CouchDB to check that all the documents pass your validation functions. If even one fails, none of the
1302
   * documents are written. If all documents pass validation, then all documents will be updated, even if that introduces
1303
   * a conflict for some or all of the documents.
1304
   * @param[in] bool $newEdits (optional) When `false` CouchDB pushes existing revisions instead of creating
1305
   * new ones. The response will not include entries for any of the successful revisions (since their rev IDs are
1306
   * already known to the sender), only for the ones that had errors. Also, the conflict error will never appear,
1307
   * since in this mode conflicts are allowed.
1308
   * @retval Response
1309
   * @see http://docs.couchdb.org/en/latest/api/database/bulk-api.html#db-bulk-docs
1310
   * @see http://docs.couchdb.org/en/latest/json-structure.html#bulk-documents
1311
   * @see http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
1312
   */
1313
  public function performBulkOperations($dbName, array $docs, $fullCommit = FALSE, $allOrNothing = FALSE, $newEdits = TRUE) {
1314
    if (count($docs) == 0)
1315
      throw new \InvalidArgumentException("The \$docs array cannot be empty.");
1316
    else
1317
      $operations = [];
1318
1319
    $path = "/".rawurlencode($this->prefix.$dbName)."/_bulk_docs";
1320
1321
    $request = new Request(Request::POST_METHOD, $path);
1322
    $request->setHeaderField(Request::CONTENT_TYPE_HF, "application/json");
1323
1324
    if ($fullCommit)
1325
      $request->setHeaderField(Request::X_COUCHDB_FULL_COMMIT_HF, "full_commit");
1326
    else
1327
      $request->setHeaderField(Request::X_COUCHDB_FULL_COMMIT_HF, "delay_commit");
1328
1329
    if ($allOrNothing)
1330
      $operations['all_or_nothing'] = 'true';
1331
1332
    if (!$newEdits)
1333
      $operations['new_edits'] = 'false';
1334
1335
    foreach ($docs as $doc) {
1336
      $this->setDocInfo($doc);
1337
      $operations['docs'][] = $doc->asArray();
1338
    }
1339
1340
    $request->setBody(json_encode($operations));
1341
1342
    return $this->send($request);
1343
  }
1344
1345
  //!@}
1346
1347
1348
  /** @name Attachments Management Methods */
1349
  //!@{
1350
1351
1352
  /**
1353
   * @brief Returns the minimal amount of information about the specified attachment.
1354
   * @param[in] string $dbName The database name.
1355
   * @param[in] string $fileName The attachment's name.
1356
   * @param[in] string $docId The document's identifier.
1357
   * @param[in] string $path The document's path.
1358
   * @param[in] string $rev (optional) The document's revision.
1359
   * @retval string The document's revision.
1360
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#db-doc-attachment
1361
   */
1362 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...
1363
    $this->validateDocPath($path, TRUE);
1364
    $this->validateAndEncodeDocId($docId);
1365
1366
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".$fileName;
1367
1368
    $request = new Request(Request::HEAD_METHOD, $path);
1369
1370
    // In case we want retrieve a specific document revision.
1371
    if (!empty($rev))
1372
      $request->setQueryParam("rev", (string)$rev);
1373
1374
    return $this->send($request);
1375
  }
1376
1377
1378
  /**
1379
   * @brief Retrieves the attachment from the specified document.
1380
   * @param[in] string $dbName The database name.
1381
   * @param[in] string $fileName The attachment's name.
1382
   * @param[in] string $docId The document's identifier.
1383
   * @param[in] string $path The document's path.
1384
   * @param[in] string $rev (optional) The document's revision.
1385
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#get--db-docid-attname
1386
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#http-range-requests
1387
   * @todo Add support for Range request, using header "Range: bytes=0-12".
1388
   */
1389 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...
1390
    $this->validateDocPath($path, TRUE);
1391
    $this->validateAndEncodeDocId($docId);
1392
1393
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".$fileName;
1394
1395
    $request = new Request(Request::GET_METHOD, $path);
1396
1397
    // In case we want retrieve a specific document revision.
1398
    if (!empty($rev))
1399
      $request->setQueryParam("rev", (string)$rev);
1400
1401
    return $this->send($request)->getBody();
1402
  }
1403
1404
1405
  /**
1406
   * @brief Inserts or updates an attachment to the specified document.
1407
   * @param[in] string $dbName The database name.
1408
   * @param[in] string $fileName The attachment's name.
1409
   * @param[in] string $docId The document's identifier.
1410
   * @param[in] string $path The document's path.
1411
   * @param[in] string $rev (optional) The document's revision.
1412
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#put--db-docid-attname
1413
   */
1414
  public function putAttachment($dbName, $fileName, $path, $docId, $rev = NULL) {
1415
    $this->validateDocPath($path, TRUE);
1416
    $this->validateAndEncodeDocId($docId);
1417
1418
    $attachment = Doc\Attachment\Attachment::fromFile($fileName);
1419
1420
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".rawurlencode($attachment->getName());
1421
1422
    $request = new Request(Request::PUT_METHOD, $path);
1423
    $request->setHeaderField(Request::CONTENT_LENGTH_HF, $attachment->getContentLength());
1424
    $request->setHeaderField(Request::CONTENT_TYPE_HF, $attachment->getContentType());
1425
    $request->setBody(base64_encode($attachment->getData()));
1426
1427
    // In case of adding or updating an existence document.
1428
    if (!empty($rev))
1429
      $request->setQueryParam("rev", (string)$rev);
1430
1431
    return $this->send($request);
1432
  }
1433
1434
1435
  /**
1436
   * @brief Deletes an attachment from the document.
1437
   * @param[in] string $dbName The database name.
1438
   * @param[in] string $fileName The attachment's name.
1439
   * @param[in] string $docId The document's identifier.
1440
   * @param[in] string $path The document's path.
1441
   * @param[in] string $rev The document's revision.
1442
   * @see http://docs.couchdb.org/en/latest/api/document/attachments.html#delete--db-docid-attname
1443
   */
1444 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...
1445
    $this->validateDocPath($path, TRUE);
1446
    $this->validateAndEncodeDocId($docId);
1447
1448
    $path = "/".rawurlencode($this->prefix.$dbName)."/".$path.$docId."/".rawurlencode($fileName);
1449
1450
    $request = new Request(Request::DELETE_METHOD, $path);
1451
    $request->setQueryParam("rev", (string)$rev);
1452
1453
    return $this->send($request);
1454
  }
1455
1456
  //!@}
1457
1458
1459
  /** @name Special Design Documents Management Methods */
1460
  //!@{
1461
1462
1463
  /**
1464
   * @brief Returns basic information about the design document and his views.
1465
   * @param[in] string $dbName The database name.
1466
   * @param[in] string $docName The design document's name.
1467
   * @retval array An associative array
1468
   * @see http://docs.couchdb.org/en/latest/api/ddoc/common.html#get--db-_design-ddoc-_info
1469
   */
1470 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...
1471
    $this->validateAndEncodeDocId($docName);
1472
1473
    $path = "/".rawurlencode($this->prefix.$dbName)."/".self::DESIGN_DOC_PATH.$docName."/_info";
1474
1475
    $request = new Request(Request::GET_METHOD, $path);
1476
1477
    return $this->send($request)->getBodyAsArray();
1478
  }
1479
1480
1481
  /**
1482
   * @brief
1483
   * @details
1484
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_show-func
1485
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_show-func
1486
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_show-func-docid
1487
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_show-func-docid
1488
   * @todo To be implemented and documented.
1489
   */
1490
  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...
1491
    throw new \BadMethodCallException("The method `showDoc()` is not available.");
1492
    // Invokes the show handler without a document
1493
    // /db/_design/design-doc/_show/show-name
1494
    // Invokes the show handler for the given document
1495
    // /db/_design/design-doc/_show/show-name/doc
1496
    // GET /db/_design/examples/_show/posts/somedocid
1497
    // GET /db/_design/examples/_show/people/otherdocid
1498
    // GET /db/_design/examples/_show/people/otherdocid?format=xml&details=true
1499
    // 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...
1500
  }
1501
1502
1503
  /**
1504
   * @brief
1505
   * @details
1506
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_list-func-view
1507
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_list-func-view
1508
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#get--db-_design-ddoc-_list-func-other-ddoc-view
1509
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_list-func-other-ddoc-view
1510
   * @todo To be implemented and documented.
1511
   */
1512
  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...
1513
    throw new \BadMethodCallException("The method `listDocs()` is not available.");
1514
    // Invokes the list handler to translate the given view results
1515
    // Invokes the list handler to translate the given view results for certain documents
1516
    // GET /db/_design/examples/_list/index-posts/posts-by-date?descending=true&limit=10
1517
    // GET /db/_design/examples/_list/index-posts/posts-by-tag?key="howto"
1518
    // GET /db/_design/examples/_list/browse-people/people-by-name?startkey=["a"]&limit=10
1519
    // GET /db/_design/examples/_list/index-posts/other_ddoc/posts-by-tag?key="howto"
1520
    // 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...
1521
  }
1522
1523
1524
  /**
1525
   * @brief
1526
   * @details
1527
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#post--db-_design-ddoc-_update-func
1528
   * @see http://docs.couchdb.org/en/latest/api/ddoc/render.html#put--db-_design-ddoc-_update-func-docid
1529
   * @todo To be implemented and documented.
1530
   */
1531
  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...
1532
    throw new \BadMethodCallException("The method `callUpdateDocFunc()` is not available.");
1533
    // Invokes the update handler without a document
1534
    // /db/_design/design-doc/_update/update-name
1535
    // Invokes the update handler for the given document
1536
    // /db/_design/design-doc/_update/update-name/doc
1537
    // a PUT request against the handler function with a document id: /<database>/_design/<design>/_update/<function>/<docid>
1538
    // a POST request against the handler function without a document id: /<database>/_design/<design>/_update/<function>
1539
  }
1540
1541
  //!@}
1542
1543
}